diff --git a/build/build.xml b/build/build.xml new file mode 100644 index 00000000..c586b1c3 --- /dev/null +++ b/build/build.xml @@ -0,0 +1,396 @@ + + + + + + + + + + + + + Jive Spark Build + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/build/builder/build/build.xml b/build/builder/build/build.xml new file mode 100644 index 00000000..803cc840 --- /dev/null +++ b/build/builder/build/build.xml @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/build/builder/how-to-build.txt b/build/builder/how-to-build.txt new file mode 100644 index 00000000..10cb28ab --- /dev/null +++ b/build/builder/how-to-build.txt @@ -0,0 +1,16 @@ +Building a Sparkplug + +To easily build a Sparkplug, we have added a simple ANT script to create a deployed plug. To create, do the following: + +1) Copy your java source files to the src directory. +2) Place any dependencies (besides Spark) into the lib directory. +3) Update the plugin.xml file to represent your plugin. +4) Go to the build directory, and type ant jar to build your plugin or +.... type "ant run" to build and deploy your plugin directly to Spark and +have Spark startup to test your plugin right away. + +Your new plugin will be called myplugin.jar. + +If you wish to deploy your plugin later, just copy your new myplugin.jar to the plugins directory of your Sparkplug distro kit. + +Enjoy! diff --git a/build/builder/plugin.xml b/build/builder/plugin.xml new file mode 100644 index 00000000..77870b13 --- /dev/null +++ b/build/builder/plugin.xml @@ -0,0 +1,11 @@ + + + My Plugin + 1.0 + You + http://www.jivesoftware.org + foo@foo.com + Sample Plugin + com.jivesoftware.plugin.MyPlugin + + diff --git a/build/built_target.bat b/build/built_target.bat new file mode 100644 index 00000000..57eafe4e --- /dev/null +++ b/build/built_target.bat @@ -0,0 +1 @@ +ant -f %1 jar \ No newline at end of file diff --git a/build/built_target.sh b/build/built_target.sh new file mode 100644 index 00000000..d0bbc85f --- /dev/null +++ b/build/built_target.sh @@ -0,0 +1 @@ +ant -f $1 jar diff --git a/build/installer/images/liveassistant-16x16.png b/build/installer/images/liveassistant-16x16.png new file mode 100644 index 00000000..405a6a51 Binary files /dev/null and b/build/installer/images/liveassistant-16x16.png differ diff --git a/build/installer/images/liveassistant-32x32.png b/build/installer/images/liveassistant-32x32.png new file mode 100644 index 00000000..410fdc58 Binary files /dev/null and b/build/installer/images/liveassistant-32x32.png differ diff --git a/build/installer/spark.install4j b/build/installer/spark.install4j new file mode 100644 index 00000000..e8a8af5e --- /dev/null +++ b/build/installer/spark.install4j @@ -0,0 +1,153 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/build/lib/dist/activation.jar b/build/lib/dist/activation.jar new file mode 100644 index 00000000..aaf5e8d1 Binary files /dev/null and b/build/lib/dist/activation.jar differ diff --git a/build/lib/dist/dom4j.jar b/build/lib/dist/dom4j.jar new file mode 100644 index 00000000..c8c4dbb9 Binary files /dev/null and b/build/lib/dist/dom4j.jar differ diff --git a/build/lib/dist/linux/jdic.jar b/build/lib/dist/linux/jdic.jar new file mode 100644 index 00000000..80cff768 Binary files /dev/null and b/build/lib/dist/linux/jdic.jar differ diff --git a/build/lib/dist/linux/libjdic.so b/build/lib/dist/linux/libjdic.so new file mode 100644 index 00000000..419912a0 Binary files /dev/null and b/build/lib/dist/linux/libjdic.so differ diff --git a/build/lib/dist/linux/libmozembed-linux-gtk1.2.so b/build/lib/dist/linux/libmozembed-linux-gtk1.2.so new file mode 100644 index 00000000..cd3647f9 Binary files /dev/null and b/build/lib/dist/linux/libmozembed-linux-gtk1.2.so differ diff --git a/build/lib/dist/linux/libmozembed-linux-gtk2.so b/build/lib/dist/linux/libmozembed-linux-gtk2.so new file mode 100644 index 00000000..8e114777 Binary files /dev/null and b/build/lib/dist/linux/libmozembed-linux-gtk2.so differ diff --git a/build/lib/dist/linux/libtray.so b/build/lib/dist/linux/libtray.so new file mode 100644 index 00000000..3495214b Binary files /dev/null and b/build/lib/dist/linux/libtray.so differ diff --git a/build/lib/dist/linux/mozembed-linux-gtk1.2 b/build/lib/dist/linux/mozembed-linux-gtk1.2 new file mode 100644 index 00000000..fb6fd9bc Binary files /dev/null and b/build/lib/dist/linux/mozembed-linux-gtk1.2 differ diff --git a/build/lib/dist/linux/mozembed-linux-gtk2 b/build/lib/dist/linux/mozembed-linux-gtk2 new file mode 100644 index 00000000..72f5877b Binary files /dev/null and b/build/lib/dist/linux/mozembed-linux-gtk2 differ diff --git a/build/lib/dist/smack.jar b/build/lib/dist/smack.jar new file mode 100644 index 00000000..740cf28e Binary files /dev/null and b/build/lib/dist/smack.jar differ diff --git a/build/lib/dist/smackx-debug.jar b/build/lib/dist/smackx-debug.jar new file mode 100644 index 00000000..ba636498 Binary files /dev/null and b/build/lib/dist/smackx-debug.jar differ diff --git a/build/lib/dist/smackx.jar b/build/lib/dist/smackx.jar new file mode 100644 index 00000000..c53c0217 Binary files /dev/null and b/build/lib/dist/smackx.jar differ diff --git a/build/lib/dist/windows/IeEmbed.exe b/build/lib/dist/windows/IeEmbed.exe new file mode 100644 index 00000000..059159fc Binary files /dev/null and b/build/lib/dist/windows/IeEmbed.exe differ diff --git a/build/lib/dist/windows/MozEmbed.exe b/build/lib/dist/windows/MozEmbed.exe new file mode 100644 index 00000000..aae7f7a5 Binary files /dev/null and b/build/lib/dist/windows/MozEmbed.exe differ diff --git a/build/lib/dist/windows/jdic.dll b/build/lib/dist/windows/jdic.dll new file mode 100644 index 00000000..b27b9466 Binary files /dev/null and b/build/lib/dist/windows/jdic.dll differ diff --git a/build/lib/dist/windows/jdic.jar b/build/lib/dist/windows/jdic.jar new file mode 100644 index 00000000..1fe5b39c Binary files /dev/null and b/build/lib/dist/windows/jdic.jar differ diff --git a/build/lib/dist/windows/tray.dll b/build/lib/dist/windows/tray.dll new file mode 100644 index 00000000..e572d5da Binary files /dev/null and b/build/lib/dist/windows/tray.dll differ diff --git a/build/lib/dist/xpp.jar b/build/lib/dist/xpp.jar new file mode 100644 index 00000000..43486c28 Binary files /dev/null and b/build/lib/dist/xpp.jar differ diff --git a/build/lib/dist/xstream.jar b/build/lib/dist/xstream.jar new file mode 100644 index 00000000..e212047b Binary files /dev/null and b/build/lib/dist/xstream.jar differ diff --git a/build/lib/i4jruntime.jar b/build/lib/i4jruntime.jar new file mode 100644 index 00000000..ed00e2bb Binary files /dev/null and b/build/lib/i4jruntime.jar differ diff --git a/build/lib/jarbundler-1.4.jar b/build/lib/jarbundler-1.4.jar new file mode 100644 index 00000000..97ad30f0 Binary files /dev/null and b/build/lib/jarbundler-1.4.jar differ diff --git a/build/lib/merge/Wrapper.dll b/build/lib/merge/Wrapper.dll new file mode 100644 index 00000000..3fea10f8 Binary files /dev/null and b/build/lib/merge/Wrapper.dll differ diff --git a/build/lib/merge/asterisk-im-client.jar b/build/lib/merge/asterisk-im-client.jar new file mode 100644 index 00000000..f335d1b1 Binary files /dev/null and b/build/lib/merge/asterisk-im-client.jar differ diff --git a/build/lib/merge/backport-util-concurrent.jar b/build/lib/merge/backport-util-concurrent.jar new file mode 100644 index 00000000..6679e38e Binary files /dev/null and b/build/lib/merge/backport-util-concurrent.jar differ diff --git a/build/lib/merge/commons-codec.jar b/build/lib/merge/commons-codec.jar new file mode 100644 index 00000000..957b6752 Binary files /dev/null and b/build/lib/merge/commons-codec.jar differ diff --git a/build/lib/merge/commons-httpclient.jar b/build/lib/merge/commons-httpclient.jar new file mode 100644 index 00000000..9a6881b7 Binary files /dev/null and b/build/lib/merge/commons-httpclient.jar differ diff --git a/build/lib/merge/commons-logging.jar b/build/lib/merge/commons-logging.jar new file mode 100644 index 00000000..b99c9375 Binary files /dev/null and b/build/lib/merge/commons-logging.jar differ diff --git a/build/lib/merge/jaxen.jar b/build/lib/merge/jaxen.jar new file mode 100644 index 00000000..32cd52ff Binary files /dev/null and b/build/lib/merge/jaxen.jar differ diff --git a/build/lib/merge/jniwrap.jar b/build/lib/merge/jniwrap.jar new file mode 100644 index 00000000..833bf178 Binary files /dev/null and b/build/lib/merge/jniwrap.jar differ diff --git a/build/lib/merge/jtoaster-1.0.4.jar b/build/lib/merge/jtoaster-1.0.4.jar new file mode 100644 index 00000000..aaf3f6ea Binary files /dev/null and b/build/lib/merge/jtoaster-1.0.4.jar differ diff --git a/build/lib/merge/looks.jar b/build/lib/merge/looks.jar new file mode 100644 index 00000000..2495ebb2 Binary files /dev/null and b/build/lib/merge/looks.jar differ diff --git a/build/lib/merge/swingx.jar b/build/lib/merge/swingx.jar new file mode 100644 index 00000000..48c637d6 Binary files /dev/null and b/build/lib/merge/swingx.jar differ diff --git a/build/lib/merge/systeminfo.jar b/build/lib/merge/systeminfo.jar new file mode 100644 index 00000000..73ff10b0 Binary files /dev/null and b/build/lib/merge/systeminfo.jar differ diff --git a/build/lib/merge/updater.jar b/build/lib/merge/updater.jar new file mode 100644 index 00000000..9ac1274e Binary files /dev/null and b/build/lib/merge/updater.jar differ diff --git a/build/lib/merge/versions.txt b/build/lib/merge/versions.txt new file mode 100644 index 00000000..982be9d1 --- /dev/null +++ b/build/lib/merge/versions.txt @@ -0,0 +1,7 @@ +hessian.jar -- hessian-3.0.8.jar +xpp3.jar -- xpp3-1.1.3.4d_b4_min.jar +xstream.jar -- xstream-1.1.jar +looks.jar -- looks-1.3.jar + +agent/jniwrap.jar -- jniwrap-2.7.1.jar +agent/winlaf.jar -- winlaf-0.5.jar \ No newline at end of file diff --git a/build/projects/Spark.ipr b/build/projects/Spark.ipr new file mode 100644 index 00000000..5998e515 --- /dev/null +++ b/build/projects/Spark.ipr @@ -0,0 +1,201 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/build/projects/Spark.iws b/build/projects/Spark.iws new file mode 100644 index 00000000..082ad76b --- /dev/null +++ b/build/projects/Spark.iws @@ -0,0 +1,597 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/documentation/LICENSE.html b/documentation/LICENSE.html new file mode 100644 index 00000000..b71d4560 --- /dev/null +++ b/documentation/LICENSE.html @@ -0,0 +1,313 @@ + +LICENSE AGREEMENT + + + + + +

Spark End User License +Agreement

+ +

THIS IS A +LEGAL AGREEMENT between "you," the end user of the Spark +software and any related plugin software, and CoolServlets, Inc. DBA Jive +Software, a Delaware limited liability company ("Jive Software").

+ +

BY +COMPLETING THE ONLINE REGISTRATION FORM AND CLICKING THE "I AGREE" BUTTON, YOU +SUBMIT TO JIVE SOFTWARE AN OFFER TO OBTAIN THE RIGHT TO USE THE LICENSED +PRODUCTS (DEFINED BELOW) UNDER THE PROVISIONS OF THIS END USER LICENSE +AGREEMENT (THE "AGREEMENT"), UNLESS A SEPARATE LICENSE AGREEMENT, WHICH, +EXPRESSLY BY ITS TERMS, HAS PRECEDENCE OVER THIS AGREEMENT, HAS BEEN SIGNED BY +BOTH PARTIES.

+ +

BY CLICKING THE "I AGREE" BUTTON, YOU HEREBY AGREE THAT YOU +HAVE THE REQUISITE AUTHORITY, POWER AND RIGHT TO FULLY BIND THE PERSON AND/OR +ENTITIES (COLLECTIVELY,"YOU") WISHING TO USE THE LICENSED PRODUCTS PURSUANT TO +THIS AGREEMENT. If YOU DO NOT HAVE THE +AUTHORITY TO BIND SUCH PERSON OR ENTITY OR YOU OR SUCH PERSON OR ENTITY do not +agree to any of the terms below, JIVE SOFTWARE is unwilling to PROVIDE THE +LICENSED PRODUCTS TO THE LICENSEE, and you should click on the "Do Not Accept" +button below to discontinue the REGISTRATION process.

+ +

As used in +this Agreement, the capitalized term "Licensed Products" means, +collectively, (a) the freeware version of the Spark instant +messaging software("Spark"), (b) the related plugin software for +which no fee is charged, as set forth in the registration form ("Free +Plugins"), (c) the related plugin software for which a fee is charged, as set +forth in the registration form ("Commercial Plugins") and (d) any and all +enhancements, upgrades, and updates that may be provided to you in the future +by Jive Licensed Products from time to time and in its sole discretion +("Upgrades"). 

+ +

Section B +applies solely with respect to Spark and any Free Plugins. Section +C applies solely with respect to Commercial Plugins to Spark. The +remainder of this Agreement applies to all Licensed Products.

+ +

A.   Ownership

+ +

The +Licensed Products and any accompanying documentation are owned by Jive Software +and ownership of the Licensed Products and such documentation shall at all +times remain with Jive Software.  Copies are provided to you only to allow +you to exercise your rights under this Agreement.  This Agreement does not +constitute a sale of the Licensed Products or any accompanying documentation, +or any portion thereof.  Without limiting the generality of the foregoing, +you do not receive any rights to any patents, copyrights, trade secrets, +trademarks or other intellectual property rights relating to or in the Licensed +Products or any accompanying documentation other than as expressly set forth in +the license grants in this Agreement.  All rights not expressly granted to +you under this Agreement are reserved by Jive Licensed Products.

+ +

B.   Grant of License Applicable To Spark and any Freeware Plugins

+ +

Subject to +the terms and conditions set out in this Agreement, Jive Software grants you a +limited, nonexclusive, nontransferable, nonsublicensable, and revocable right +to use the Spark and any Free Plugins, together the "Free Licensed +Products," solely in accordance with the following terms and conditions:

+ +

1.      Use of the Free Licensed +Products.  The Free Licensed Products is being distributed as +freeware.  This means it may be freely used, copied and distributed as +long as it is not sold or distributed for any consideration and all original +files are included, including this Agreement and Jive Software's copyright +notice.  You may use the Free Licensed Products on as many computers as +you require.

+ +

2.      Distribution +Permitted.  You may make copies of your copy of the Free Licensed Products +to give to others provided that such copies are not modified from the original +downloaded copy of the Free Licensed Products.  You may not charge a fee +for distributing copies of the Free Licensed Products except that freeware +distribution companies may charge their normal shipping and handling fees not +to exceed $5.00 U.S. per copy. If any copies of the Free Licensed Products +are distributed, Jive Licensed Products requires that you send Jive Licensed +Products an e-mail addressed to info@jivesoftware.com +notifying Jive Licensed Products of such distribution and the identity of the +person or entity receiving the copy of the Free Licensed Products including a +listing of which products such person received.

+ +

3.      Termination.  Jive +Software may terminate your license in the Free Licensed Products at any time, +for any reason or no reason. 

+ +

4.      Fees.  There is no +license fee for the Free Licensed Products.  If you wish to receive the +Commercial Plugins (defined below) for Spark, you will be required +to pay the applicable license fee.

+ +

C.   Grant of License Applicable To any Commercial Plugins for Spark

+ +

Subject to +the terms and conditions set out in this Agreement, Jive Software grants you a +limited, nonexclusive, nontransferable, nonsublicensable and revocable right to +use the Commercial Plugins solely in accordance with the following terms and +conditions:

+ +

1.      Use of Commercial +Plugins.  You may download and internally use the Commercial Plugins on +multiple computers owned, leased or rented by you; however, you are only +allowed to run the Commercial Plugins on (a) your own computer, or (b) on as +many computers as you have purchased seat licenses ("Seat Licenses"), as listed +on the product invoice ("Invoice") made available to you via the world wide web +after you have submitted a purchase order for such Commercial Plugins, or on +the receipt ("Receipt") made available to you via the world wide web after you +have submitted an online order for such Commercial Plugins. All copies of +Spark and its Commercial Plugins must include Jive Licensed +Product's copyright notice.

+ +

2.      Distribution +Prohibited.  You may not distribute copies of the Commercial Plugins for +use by any individual other than to those computers for which you have +purchased the Commercial Plugins Seat Licenses. Distribution to or +allowing any third party access or use of the Commercial Plugins by you, +whether by means of a service bureau, lease or otherwise, is hereby expressly prohibited.

+ +

3.      Fees.  You shall pay +to Jive Software all "Fees" consisting of "License Fees" for the Licensed +Products and related Maintenance and Support Fees ("Maintenance and Support +Fees"). Maintenance and Support Fees are good for one (1) year for application +for which Maintenance and Support Services Fee has been paid as of the date of +the invoice or online purchase and/or any anniversaries of such date, and then +annual payment of Maintenance and Support Services Fee required for renewal +thereafter, unless Licensee has paid in advance for future years. All such +Fees shall be as listed on the Invoice and/or Receipt.

+ +

4.      Maintenance and +Support.  Jive Licensed Products will provide you with support services +("Support Services") for a period that begins on the purchase date and ends 365 +days later, unless you elect to continue paying the Maintenance and Support +Fees.  The nature, scope and extent of Support Services shall be as set +forth on Jive Software's website at http://www.jivesoftware.com/support/overview.jsp. +Support terms are subject to change in Jive Software's sole discretion. Jive +Software will also provide you with Upgrades for a period that begins on the +purchase date and ends 365 days later, unless you elect to continue paying the +Maintenance and Support Fees. Such Upgrades will include any Upgrades for +Spark and the Commercial Plugins that are released by Jive Licensed +Products for general distribution during the one year period for which you are +entitled to receive free Upgrades.  Jive Licensed Products has no +obligation to provide you with any Upgrades that are not released for general +distribution to Jive Licensed Products's other licensees.  Nothing in this +Agreement shall be construed to obligate Jive Licensed Products to provide +Upgrades to you under any circumstances.

+ +

D.   Prohibited Conduct

+ +

You +represent and warrant that you will not violate any of the terms and conditions +set forth in this Agreement and that:

+ +

You will +not, and will not permit others to: (i) reverse engineer, decompile, +disassemble, derive the source code of, modify, or create derivative works from +the Licensed Products; or (ii) use, copy, modify, alter, or transfer, +electronically or otherwise, the Licensed Products or any of the accompanying +documentation except as expressly permitted in this Agreement; or (iii) +redistribute, sell, rent, lease, sublicense, or otherwise transfer rights to +the Licensed Products whether in a stand-alone configuration or as incorporated +with other software code written by any party except as expressly permitted in +this Agreement.

+ +

You will +not use the Licensed Products to engage in or allow others to engage in any +illegal activity.

+ +

You will +not engage in use of the Licensed Products that will interfere with or damage +the operation of the services of third parties by overburdening/disabling +network resources through automated queries, excessive usage or similar +conduct.

+ +

You will +not use the Licensed Products to engage in any activity that will violate the +rights of third parties, including, without limitation, through the use, public +display, public performance, reproduction, distribution, or modification of +communications or materials that infringe copyrights, trademarks, publicity +rights, privacy rights, other proprietary rights, or rights against defamation +of third parties.

+ +

You will +not transfer the Licensed Products or utilize the Licensed Products in +combination with third party software authored by you or others to create an +integrated software program which you transfer to unrelated third parties.

+ +

E.   Upgrades, Updates And Enhancements

+ +

All +Upgrades shall be deemed to be part of the Licensed Products and will be +subject to this Agreement.

+ +

F.   Disclaimer of Warranty

+ +

THE +LICENSED PRODUCTS ARE PROVIDED ON AN AS IS BASIS, WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING, WITHOUT LIMITATION, THE WARRANTIES THAT IT IS +FREE OF DEFECTS, VIRUS FREE, ABLE TO OPERATE ON AN UNINTERRUPTED BASIS, +MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE, WITH CLEAR TITLE OR +NON-INFRINGING.  THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART +OF THIS LICENSE AND AGREEMENT.  NO USE OF THE LICENSED PRODUCTS ARE +AUTHORIZED HEREUNDER EXCEPT UNDER THIS DISCLAIMER. JIVE SOFTWARE DOES NOT +GUARANTEE THAT ANY OF THE LICENSED PRODUCTS SHALL MEET YOUR SPECIFIC NEEDS.

+ +

G.   Limitation of Liability

+ +

TO THE +MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, IN NO EVENT WILL JIVE SOFTWARE BE +LIABLE FOR ANY INDIRECT, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OF OR INABILITY TO USE THE LICENSED PRODUCTS, INCLUDING, WITHOUT +LIMITATION, DAMAGES FOR LOST PROFITS, LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER +FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN +IF ADVISED OF THE POSSIBILITY THEREOF, AND REGARDLESS OF THE LEGAL OR EQUITABLE +THEORY (CONTRACT, TORT OR OTHERWISE) UPON WHICH THE CLAIM IS BASED.  IN +ANY CASE, JIVE SOFTWARE'S COLLECTIVE LIABILITY UNDER ANY PROVISION OF THIS +LICENSE SHALL NOT EXCEED IN THE AGGREGATE (1) WITH RESPECT TO +COMMERCIAL PLUGINS, THE SUM OF THE FEES YOU PAID FOR THE LICENSE TO SUCH +COMMERCIAL PLUGINS AND (2) WITH RESPECT TO FREE LICENSED PRODUCTS, $100.

+ +

H.   Export Control

+ +

The +Licensed Products may contain encryption and is subject to United States export control laws and regulations and may be subject to export or import regulations +in other countries, including controls on encryption products.  You agree +that you will not export, re-export or transfer the Licensed Products in +violation of any applicable laws or regulations of the United States or the country where you legally obtained it.  You are responsible for obtaining +any licenses to export, re-export, transfer or import the Licensed Products.

+ +

In addition +to the above, the Licensed Products may not be used by, or exported or +re-exported to: (i) any U.S. or EU sanctioned or embargoed country, or to +nationals or residents of such countries; or (ii) to any person, entity or +organization or other party identified on the U.S. Department of Commerce.s +Table of Denial Orders or the U.S. Department of Treasury.s lists of .Specially +Designated Nationals and Blocked Persons,. as published and revised from time +to time; (iii) to any party engaged in nuclear, chemical/biological weapons or +missile proliferation activities, unless authorized by U.S. and local (as +required) law or regulations.

+ +

I.   Legends and Notices

+ +

You agree +that you will not remove or alter any trademark, logo, copyright or other +proprietary notices, legends, symbols or labels in the Licensed Products or any +accompanying documentation.

+ +

J.   Term and Termination

+ +

This +Agreement is effective upon your acceptance as provided herein and payment of +the applicable Fees (if any), and will remain in force until terminated.  +You may terminate the licenses granted in this Agreement at any time by +destroying the Licensed Products and any accompanying documentation, together +with any and all copies thereof.  The licenses granted in this Agreement +will terminate automatically if you breach any of its terms or conditions or +any of the terms or conditions of any other agreement between you and Jive +Licensed Products.  Upon termination, you shall immediately destroy the +original and all copies of the Licensed Products and any accompanying +documentation, or return them to Jive Licensed Products and you shall retain no +further rights in or to the Licensed Products.

+ +

K.   Licensed Products Suggestions

+ +

Jive +Software welcomes suggestions for enhancing the Licensed Products and any +accompanying documentation that may result in computer programs, reports, +presentations, documents, ideas or inventions relating or useful to Jive +Software's business.  You acknowledge that all title, ownership rights, and +intellectual property rights concerning such suggestions shall become the +exclusive property of Jive Software and may be used for its business purposes +in its sole discretion without any payment or accounting to you and you hereby +assign all such rights to Jive Software irrevocably.

+ +

Miscellaneous

+ +

This +Agreement constitutes the entire agreement between the parties concerning the +Licensed Products, and may be amended only by a writing signed by both parties +that expressly references this Agreement.  This Agreement shall be +governed by the laws of the State of Oregon, excluding its conflict of law +provisions.  All disputes relating to this Agreement are subject to the +exclusive jurisdiction of the courts of Multnomah County, Oregon and you +expressly consent to the exercise of personal jurisdiction in the courts of Multnomah County, Oregon in connection with any such dispute.  This Agreement shall +not be governed by the United Nations Convention on Contracts for the +International Sale of Goods.  If any provision in this Agreement should be +held illegal or unenforceable by a court of competent jurisdiction, such +provision shall be modified to the extent necessary to render it enforceable +without losing its intent, or severed from this Agreement if no such modification +is possible, and other provisions of this Agreement shall remain in full force +and effect.  A waiver by either party of any term or condition of this +Agreement or any breach thereof, in any one instance, shall not waive such term +or condition or any subsequent breach thereof. You will indemnify Jive Software +for any breach of the terms or conditions of this Agreement.

+ +
+ + \ No newline at end of file diff --git a/documentation/README.html b/documentation/README.html new file mode 100644 index 00000000..7f88efe9 --- /dev/null +++ b/documentation/README.html @@ -0,0 +1,162 @@ + + + + + + + Jive Spark README + + + + + + + + + + + + + + + +

Jive Spark README

+ + + +

+ + + + + + + + + + + + + + + + + +
version:2.0 Beta
released:June 20, 2006
+ + +

Thank you for choosing Spark!

+ +

Spark is a full-featured instant messaging (IM) client that uses the XMPP protocol.

+ +

Documentation

+ +

Basic information on Spark can be found in the install guide and + on the Jive Software website.

+ +

If you need additional help using or installing Spark, + please visit the + online support forums. Commercial support (email and phone) from + Jive Software Support is also available. + +

Changelog

+ +View the changelog for a list of changes since the last release. + +

License Agreements

+ +

By using this software, you agree to the terms of the included license agreement. +

+ + + + + diff --git a/documentation/builder.jar b/documentation/builder.jar new file mode 100644 index 00000000..9f293e22 Binary files /dev/null and b/documentation/builder.jar differ diff --git a/documentation/changelog.html b/documentation/changelog.html new file mode 100644 index 00000000..fbe7a092 --- /dev/null +++ b/documentation/changelog.html @@ -0,0 +1,388 @@ + + + + + Spark Changelog + + + + + + + + + +

Spark Changelog

+

+ +2.0 Beta -- June 20th, 2006 +

+

New Feature

+ + +

Bug

+ + +

Improvement

+ + + +

+ +1.1.4 -- April 13, 2006 + +

+ +

New Features

+ +

Bug

+ + +

Improvements

+ + + 1.1.3 -- March 15, 2006 + +

+ +

Bug

+ + +

Improvement

+ + + + 1.1.2 -- March 9, 2006 + +

+ +

New Features

+ + +

Improvements

+ + +

Bug Fixes

+ + +

+ + 1.1.1 -- February 16, 2006 + +

+ +

+

Bug Fixes

+ + +

+ + 1.1.0 -- February 9, 2006 + +

+ +

New Features

+ + +

Bug Fixes

+ + +

+ + 1.0.4 -- January 23, 2006 + +

+ +

New Features

+ + +

Bug Fixes

+ + +

+ + 1.0.3 -- January 5, 2006 + +

+ +

New Features

+ + +

Bug Fixes

+ + +

+ + 1.0.2 -- December 15, 2005 + +

+ +

New Features

+ + +

Bug Fixes

+ + +

+ + 1.0.1 -- December 1, 2005 + +

+ +

New Features

+ + +

Bug Fixes

+ + +

+ + 1.0.0 -- November 17, 2005 + +

+ +

+ + + + + + diff --git a/documentation/changes.xsl b/documentation/changes.xsl new file mode 100644 index 00000000..425a7fbe --- /dev/null +++ b/documentation/changes.xsl @@ -0,0 +1,27 @@ + + + + + +

Change log

+ + + + +
+ + + + + + +
  • + : + [] -
  • +
    + +
    \ No newline at end of file diff --git a/documentation/images/banner-spark.gif b/documentation/images/banner-spark.gif new file mode 100644 index 00000000..2706beac Binary files /dev/null and b/documentation/images/banner-spark.gif differ diff --git a/documentation/images/banner-spring.gif b/documentation/images/banner-spring.gif new file mode 100644 index 00000000..a72691c0 Binary files /dev/null and b/documentation/images/banner-spring.gif differ diff --git a/documentation/images/chat-room.png b/documentation/images/chat-room.png new file mode 100644 index 00000000..f184cf0a Binary files /dev/null and b/documentation/images/chat-room.png differ diff --git a/documentation/images/contact-list.png b/documentation/images/contact-list.png new file mode 100644 index 00000000..250b4bec Binary files /dev/null and b/documentation/images/contact-list.png differ diff --git a/documentation/images/login-dialog.png b/documentation/images/login-dialog.png new file mode 100644 index 00000000..6932444e Binary files /dev/null and b/documentation/images/login-dialog.png differ diff --git a/documentation/install-guide.html b/documentation/install-guide.html new file mode 100644 index 00000000..4aa279a1 --- /dev/null +++ b/documentation/install-guide.html @@ -0,0 +1,44 @@ + + + + Spark Installation Guide + + + + + + + +

    Spark Installation Guide

    + +

    Spark consists of the following: + +

    + + +

    Installation

    + + + + + diff --git a/documentation/sparkplug_dev_guide.html b/documentation/sparkplug_dev_guide.html new file mode 100644 index 00000000..dc04fc19 --- /dev/null +++ b/documentation/sparkplug_dev_guide.html @@ -0,0 +1,1043 @@ + + + Sparkplug Development Guide + + + + +
    + + + + + + +
    Spark
    +
    +
    +

    What Are Sparkplugs?

    + +

    Sparkplugs dynamically extend the features of the Spark instant messaging client. +Use Sparkplugs to customize Spark for your business or organization or to add an innovative twist to instant messaging. The +extensive plugin API allows for complete client flexibility but is still simple and (we hope!) fun to use.

    + +

    This guide provides a high level overview of the Sparkplug API and examples of several common client customizations. Or, jump +directly into the Javadocs. Online, you'll find several other resources: + +

    +

    + +

    Once I Build It, Then What?

    +

    After you've built your amazingly cool Sparkplug, it's easy to rollout to your users. Just have them drop it in the plugins directory of +their Spark installation. If your Sparkplug is generally useful, we hope you'll share it with the whole Spark community! To make your Sparkplug +available via the public repository at jivesoftware.org, submit it to +plugins@jivesoftware.org.

    + + +

    Contents

    + +This document contains the following information: + + +

    Overview of the Spark Client

    +

    +The Spark client is designed with the idea that most users find the different aspects of a chat client familiar and easy to use. +All components you see below are either accessible from the Workspace or ChatRoom object and can be manipulated based on your +needs. +

    + + +
    + + + + +

    Overview of the Spark API

    + +The Spark API provides a framework for adding extensions on top of the protocol and/or UI of the Spark client. For example, you could write your own message filter or add a button to a chat room and send files using the File Transfer API. The Spark API has the following characteristics: + + +

    Structure of a Plugin

    + +

    Plugins are shipped as compressed JAR (Java Archive) files. The files in a plugin archive are as follows:

    + +
    + Plugin Structure +
    myplugin.jar!/
    + |- plugin.xml     <- Plugin definition file
    + |- libs/          <- Contains all the class archives needed to run this plugin.
    +
    +
    +
    + +

    +The plugin.xml file specifies the main Plugin class. A sample +file might look like the following: +

    + +
    + Sample plugin.xml +
    +<?xml version="1.0" encoding="UTF-8"?>
    +
    +  <!-- Google Plugin Structure -->
    +      <plugin>
    +        <name>Google Desktop Plugin</name>
    +        <class>com.examples.plugins.GooglePlugin</class>
    +        <author>Derek DeMoro</author>
    +        <version>1.0</version>
    +        <description>Enables users to find files and emails relating to users using Google Desktop technology.</description>
    +        <email>ddman@jivesoftware.com</email>
    +      </plugin>
    +
    +
    + +

    Installing your Plugin

    + +

    +You only need to drop your newly created jar file into the plugins directory of your Spark client install. + +

    + +
    + Directory Structure +
    Spark/
    + |- plugins/     <- Put your Sparkplug jar file here
    + |- lib/       <- The main classes and libraries needed to run Live Assistant
    + |- resources/ <- Contains other supportive documents and libraries
    + |- docs/ <- Help Manuals and the JavaDoc to help you develop your own plugins.
    +
    +
    + + +

    Your plugin class must implement the +Plugin + +interface from the Spark Client API. The Plugin interface has +methods for initializing and shutting down the plugin. +

    + +

    Getting Started Writing Sparkplugs

    +

    +In order to build your own Sparkplugs, you will need to setup your project properly to have all items in your classpath. + +With the Sparkplug.zip or Sparplug.tar.gz you have just extracted, you should see the following contents: + +

    + Directory Structure +
    Sparkplugs/
    + |- build/     <- Simple structure to allow you to easily build your plugins using ANT.
    + |- images/       <-  Images used in this guide.
    + |- plugin_development_guide.html/ <- The complete development guide (this document).
    + |- api/  <- Contains the Sparkplug Javadocs.
    + |- spark/ <- The spark build structure you will need for classpath purposes
    +    |-bin    <- Contacts the startup.bat which starts up Spark for testing.
    +    |-lib    <- Contains all the archives needed to run Spark.
    +    |-logs   <- Where all logs are stored.
    +    |-plugins <- Location where all plugins should be deployed to.
    +    |-resources <- Contains native libraries for running OS specific operations.
    +
    +
    + +

    +To setup a project to run Spark within your IDE, you will need to the following: +

    + + +

    We have also provided a build structure to easily build and deploy your own plugins directly into your Spark test bed. To use, read the file in the builder/ directory.

    +

    + + +

    Spark How-To's

    + +

    +All code samples can be found in the examples.jar file located here. +

    + + + +

    How do I create a simple plugin?

    + +
      +
    1. Implement Plugin. +
    + +
    + + Simple Plugin +
    +package com.jivesoftware.spark.examples;
    +
    +import com.jivesoftware.spark.plugin.Plugin;
    +
    +/**
    + * Implements the Spark Plugin framework to display the different possibilities using
    + * Spark.
    + */
    +public class ExamplePlugin implements Plugin {
    +
    +    /**
    +     * Called after Spark is loaded to initialize the new plugin.
    +     */
    +    public void initialize() {
    +        System.out.println("Welcome To Spark");
    +
    +    }
    +
    +    /**
    +     * Called when Spark is shutting down to allow for persistence of information
    +     * or releasing of resources.
    +     */
    +    public void shutdown() {
    +
    +    }
    +
    +    /**
    +     * Return true if the Spark can shutdown on users request.
    +     * @return true if Spark can shutdown on users request.
    +     */
    +    public boolean canShutDown() {
    +        return true;
    +    }
    +
    +    /**
    +    * Is called when a user explicitly asked to uninstall this plugin.
    +    * The plugin owner is responsible to clean up any resources and
    +    * remove any components install in Spark.
    +    */
    +    public void uninstall(){
    +       // Remove all resources belonging to this plugin.
    +    }
    +}
    +
    +
    +
    + +

    How to add your own Tab to the Spark Workspace?

    + +
      +
    1. Implement Plugin. +
    2. Retrieve the Workspace which is the UI for Spark. +
    3. Retrieve the WorkspacePane which is the Tabbed Pane used by Spark. +
    4. Add your own tab. +
    + +
    + + Add a Tab to Spark +
    +public class ExamplePlugin implements Plugin {
    +.
    +.
    +.
    +    /**
    +     * Adds a tab to Spark
    +     */
    +    private void addTabToSpark(){
    +         // Get Workspace UI from SparkManager
    +        Workspace workspace = SparkManager.getWorkspace();
    +
    +        // Retrieve the Tabbed Pane from the WorkspaceUI.
    +        JTabbedPane tabbedPane = workspace.getWorkspacePane();
    +
    +        // Add own Tab.
    +        tabbedPane.addTab("My Plugin", new JButton("Hello"));
    +    }
    +.
    +.
    +.
    +}
    +
    +
    + +

    How do I add a context menu listener to the contact list?

    + +
      +
    1. Implement Plugin. +
    2. Retrieve the ContactList which is part of Spark's Workspace. +
    3. Add ContactListListener. +
    + +
    + + Add a ContextMenu Listener to ContactList +
    +  private void addContactListListener(){
    +         // Get Workspace UI from SparkManager
    +        Workspace workspace = SparkManager.getWorkspace();
    +
    +        // Retrieve the ContactList from the Workspace
    +        ContactList contactList = workspace.getContactList();
    +
    +        // Create an action to add to the Context Menu
    +        final Action sayHelloAction = new AbstractAction() {
    +            public void actionPerformed(ActionEvent actionEvent) {
    +                JOptionPane.showMessageDialog(SparkManager.getMainWindow(), "Welcome to Spark");
    +            }
    +        };
    +
    +        sayHelloAction.putValue(Action.NAME, "Say Hello To Me");
    +
    +
    +        // Add own Tab.
    +        contactList.addContextMenuListener(new ContextMenuListener() {
    +            public void poppingUp(Object object, JPopupMenu popup) {
    +                if(object instanceof ContactItem){
    +                    popup.add(sayHelloAction);
    +                }
    +            }
    +
    +            public void poppingDown(JPopupMenu popup) {
    +
    +            }
    +
    +            public boolean handleDefaultAction(MouseEvent e) {
    +                return false;
    +            }
    +        });
    +    }
    +
    +
    + + +

    How do I add my own ContextMenu Listener to a ChatRoom

    + +
      +
    1. Implement Plugin. +
    2. Add a ChatRoomListener to the ChatManager. +
    3. Get either the TranscriptWindow or ChatInputEditor from the ChatRoom. +
    4. Add a ContactMenuListener to the ChatArea. +
    + +
    + + Add a ContextMenuListener to a ChatRoom, TranscriptWindow or ChatInputEditor +
    +    private void addContactListenerToChatRoom() {
    +       // Retrieve a ChatManager from SparkManager
    +        ChatManager chatManager = SparkManager.getChatManager();
    +
    +        final ContextMenuListener listener = new ContextMenuListener() {
    +            public void poppingUp(Object object, JPopupMenu popup) {
    +                final TranscriptWindow chatWindow = (TranscriptWindow)object;
    +                Action clearAction = new AbstractAction() {
    +                    public void actionPerformed(ActionEvent actionEvent) {
    +                        try {
    +                            chatWindow.insert("My own text :)");
    +                        }
    +                        catch (BadLocationException e) {
    +                            e.printStackTrace();
    +                        }
    +                    }
    +                };
    +
    +                clearAction.putValue(Action.NAME, "Insert my own text");
    +                popup.add(clearAction);
    +            }
    +
    +            public void poppingDown(JPopupMenu popup) {
    +
    +            }
    +
    +            public boolean handleDefaultAction(MouseEvent e) {
    +                return false;
    +            }
    +        };
    +
    +        // Add a ChatRoomListener to the ChatManager to allow for notifications
    +        // when a room is being opened. Note: I will use a ChatRoomListenerAdapter for brevity.
    +        chatManager.addChatRoomListener(new ChatRoomListenerAdapter() {
    +            public void chatRoomOpened(ChatRoom room) {
    +                room.getTranscriptWindow().addContextMenuListener(listener);
    +            }
    +
    +            public void chatRoomLeft(ChatRoom room) {
    +                room.getTranscriptWindow().removeContextMenuListener(listener);
    +            }
    +        });
    +    }
    +
    +
    + +

    How do I add my own Menu to Spark?

    + +
      +
    1. Implement Plugin. +
    2. Retrieve the MainWindow from SparkManager. +
    3. Either create a new Menu or add a MenuItem to one of the pre-existing menus. +
    + +
    + + Add a Menu To Spark +
    +    /**
    +     * Adds a new menu and child menu item to Spark.
    +     */
    +    private void addMenuToSpark(){
    +        // Retrieve the MainWindow UI from Spark.
    +        final MainWindow mainWindow = SparkManager.getMainWindow();
    +
    +        // Create new Menu
    +        JMenu myPluginMenu = new JMenu("My Plugin Menu");
    +
    +        // Create Action to test Menu install.
    +        Action showMessage = new AbstractAction() {
    +            public void actionPerformed(ActionEvent actionEvent) {
    +                JOptionPane.showMessageDialog(mainWindow, "Yeah, It works.");
    +            }
    +        };
    +
    +        // Give the menu item a name.
    +        showMessage.putValue(Action.NAME, "Check if it works");
    +
    +        // Add to Menu
    +        myPluginMenu.add(showMessage);
    +
    +        // Add Menu To Spark
    +        mainWindow.getJMenuBar().add(myPluginMenu);
    +    }
    +
    +
    + +

    How do I add a button to a Chat Room?

    + +
      +
    1. Implement Plugin. +
    2. Add a ChatRoomListener to ChatManager. +
    3. When the room is opened, add your ChatRoomButton to the ToolBar of the ChatRoom. +
    + +
    + + Add a button to a Chat Room +
    +    /**
    +     * Adds a button to each Chat Room that is opened.
    +     */
    +    private void addChatRoomButton(){
    +        // Retrieve ChatManager from the SparkManager
    +        ChatManager chatManager = SparkManager.getChatManager();
    +
    +        // Create a new ChatRoomButton.
    +        final ChatRoomButton button = new ChatRoomButton("Push Me");
    +
    +
    +        // Add to a new ChatRoom when the ChatRoom opens.
    +        chatManager.addChatRoomListener(new ChatRoomListenerAdapter() {
    +            public void chatRoomOpened(ChatRoom room) {
    +                room.getToolBar().addChatRoomButton(button);
    +            }
    +
    +            public void chatRoomLeft(ChatRoom room) {
    +                room.getToolBar().removeChatRoomButton(button);
    +            }
    +        });
    +    }
    +
    +
    + +

    How do I add my own searching feature in Spark like the User Search or Google Search in Firefox?

    + +
      +
    1. Implement Plugin. +
    2. Create a searchable object by implementing the Searchable interface. +
    3. Add the Searchable implementation to the SearchManager. +
    + +
    + + Add a search feature to Spark like User Search or Google Search in Firefox +
    +    /**
    +     * Called after Spark is loaded to initialize the new plugin.
    +     */
    +    public void initialize() {
    +        // Register new Searchable object "SearchMe" with the SearchManager.
    +        SearchManager searchManager = SparkManager.getSearchManager();
    +        searchManager.addSearchService(new SearchMe());
    +    } 
    +
    +See the SearchMe code below. +
     
    +    
    +package com.jivesoftware.spark.examples;
    +
    +import com.jivesoftware.spark.search.Searchable;
    +import com.jivesoftware.spark.SparkManager;
    +import com.jivesoftware.resource.LaRes;
    +
    +import javax.swing.Icon;
    +import javax.swing.JOptionPane;
    +
    +/**
    + * A simple example of how to integrate ones own search into Spark.
    + */
    +public class SearchMe implements Searchable {
    +
    +    /**
    +     * The icon to show in the search box.
    +     * @return the icon.
    +     */
    +    public Icon getIcon() {
    +        return LaRes.getImageIcon(LaRes.SMALL_AGENT_IMAGE);
    +    }
    +
    +    /**
    +     * Returns the name of this search object that is displayed in the drop down box.
    +     * @return the name.
    +     */
    +    public String getName() {
    +        return "Searches Nothing Really";
    +    }
    +
    +    /**
    +     * Returns the text that should be displayed in grey when this searchable object
    +     * is initially selected.
    +     * @return the text.
    +     */
    +    public String getDefaultText() {
    +        return "Click to search me.";
    +    }
    +
    +    /**
    +     * Returns the text to display in the tooltip.
    +     * @return the tooltip text.
    +     */
    +    public String getToolTip() {
    +        return "Shows an example of integrating ones own search into Spark.";
    +    }
    +
    +    /**
    +     * Is called when a user hits "Enter" key.
    +     * @param query the query the user is searching for.
    +     */
    +    public void search(String query) {
    +        JOptionPane.showMessageDialog(SparkManager.getMainWindow(), "Nothing Found :(");
    +    }
    +}
    +
    +
    + +

    How can I intercept a File Transfer request?

    + +
      +
    1. Implement Plugin. +
    2. Implement TransferListener. +
    3. Register your TransferListener. +
    + +
    + + Intercept File Transfer Requests +
    +    /**
    +     * Listen for incoming transfer requests and either handle them yourself, or pass them
    +     * off to be handled by the next listener. If no one handles it, then Spark will handle it.
    +     */
    +    private void addTransferListener(){
    +
    +        SparkTransferManager transferManager = SparkManager.getTransferManager();
    +
    +        transferManager.addTransferListener(new TransferListener() {
    +            public boolean handleTransfer(FileTransferRequest request) {
    +                // If I wanted to handle it, take the request, accept it and get the inputstream.
    +                
    +                // Otherwise, return false.
    +                return false;
    +            }
    +        });
    +    }
    +
    +
    + +

    How can I send a file to another user?

    + +
      +
    1. Implement Plugin. +
    2. Get the full jid of the user via the UserManager. +
    3. Get the SparkTransferManager and send the file. +
    + +
    + + Send a file to another user +
    +    /**
    +     * Sends a file to a user in your ContactList.
    +     */
    +    private void sendFile(){
    +        // Retrieve SparkTransferManager from the SparkManager.
    +        SparkTransferManager transferManager = SparkManager.getTransferManager();
    +
    +        // In order to send a file to a person, you will need to know their full Jabber
    +        // ID.
    +
    +        // Retrieve the Jabber ID for a user via the UserManager. This can
    +        // return null if the user is not in the ContactList or is offline.
    +        UserManager userManager = SparkManager.getUserManager();
    +        String jid = userManager.getJIDFromNickname("Matt");
    +        if(jid != null){
    +            transferManager.sendFile(new File("MyFile.txt"), jid);
    +        }
    +    }
    +
    +
    + +

    How can I control the UI and event handling of a ContactItem?

    + +
      +
    1. Implement Plugin. +
    2. Get the ContactList. +
    3. Get a ContactItem(s) based on a user's jid. +
    4. Add your own ContactItemHandler to the ContactItem. +
    + +
    + + Control the UI and event handling of a ContactItem +
    +    /**
    +     * Controls the UI of a ContactItem.
    +     */
    +    private void handleUIAndEventsOfContactItem(){
    +
    +        ContactList contactList = SparkManager.getWorkspace().getContactList();
    +
    +        ContactItem item = contactList.getContactItemByJID("paul@jivesoftware.com/spark");
    +
    +        ContactItemHandler handler = new ContactItemHandler() {
    +            /**
    +             * Called when this users presence changes. You are responsible for changing the
    +             * icon (or not) of this contact item.
    +             * @param presence the users new presence.
    +             */
    +            public void handlePresence(Presence presence) {
    +
    +            }
    +
    +            /**
    +             * Is called when a user double-clicks the item.
    +             * @return true if you are handling the event.
    +             */
    +            public boolean handleDoubleClick() {
    +                return false;
    +            }
    +        };
    +
    +        item.setHandler(handler);
    +    }
    +
    +
    + + +

    How can I be notified when the Spark user changes their presence?

    + +
      +
    1. Implement Plugin. +
    2. Get the SessionManager from SparkManager. +
    3. Add your own PresenceListener to SessionManager. +
    + +
    + + Receive notification when the Spark user changes their presence +
    +    /**
    +     * Allows a plugin to be notified when the Spark users changes their
    +     * presence.
    +     */
    +    private void addPersonalPresenceListener(){
    +        SessionManager sessionManager = SparkManager.getSessionManager();
    +
    +        sessionManager.addPresenceListener(new PresenceListener() {
    +
    +            /**
    +             * Spark user changed their presence.
    +             * @param presence the new presence.
    +             */
    +            public void presenceChanged(Presence presence) {
    +                
    +            }
    +        });
    +    }
    +
    +
    + +

    How can I add a message filter?

    + +
      +
    1. Implement Plugin. +
    2. Get the ChatManager from SparkManager. +
    3. Create an instance of Message Filter. +
    4. Register with the ChatManager. +
    + +
    + + Adding own Message Filter +
    +    /**
    +     * Installs a new MessageFilter.
    +     */
    +    private void installMessageFilter() {
    +        // Retrieve the ChatManager from SparkManager
    +        ChatManager chatManager = SparkManager.getChatManager();
    +
    +        MessageFilter messageFilter = new MessageFilter() {
    +            public void filter(Message message) {
    +                String currentBody = message.getBody();
    +                currentBody = currentBody.replaceAll("bad words", "good words");
    +                message.setBody(currentBody);
    +            }
    +        };
    +
    +        chatManager.addMessageFilter(messageFilter);
    +
    +        // Just remember to remove your filter if need be.
    +    }
    +
    +
    + +

    How can I create a person-to-person Chat Room

    + +
      +
    1. Implement Plugin. +
    2. Get the ChatManager from SparkManager. +
    3. Create a new ChatRoom using the ChatManager. +
    4. Optionally make it the active ChatRoom using the ChatContainer. +
    + +
    + + Creating Person-to-Person Chat Room +
    +    /**
    +     * Creates a person to person Chat Room and makes it the active chat.
    +     */
    +    private void createPersonToPersonChatRoom(){
    +        
    +        // Get the ChatManager from Sparkmanager
    +        ChatManager chatManager = SparkManager.getChatManager();
    +        
    +        // Create the room.
    +        ChatRoom chatRoom = chatManager.createChatRoom("don@jivesoftware.com", "Don The Man", "The Chat Title");
    +        
    +        // If you wish to make this the active chat room.
    +        
    +        // Get the ChatContainer (This is the container for all Chat Rooms)
    +        ChatContainer chatContainer = chatManager.getChatContainer();
    +        
    +        // Ask the ChatContainer to make this chat the active chat.
    +        chatContainer.activateChatRoom(chatRoom);
    +    }
    +
    +
    + +

    How can I create a public Conference room?

    + +
      +
    1. Implement Plugin. +
    2. Get the ChatManager from SparkManager. +
    3. Create a new conference ChatRoom using the ChatManager. +
    4. Optionally make it the active ChatRoom using the ChatContainer. +
    + +
    + + Creating a Conference Room +
    +    /**
    +     * Creates a person to person Chat Room and makes it the active chat.
    +     */
    +    private void createConferenceRoom() {
    +
    +        // Get the ChatManager from Sparkmanager
    +        ChatManager chatManager = SparkManager.getChatManager();
    +
    +        Collection serviceNames = null;
    +
    +        // Get the service name you wish to use.
    +        try {
    +            serviceNames = MultiUserChat.getServiceNames(SparkManager.getConnection());
    +        }
    +        catch (XMPPException e) {
    +            e.printStackTrace();
    +        }
    +
    +        // Create the room.
    +        ChatRoom chatRoom = chatManager.createConferenceRoom("BusinessChat", (String)serviceNames.toArray()[0]);
    +
    +        // If you wish to make this the active chat room.
    +
    +        // Get the ChatContainer (This is the container for all Chat Rooms)
    +        ChatContainer chatContainer = chatManager.getChatContainer();
    +
    +        // Ask the ChatContainer to make this chat the active chat.
    +        chatContainer.activateChatRoom(chatRoom);
    +    }
    +}
    +
    +
    + + +

    How can I add my own Preferences?

    + +
      +
    1. Implement Plugin. +
    2. Create a class that implements Preference. +
    3. Create a UI to associate with the Preference. +
    4. Register your new Preference with the PreferenceManager. +
    + +
    + + Creating a Preference + +
    + + + +

    How to show an alert, like when a new message comes in?

    + +
    How to show an alert, like when a new message comes in? + +
    +    // Get the ChatContainer from the ChatManager.
    +    
    +     ChatContainer chatContainer = ChatManager.getChatContainer();
    +     
    +     // Get the room you wish to be notified.
    +     ChatRoom chatRoom = chatContainer.getActiveChatRoom();
    +     chatContainer.startFlashing(chatRoom);
    +
    + +
    + + + + + + + + + diff --git a/documentation/style.css b/documentation/style.css new file mode 100644 index 00000000..473fab58 --- /dev/null +++ b/documentation/style.css @@ -0,0 +1,124 @@ +BODY { + font-size : 100%; + background-color : #fff; +} +BODY, TD, TH { + font-family : arial, helvetica, sans-serif; + font-size : 10pt; +} +PRE, TT, CODE { + font-family : courier new, monospaced; + font-size : 9pt; +} +A:hover { + text-decoration : none; +} +LI { + padding-bottom : 4px; +} +H1 { + font-family : tahoma, arial, helvetica, sans-serif; + font-size : 1.4em; + font-weight: bold; + border-bottom : 1px #ccc solid; + padding-bottom : 2px; + display : inline; + padding-left : 5px; +} +H2 { + font-size : 1.2em; + font-weight : bold; +} +H3 { + font-size : 1.0em; + font-weight : bold; +} +TT { + font-family : courier new; + font-weight : bold; + color : #060; +} +FIELDSET PRE { + padding : 1em; + margin : 0px; +} +FIELDSET { + margin-left : 2em; + margin-right : 2em; + border : 1px #ccc solid; + -moz-border-radius : 5px; +} +.comment { + color : #666; + font-style : italic; +} + +.subheader { + font-weight : bold; +} +.footer { + font-size : 0.8em; + color : #999; + text-align : center; + width : 100%; + border-top : 1px #ccc solid; + padding-top : 2px; +} +.code { + border : 1px #ccc solid; + padding : 0em 1.0em 0em 1.0em; + margin : 4px 0px 4px 0px; +} +.nav, .nav A { + font-family : verdana; + font-size : 0.85em; + color : #600; + text-decoration : none; + font-weight : bold; +} +.note { + font-family : verdana; + font-size : 0.85em; + color : #600; + text-decoration : none; + font-weight : bold; +} +.nav { + width : 100%; + border-bottom : 1px #ccc solid; + padding : 3px 3px 5px 1px; +} +.nav A:hover { + text-decoration : underline; +}.question { + font-weight: 600; +} +.answer { + font-weight: 300; +} +.toc { + right: 5px; +} +TABLE.dbtable { + border : 1px #ccc solid; + width : 600px; +} +TR, TH { + border-bottom : 1px #ccc solid; +} +TH, TD { + padding-right : 15px; +} +TH { + text-align : left; + white-space : nowrap; + background-color : #eee; +} +.primary-key { + background-color : #ffc; +} +#bannerbox .spring { + background-image : url(images/banner-spring.gif); + background-position : top; + background-repeat : repeat-x; +} diff --git a/src/java/org/jivesoftware/AccountCreationWizard.java b/src/java/org/jivesoftware/AccountCreationWizard.java new file mode 100644 index 00000000..d57f5d2f --- /dev/null +++ b/src/java/org/jivesoftware/AccountCreationWizard.java @@ -0,0 +1,290 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware; + +import org.jivesoftware.smack.AccountManager; +import org.jivesoftware.smack.SSLXMPPConnection; +import org.jivesoftware.smack.XMPPConnection; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.spark.component.TitlePanel; +import org.jivesoftware.spark.util.ModelUtil; +import org.jivesoftware.spark.util.ResourceUtils; +import org.jivesoftware.spark.util.SwingWorker; +import org.jivesoftware.sparkimpl.settings.local.LocalPreferences; +import org.jivesoftware.sparkimpl.settings.local.SettingsManager; + +import javax.swing.JButton; +import javax.swing.JDialog; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JPasswordField; +import javax.swing.JProgressBar; +import javax.swing.JTextField; + +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +public class AccountCreationWizard extends JPanel { + private JLabel usernameLabel = new JLabel(); + private JTextField usernameField = new JTextField(); + + private JLabel passwordLabel = new JLabel(); + private JPasswordField passwordField = new JPasswordField(); + + private JLabel confirmPasswordLabel = new JLabel(); + private JPasswordField confirmPasswordField = new JPasswordField(); + + private JLabel serverLabel = new JLabel(); + private JTextField serverField = new JTextField(); + + private JButton createAccountButton = new JButton(); + private JButton closeButton = new JButton(); + + private JDialog dialog; + + private boolean registered; + private XMPPConnection con = null; + private JProgressBar bar; + + + public AccountCreationWizard() { + // Associate Mnemonics + ResourceUtils.resLabel(usernameLabel, usernameField, "&Username:"); + ResourceUtils.resLabel(passwordLabel, passwordField, "&Password:"); + ResourceUtils.resLabel(confirmPasswordLabel, confirmPasswordField, "&Confirm Password:"); + ResourceUtils.resLabel(serverLabel, serverField, "&Server:"); + ResourceUtils.resButton(createAccountButton, "&Create Account"); + + setLayout(new GridBagLayout()); + + // Add component to UI + add(usernameLabel, new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + add(usernameField, new GridBagConstraints(1, 0, 3, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 150, 0)); + + add(passwordLabel, new GridBagConstraints(0, 1, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + add(passwordField, new GridBagConstraints(1, 1, 3, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); + + add(confirmPasswordLabel, new GridBagConstraints(0, 2, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + add(confirmPasswordField, new GridBagConstraints(1, 2, 3, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); + + add(serverLabel, new GridBagConstraints(0, 3, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + add(serverField, new GridBagConstraints(1, 3, 3, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); + + bar = new JProgressBar(); + + + add(bar, new GridBagConstraints(1, 4, 3, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); + bar.setVisible(false); + add(createAccountButton, new GridBagConstraints(2, 5, 1, 1, 1.0, 0.0, GridBagConstraints.EAST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + + + ResourceUtils.resButton(closeButton, "&Close"); + add(closeButton, new GridBagConstraints(3, 5, 1, 1, 0.0, 0.0, GridBagConstraints.EAST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + + + createAccountButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent actionEvent) { + createAccount(); + } + }); + + closeButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent actionEvent) { + dialog.dispose(); + } + }); + } + + public String getUsername() { + return usernameField.getText(); + } + + public String getPassword() { + return new String(passwordField.getPassword()); + } + + public String getConfirmPassword() { + return new String(confirmPasswordField.getPassword()); + } + + public String getServer() { + return serverField.getText(); + } + + public boolean passwordsMatch() { + return getPassword().equals(getConfirmPassword()); + } + + public void createAccount() { + boolean errors = false; + String errorMessage = ""; + + if (!ModelUtil.hasLength(getUsername())) { + errors = true; + usernameField.requestFocus(); + errorMessage = "Please specify a username for the account."; + } + else if (!ModelUtil.hasLength(getPassword())) { + errors = true; + errorMessage = "Please specify a password for this account."; + } + else if (!ModelUtil.hasLength(getConfirmPassword())) { + errors = true; + errorMessage = "Please specify a confirmation password."; + } + else if (!ModelUtil.hasLength(getServer())) { + errors = true; + errorMessage = "Please specify the server to create the account on."; + } + else if (!passwordsMatch()) { + errors = true; + errorMessage = "The passwords do not match. Please confirm passwords."; + } + + if (errors) { + JOptionPane.showMessageDialog(this, errorMessage, "Account Creation Problem", JOptionPane.ERROR_MESSAGE); + return; + } + + final Component ui = this; + bar.setIndeterminate(true); + bar.setStringPainted(true); + bar.setString("Registering with " + getServer() + ". Please wait..."); + bar.setVisible(true); + final SwingWorker worker = new SwingWorker() { + int errorCode; + + + public Object construct() { + try { + createAccountButton.setEnabled(false); + con = getConnection(); + } + catch (XMPPException e) { + return e; + } + try { + final AccountManager accountManager = new AccountManager(con); + accountManager.createAccount(getUsername(), getPassword()); + } + catch (XMPPException e) { + errorCode = e.getXMPPError().getCode(); + } + return "ok"; + } + + public void finished() { + bar.setVisible(false); + if (con == null) { + if (ui.isShowing()) { + createAccountButton.setEnabled(true); + JOptionPane.showMessageDialog(ui, "Unable to connect to " + getServer() + ".", "Account Creation Problem", JOptionPane.ERROR_MESSAGE); + createAccountButton.setEnabled(true); + } + return; + } + + if (errorCode == 0) { + accountCreationSuccessful(); + } + else { + accountCreationFailed(errorCode); + } + } + }; + + worker.start(); + } + + private void accountCreationFailed(int errorCode) { + String message = "Unable to create account."; + if (errorCode == 409) { + message = "Account already exists. Please specify different username."; + usernameField.setText(""); + usernameField.requestFocus(); + } + JOptionPane.showMessageDialog(this, message, "Account Creation Problem", JOptionPane.ERROR_MESSAGE); + createAccountButton.setEnabled(true); + } + + private void accountCreationSuccessful() { + registered = true; + JOptionPane.showMessageDialog(this, "New Account has been created.", "Account Created", JOptionPane.INFORMATION_MESSAGE); + dialog.dispose(); + } + + public void invoke(JFrame parent) { + dialog = new JDialog(parent, "Create New Account", true); + + TitlePanel titlePanel = new TitlePanel("Account Registration", "Register a new account to chat", null, true); + dialog.getContentPane().setLayout(new BorderLayout()); + dialog.getContentPane().add(titlePanel, BorderLayout.NORTH); + dialog.getContentPane().add(this, BorderLayout.CENTER); + dialog.pack(); + dialog.setSize(400, 300); + dialog.setLocationRelativeTo(parent); + dialog.setVisible(true); + } + + private XMPPConnection getConnection() throws XMPPException { + LocalPreferences localPref = SettingsManager.getLocalPreferences(); + XMPPConnection con = null; + + // Get connection + + int port = localPref.getXmppPort(); + + String serverName = getServer(); + + int checkForPort = serverName.indexOf(":"); + if (checkForPort != -1) { + String portString = serverName.substring(checkForPort + 1); + if (ModelUtil.hasLength(portString)) { + // Set new port. + port = Integer.valueOf(portString).intValue(); + } + } + + boolean useSSL = localPref.isSSL(); + boolean hostPortConfigured = localPref.isHostAndPortConfigured(); + + if (useSSL) { + if (!hostPortConfigured) { + con = new SSLXMPPConnection(serverName); + } + else { + con = new SSLXMPPConnection(localPref.getXmppHost(), port, serverName); + } + } + else { + if (!hostPortConfigured) { + con = new XMPPConnection(serverName); + } + else { + con = new XMPPConnection(localPref.getXmppHost(), port, serverName); + } + } + return con; + + } + + public boolean isRegistered() { + return registered; + } +} + diff --git a/src/java/org/jivesoftware/Installer.java b/src/java/org/jivesoftware/Installer.java new file mode 100644 index 00000000..ea30b72b --- /dev/null +++ b/src/java/org/jivesoftware/Installer.java @@ -0,0 +1,69 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware; + +import com.install4j.api.InstallAction; +import com.install4j.api.InstallerWizardContext; +import com.install4j.api.ProgressInterface; +import com.install4j.api.UserCanceledException; +import com.install4j.api.windows.WinRegistry; + +import java.io.File; + +public class Installer extends InstallAction { + + private InstallerWizardContext context; + + public int getPercentOfTotalInstallation() { + return 0; + } + + + public boolean performAction(InstallerWizardContext installerWizardContext, ProgressInterface progressInterface) throws UserCanceledException { + context = installerWizardContext; + + final String osName = System.getProperty("os.name"); + if (!osName.startsWith("Windows")) { + return true; + } + + addStartup(installerWizardContext.getInstallationDirectory()); + return true; + } + + public void addStartup(File dir) { + File jivec = new File(dir, "Spark.exe"); + String path = jivec.getAbsolutePath(); + WinRegistry.setValue(WinRegistry.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Run", "Spark", path); + + addURIMapping(dir); + } + + private void addURIMapping(File dir) { + File jivec = new File(dir, "Spark.exe"); + String path = jivec.getAbsolutePath(); + + boolean exists = WinRegistry.keyExists(WinRegistry.HKEY_CLASSES_ROOT, "xmpp"); + if (exists) { + } + // JOptionPane.showConfirmDialog(null, "Another application is currently registered to handle XMPP instant messaging. Make Spark the default XMPP instant messaging client?", "Confirmation", } + WinRegistry.deleteKey(WinRegistry.HKEY_CLASSES_ROOT, "xmpp", true); + + WinRegistry.createKey(WinRegistry.HKEY_CLASSES_ROOT, "xmpp"); + WinRegistry.setValue(WinRegistry.HKEY_CLASSES_ROOT, "xmpp", "", "URL:XMPP Address"); + WinRegistry.setValue(WinRegistry.HKEY_CLASSES_ROOT, "xmpp", "URL Protocol", ""); + + WinRegistry.createKey(WinRegistry.HKEY_CLASSES_ROOT, "xmpp\\shell\\open\\command"); + WinRegistry.setValue(WinRegistry.HKEY_CLASSES_ROOT, "xmpp\\shell\\open\\command", "", path + " %1"); + } + + +} diff --git a/src/java/org/jivesoftware/LoginDialog.java b/src/java/org/jivesoftware/LoginDialog.java new file mode 100644 index 00000000..74793b45 --- /dev/null +++ b/src/java/org/jivesoftware/LoginDialog.java @@ -0,0 +1,818 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware; + +import org.jivesoftware.resource.Default; +import org.jivesoftware.resource.SparkRes; +import org.jivesoftware.smack.Roster; +import org.jivesoftware.smack.SSLXMPPConnection; +import org.jivesoftware.smack.SmackConfiguration; +import org.jivesoftware.smack.XMPPConnection; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.packet.XMPPError; +import org.jivesoftware.spark.SessionManager; +import org.jivesoftware.spark.SparkManager; +import org.jivesoftware.spark.Workspace; +import org.jivesoftware.spark.component.RolloverButton; +import org.jivesoftware.spark.util.Encryptor; +import org.jivesoftware.spark.util.GraphicUtils; +import org.jivesoftware.spark.util.ModelUtil; +import org.jivesoftware.spark.util.ResourceUtils; +import org.jivesoftware.spark.util.SwingWorker; +import org.jivesoftware.spark.util.log.Log; +import org.jivesoftware.sparkimpl.plugin.layout.LayoutSettings; +import org.jivesoftware.sparkimpl.plugin.layout.LayoutSettingsManager; +import org.jivesoftware.sparkimpl.settings.local.LocalPreferences; +import org.jivesoftware.sparkimpl.settings.local.SettingsManager; + +import javax.swing.BorderFactory; +import javax.swing.ImageIcon; +import javax.swing.JCheckBox; +import javax.swing.JComponent; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JPasswordField; +import javax.swing.JTextField; +import javax.swing.text.JTextComponent; + +import java.awt.BorderLayout; +import java.awt.CardLayout; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Image; +import java.awt.Insets; +import java.awt.LayoutManager; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.awt.geom.AffineTransform; + +/** + * Dialog to log in a user into the Spark Server. The LoginDialog is used only + * for login in registered users into the Spark Server. + */ +public final class LoginDialog { + private JFrame loginDialog; + private static final String BUTTON_PANEL = "buttonpanel"; // NOTRANS + private static final String PROGRESS_BAR = "progressbar"; // NOTRANS + private LocalPreferences localPref; + // private TrayIcon trayIcon; + // private SystemTray tray; + + /** + * Empty Constructor + */ + public LoginDialog() { + localPref = SettingsManager.getLocalPreferences(); + + // Remove all Spark Tray issues on startup. This will increase timeouts of spark. + + /* + if (Spark.isWindows()) { + try { + tray = SystemTray.getDefaultSystemTray(); + trayIcon = new TrayIcon(LaRes.getImageIcon(LaRes.MAIN_IMAGE)); + trayIcon.setToolTip(Default.getString(Default.APPLICATION_NAME)); + trayIcon.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + if (loginDialog != null) { + if (loginDialog.isVisible()) { + loginDialog.setVisible(false); + } + else { + loginDialog.setVisible(true); + } + } + } + }); + + if (localPref.isStartedHidden() || localPref.isAutoLogin()) { + tray.addTrayIcon(trayIcon); + } + } + catch (Exception e) { + Log.error(e); + } + } + */ + } + + /** + * Invokes the LoginDialog to be visible. + * + * @param parentFrame the parentFrame of the Login Dialog. This is used + * for correct parenting. + */ + public void invoke(JFrame parentFrame) { + // Before creating any connections. Update proxy if needed. + updateProxyConfig(); + + LoginPanel loginPanel = new LoginPanel(); + + // Construct Dialog + loginDialog = new JFrame(Default.getString(Default.APPLICATION_NAME)); + loginDialog.setIconImage(SparkRes.getImageIcon(SparkRes.MAIN_IMAGE).getImage()); + + final JPanel mainPanel = new GrayBackgroundPanel(); + final GridBagLayout mainLayout = new GridBagLayout(); + mainPanel.setLayout(mainLayout); + + final ImagePanel imagePanel = new ImagePanel(); + + mainPanel.add(imagePanel, + new GridBagConstraints(0, 0, 4, 1, + 1.0, 1.0, GridBagConstraints.NORTHWEST, GridBagConstraints.BOTH, + new Insets(0, 0, 0, 0), 0, 0)); + + final String showPoweredBy = Default.getString(Default.SHOW_POWERED_BY); + if (ModelUtil.hasLength(showPoweredBy) && "true".equals(showPoweredBy)) { + // Handle PoweredBy for custom clients. + final JLabel poweredBy = new JLabel(SparkRes.getImageIcon(SparkRes.POWERED_BY_IMAGE)); + mainPanel.add(poweredBy, + new GridBagConstraints(0, 1, 4, 1, + 1.0, 0.0, GridBagConstraints.NORTHEAST, GridBagConstraints.HORIZONTAL, + new Insets(0, 0, 2, 0), 0, 0)); + + } + imagePanel.setBorder(BorderFactory.createMatteBorder(0, 0, 1, 0, Color.lightGray)); + + loginPanel.setOpaque(false); + mainPanel.add(loginPanel, + new GridBagConstraints(0, 2, 2, 1, + 1.0, 1.0, GridBagConstraints.NORTHWEST, GridBagConstraints.HORIZONTAL, + new Insets(0, 0, 0, 0), 0, 0)); + + loginDialog.setContentPane(mainPanel); + loginDialog.setLocationRelativeTo(parentFrame); + + loginDialog.setResizable(false); + loginDialog.pack(); + + // Center dialog on screen + GraphicUtils.centerWindowOnScreen(loginDialog); + + // Show dialog + loginDialog.addWindowListener(new WindowAdapter() { + public void windowClosing(WindowEvent e) { + quitLogin(); + } + }); + if (loginPanel.getUsername().trim().length() > 0) { + loginPanel.getPasswordField().requestFocus(); + } + + if (!localPref.isStartedHidden() || !localPref.isAutoLogin()) { + // Make dialog top most. + loginDialog.setVisible(true); + } + } + + /** + * Define Login Panel implementation. + */ + private final class LoginPanel extends JPanel implements KeyListener, ActionListener, FocusListener { + private final JLabel usernameLabel = new JLabel(); + private final JTextField usernameField = new JTextField(); + + private final JLabel passwordLabel = new JLabel(); + private final JPasswordField passwordField = new JPasswordField(); + + private final JLabel serverLabel = new JLabel(); + private final JTextField serverField = new JTextField(); + + private final JCheckBox savePasswordBox = new JCheckBox(); + private final JCheckBox autoLoginBox = new JCheckBox(); + private final RolloverButton loginButton = new RolloverButton(); + private final RolloverButton connectionButton = new RolloverButton(); + private final RolloverButton quitButton = new RolloverButton(); + + private final RolloverButton createAccountButton = new RolloverButton(); + + private final JLabel progressBar = new JLabel(); + + // Panel used to hold buttons + private final CardLayout cardLayout = new CardLayout(0, 5); + final JPanel cardPanel = new JPanel(cardLayout); + + final JPanel buttonPanel = new JPanel(new GridBagLayout()); + private final GridBagLayout GRIDBAGLAYOUT = new GridBagLayout(); + private XMPPConnection connection = null; + + + LoginPanel() { + //setBorder(BorderFactory.createTitledBorder("Sign In Now")); + ResourceUtils.resButton(savePasswordBox, "Save &Password"); + ResourceUtils.resButton(autoLoginBox, "&Auto Login"); + ResourceUtils.resLabel(serverLabel, serverField, "&Server:"); + ResourceUtils.resButton(createAccountButton, "&Accounts"); + + savePasswordBox.setOpaque(false); + autoLoginBox.setOpaque(false); + setLayout(GRIDBAGLAYOUT); + + + add(usernameLabel, + new GridBagConstraints(0, 1, 1, 1, 0.0, 0.0, + GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(15, 5, 5, 5), 0, 0)); + add(usernameField, + new GridBagConstraints(1, 1, 2, 1, + 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, + new Insets(15, 5, 5, 5), 0, 0)); + + add(passwordField, + new GridBagConstraints(1, 2, 2, 1, + 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, + new Insets(0, 5, 5, 5), 0, 0)); + add(passwordLabel, + new GridBagConstraints(0, 2, 1, 1, 0.0, 0.0, + GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, 5, 5, 5), 5, 0)); + + // Add Server Field Properties + add(serverField, + new GridBagConstraints(1, 4, 2, 1, + 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, + new Insets(0, 5, 5, 5), 0, 0)); + add(serverLabel, + new GridBagConstraints(0, 4, 1, 1, 0.0, 0.0, + GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, 5, 5, 5), 5, 0)); + + + add(savePasswordBox, + new GridBagConstraints(0, 5, 2, 1, 1.0, 0.0, + GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(0, 5, 5, 5), 0, 0)); + add(autoLoginBox, + new GridBagConstraints(0, 6, 2, 1, 1.0, 0.0, + GridBagConstraints.EAST, GridBagConstraints.HORIZONTAL, new Insets(0, 5, 5, 5), 0, 0)); + + // Add button but disable the login button initially + savePasswordBox.addActionListener(this); + autoLoginBox.addActionListener(this); + + buttonPanel.add(quitButton, + new GridBagConstraints(0, 0, 1, 1, 1.0, 0.0, + GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 0, 5, 0), 0, 0)); + + if (!"true".equals(Default.getString(Default.ACCOUNT_DISABLED))) { + buttonPanel.add(createAccountButton, + new GridBagConstraints(1, 0, 1, 1, 0.0, 0.0, + GridBagConstraints.EAST, GridBagConstraints.HORIZONTAL, new Insets(5, 0, 5, 0), 0, 0)); + } + buttonPanel.add(connectionButton, + new GridBagConstraints(2, 0, 1, 1, 0.0, 0.0, + GridBagConstraints.EAST, GridBagConstraints.HORIZONTAL, new Insets(5, 0, 5, 0), 0, 0)); + buttonPanel.add(loginButton, + new GridBagConstraints(3, 0, 4, 1, 0.0, 0.0, + GridBagConstraints.EAST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 0), 0, 0)); + + cardPanel.add(buttonPanel, BUTTON_PANEL); + + cardPanel.setOpaque(false); + buttonPanel.setOpaque(false); + + progressBar.setHorizontalAlignment(JLabel.CENTER); + cardPanel.add(progressBar, PROGRESS_BAR); + + add(cardPanel, + new GridBagConstraints(0, 7, 4, 1, + 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, + new Insets(5, 5, 5, 5), 0, 0)); + loginButton.setEnabled(false); + + // Add KeyListener + usernameField.addKeyListener(this); + passwordField.addKeyListener(this); + serverField.addKeyListener(this); + + passwordField.addFocusListener(this); + usernameField.addFocusListener(this); + serverField.addFocusListener(this); + + // Add ActionListener + quitButton.addActionListener(this); + loginButton.addActionListener(this); + connectionButton.addActionListener(this); + + // Make same size + GraphicUtils.makeSameSize(new JComponent[]{usernameField, passwordField}); + + // Set progress bar description + progressBar.setText(SparkRes.getString(SparkRes.LOGIN_DIALOG_AUTHENTICATING)); + //progressBar.setStringPainted(true); + + // Set Resources + ResourceUtils.resLabel(usernameLabel, usernameField, SparkRes.getString(SparkRes.LOGIN_DIALOG_USERNAME)); + ResourceUtils.resLabel(passwordLabel, passwordField, SparkRes.getString(SparkRes.LOGIN_DIALOG_PASSWORD)); + ResourceUtils.resButton(quitButton, SparkRes.getString(SparkRes.LOGIN_DIALOG_QUIT)); + ResourceUtils.resButton(loginButton, SparkRes.getString(SparkRes.LOGIN_DIALOG_LOGIN)); + ResourceUtils.resButton(connectionButton, "Ad&vanced"); + + // Load previous instances + String userProp = localPref.getUsername(); + String serverProp = localPref.getServer(); + + if (userProp != null && serverProp != null) { + usernameField.setText(userProp); + serverField.setText(serverProp); + } + + // Check Settings + if (localPref.isSavePassword()) { + String encryptedPassword = localPref.getPassword(); + if (encryptedPassword != null) { + String password = Encryptor.decrypt(encryptedPassword); + passwordField.setText(password); + } + savePasswordBox.setSelected(true); + loginButton.setEnabled(true); + } + autoLoginBox.setSelected(localPref.isAutoLogin()); + if (autoLoginBox.isSelected()) { + savePasswordBox.setEnabled(false); + autoLoginBox.setEnabled(false); + validateLogin(); + return; + } + + // Handle arguments + String username = Spark.getArgumentValue("username"); + String password = Spark.getArgumentValue("password"); + String server = Spark.getArgumentValue("server"); + + if (username != null) { + usernameField.setText(username); + } + + if (password != null) { + passwordField.setText(password); + } + + if (server != null) { + serverField.setText(server); + } + + if (username != null && server != null && password != null) { + savePasswordBox.setEnabled(false); + autoLoginBox.setEnabled(false); + validateLogin(); + } + + createAccountButton.addActionListener(this); + + final String lockedDownURL = Default.getString(Default.HOST_NAME); + if (ModelUtil.hasLength(lockedDownURL)) { + serverField.setEnabled(false); + serverField.setText(lockedDownURL); + } + } + + private String getUsername() { + return usernameField.getText().trim(); + } + + private String getPassword() { + return new String(passwordField.getPassword()).trim(); + } + + private String getServerName() { + return serverField.getText().trim(); + } + + /** + * ActionListener implementation. + * + * @param e the ActionEvent + */ + public void actionPerformed(ActionEvent e) { + + if (e.getSource() == quitButton) { + quitLogin(); + } + else if (e.getSource() == createAccountButton) { + AccountCreationWizard createAccountPanel = new AccountCreationWizard(); + createAccountPanel.invoke(loginDialog); + + if (createAccountPanel.isRegistered()) { + usernameField.setText(createAccountPanel.getUsername()); + passwordField.setText(createAccountPanel.getPassword()); + serverField.setText(createAccountPanel.getServer()); + loginButton.setEnabled(true); + } + } + else if (e.getSource() == loginButton) { + validateLogin(); + } + else if (e.getSource() == connectionButton) { + final LoginSettingDialog loginSettingsDialog = new LoginSettingDialog(); + loginSettingsDialog.invoke(loginDialog); + } + else if (e.getSource() == savePasswordBox) { + autoLoginBox.setEnabled(savePasswordBox.isSelected()); + + if (!savePasswordBox.isSelected()) { + autoLoginBox.setSelected(false); + } + } + else if (e.getSource() == autoLoginBox) { + if (autoLoginBox.isSelected()) { + savePasswordBox.setSelected(true); + } + } + } + + /** + * KeyListener implementation. + * + * @param e the KeyEvent to process. + */ + public void keyTyped(KeyEvent e) { + validate(e); + } + + public void keyPressed(KeyEvent e) { + + // Do nothing. + } + + public void keyReleased(KeyEvent e) { + validateDialog(); + } + + private void validateDialog() { + loginButton.setEnabled(ModelUtil.hasLength(getUsername()) && ModelUtil.hasLength(getPassword()) + && ModelUtil.hasLength(getServerName())); + } + + private void validate(KeyEvent e) { + if (loginButton.isEnabled() && e.getKeyChar() == KeyEvent.VK_ENTER) { + validateLogin(); + } + } + + public void focusGained(FocusEvent e) { + Object o = e.getSource(); + if (o instanceof JTextComponent) { + ((JTextComponent)o).selectAll(); + } + } + + public void focusLost(FocusEvent e) { + } + + private void enableComponents(boolean editable) { + + // Need to set both editable and enabled for best behavior. + usernameField.setEditable(editable); + usernameField.setEnabled(editable); + + passwordField.setEditable(editable); + passwordField.setEnabled(editable); + + serverField.setEditable(editable); + serverField.setEnabled(editable); + + if (editable) { + + // Reapply focus to username field + passwordField.requestFocus(); + } + } + + private void showProgressBar(boolean show) { + if (show) { + cardLayout.show(cardPanel, PROGRESS_BAR); + // progressBar.setIndeterminate(true); + } + else { + cardLayout.show(cardPanel, BUTTON_PANEL); + } + } + + private void validateLogin() { + + SwingWorker worker = new SwingWorker() { + public Object construct() { + + boolean loginSuccessfull = login(); + if (loginSuccessfull) { + progressBar.setText("Connecting. Please wait..."); + + // Startup Spark + startSpark(); + + // dispose login dialog + loginDialog.dispose(); + + // Show ChangeLog if we need to. + // new ChangeLogDialog().showDialog(); + } + else { + savePasswordBox.setEnabled(true); + autoLoginBox.setEnabled(true); + enableComponents(true); + showProgressBar(false); + } + return Boolean.valueOf(loginSuccessfull); + } + }; + + // Start the login process in seperate thread. + // Disable textfields + enableComponents(false); + + // Show progressbar + showProgressBar(true); + + worker.start(); + } + + public JPasswordField getPasswordField() { + return passwordField; + } + + /** + * Login to the specified server using username, password, and workgroup. + * Handles error representation as well as logging. + * + * @return true if login was successful, false otherwise + */ + private boolean login() { + final SessionManager sessionManager = SparkManager.getSessionManager(); + + boolean hasErrors = false; + String errorMessage = null; + + // Handle specifyed Workgroup + String serverName = getServerName(); + + if (!hasErrors) { + localPref = SettingsManager.getLocalPreferences(); + + SmackConfiguration.setPacketReplyTimeout(localPref.getTimeOut() * 1000); + + // Get connection + try { + int port = localPref.getXmppPort(); + + int checkForPort = serverName.indexOf(":"); + if (checkForPort != -1) { + String portString = serverName.substring(checkForPort + 1); + if (ModelUtil.hasLength(portString)) { + // Set new port. + port = Integer.valueOf(portString).intValue(); + } + } + + boolean useSSL = localPref.isSSL(); + boolean hostPortConfigured = localPref.isHostAndPortConfigured(); + + if (useSSL) { + if (!hostPortConfigured) { + connection = new SSLXMPPConnection(serverName); + } + else { + connection = new SSLXMPPConnection(localPref.getXmppHost(), port, serverName); + } + } + else { + if (!hostPortConfigured) { + connection = new XMPPConnection(serverName); + } + else { + connection = new XMPPConnection(localPref.getXmppHost(), port, serverName); + } + } + + String resource = localPref.getResource(); + if (!ModelUtil.hasLength(resource)) { + resource = "spark"; + } + connection.login(getUsername(), getPassword(), resource, false); + + // Subscriptions are always manual + Roster roster = connection.getRoster(); + roster.setSubscriptionMode(Roster.SUBSCRIPTION_MANUAL); + + sessionManager.setServerAddress(connection.getServiceName()); + sessionManager.initializeSession(connection, getUsername(), getPassword()); + final String jid = getUsername() + '@' + sessionManager.getServerAddress() + "/" + resource; + sessionManager.setJID(jid); + + } + catch (Exception xee) { + if (xee instanceof XMPPException) { + XMPPException xe = (XMPPException)xee; + final XMPPError error = xe.getXMPPError(); + int errorCode = 0; + if (error != null) { + errorCode = error.getCode(); + } + if (errorCode == 401) { + errorMessage = SparkRes.getString(SparkRes.INVALID_USERNAME_PASSWORD); + } + else if (errorCode == 502 || errorCode == 504) { + errorMessage = SparkRes.getString(SparkRes.SERVER_UNAVAILABLE); + } + else { + errorMessage = SparkRes.getString(SparkRes.UNRECOVERABLE_ERROR); + } + } + else { + errorMessage = SparkRes.getString(SparkRes.UNRECOVERABLE_ERROR); + } + + // Log Error + Log.warning("Exception in Login:", xee); + hasErrors = true; + } + } + if (hasErrors) { + progressBar.setVisible(false); + //progressBar.setIndeterminate(false); + + // Show error dialog + if (!localPref.isStartedHidden() || !localPref.isAutoLogin()) { + + JOptionPane.showMessageDialog(loginDialog, errorMessage, SparkRes.getString(SparkRes.ERROR_DIALOG_TITLE), + JOptionPane.ERROR_MESSAGE); + + } + setEnabled(true); + return false; + } + + // Since the connection and workgroup are valid. Add a ConnectionListener + connection.addConnectionListener(SparkManager.getSessionManager()); + + // Persist information + localPref.setUsername(getUsername()); + + String encodedPassword = null; + try { + encodedPassword = Encryptor.encrypt(getPassword()); + } + catch (Exception e) { + Log.error("Error encrypting password.", e); + } + localPref.setPassword(encodedPassword); + localPref.setSavePassword(savePasswordBox.isSelected()); + localPref.setAutoLogin(autoLoginBox.isSelected()); + localPref.setServer(serverField.getText()); + + + SettingsManager.saveSettings(); + + return !hasErrors; + } + } + + /** + * If the user quits, just shut down the + * application. + */ + private void quitLogin() { + System.exit(1); + } + + /** + * Initializes Spark and initializes all plugins. + */ + private void startSpark() { + // Invoke the MainWindow. + final MainWindow mainWindow = MainWindow.getInstance(); + + /* + if (tray != null) { + // Remove trayIcon + tray.removeTrayIcon(trayIcon); + } + */ + // Creates the Spark Workspace and add to MainWindow + Workspace workspace = Workspace.getInstance(); + + + mainWindow.getContentPane().add(workspace, BorderLayout.CENTER); + + mainWindow.pack(); + + LayoutSettings settings = LayoutSettingsManager.getLayoutSettings(); + int x = settings.getMainWindowX(); + int y = settings.getMainWindowY(); + int width = settings.getMainWindowWidth(); + int height = settings.getMainWindowHeight(); + + if (x == 0 && y == 0) { + // Use Default size + mainWindow.setSize(310, 520); + + // Center Window on Screen + GraphicUtils.centerWindowOnScreen(mainWindow); + } + else { + mainWindow.setBounds(x, y, width, height); + } + + if (loginDialog.isVisible()) { + mainWindow.setVisible(true); + } + + loginDialog.setVisible(false); + + // Build the layout in the workspace + workspace.buildLayout(); + } + + private void updateProxyConfig() { + if (ModelUtil.hasLength(Default.getString(Default.PROXY_PORT)) && ModelUtil.hasLength(Default.getString(Default.PROXY_HOST))) { + String port = Default.getString(Default.PROXY_PORT); + String host = Default.getString(Default.PROXY_HOST); + System.setProperty("socksProxyHost", host); + System.setProperty("socksProxyPort", port); + return; + } + + boolean proxyEnabled = localPref.isProxyEnabled(); + if (proxyEnabled) { + String host = localPref.getHost(); + String port = localPref.getPort(); + String username = localPref.getProxyUsername(); + String password = localPref.getProxyPassword(); + String protocol = localPref.getProtocol(); + + if (protocol.equals("SOCKS")) { + System.setProperty("socksProxyHost", host); + System.setProperty("socksProxyPort", port); + } + else { + System.setProperty("http.proxyHost", host); + System.setProperty("http.proxyPort", port); + } + } + } + + public class GrayBackgroundPanel extends JPanel { + final ImageIcon icons = Default.getImageIcon(Default.LOGIN_DIALOG_BACKGROUND_IMAGE); + + public GrayBackgroundPanel() { + + } + + public GrayBackgroundPanel(LayoutManager layout) { + super(layout); + } + + public void paintComponent(Graphics g) { + Image backgroundImage = icons.getImage(); + double scaleX = getWidth() / (double)backgroundImage.getWidth(null); + double scaleY = getHeight() / (double)backgroundImage.getHeight(null); + AffineTransform xform = AffineTransform.getScaleInstance(scaleX, scaleY); + ((Graphics2D)g).drawImage(backgroundImage, xform, this); + } + } + + public class ImagePanel extends JPanel { + final ImageIcon icons = Default.getImageIcon(Default.MAIN_IMAGE); + + public ImagePanel() { + + } + + public ImagePanel(LayoutManager layout) { + super(layout); + } + + public void paintComponent(Graphics g) { + Image backgroundImage = icons.getImage(); + double scaleX = getWidth() / (double)backgroundImage.getWidth(null); + double scaleY = getHeight() / (double)backgroundImage.getHeight(null); + AffineTransform xform = AffineTransform.getScaleInstance(scaleX, scaleY); + ((Graphics2D)g).drawImage(backgroundImage, xform, this); + } + + public Dimension getPreferredSize() { + final Dimension size = super.getPreferredSize(); + size.width = icons.getIconWidth(); + size.height = icons.getIconHeight(); + return size; + } + } + +} diff --git a/src/java/org/jivesoftware/LoginSettingDialog.java b/src/java/org/jivesoftware/LoginSettingDialog.java new file mode 100644 index 00000000..c5daa6f2 --- /dev/null +++ b/src/java/org/jivesoftware/LoginSettingDialog.java @@ -0,0 +1,427 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware; + + +import org.jivesoftware.resource.SparkRes; +import org.jivesoftware.spark.component.TitlePanel; +import org.jivesoftware.spark.util.ModelUtil; +import org.jivesoftware.spark.util.ResourceUtils; +import org.jivesoftware.sparkimpl.settings.local.LocalPreferences; +import org.jivesoftware.sparkimpl.settings.local.SettingsManager; + +import javax.swing.BorderFactory; +import javax.swing.JCheckBox; +import javax.swing.JComboBox; +import javax.swing.JComponent; +import javax.swing.JDialog; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JPasswordField; +import javax.swing.JTabbedPane; +import javax.swing.JTextField; + +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.Properties; + +/** + * Allows users to configure startup options. + */ +public class LoginSettingDialog implements PropertyChangeListener { + private JOptionPane optionPane; + private JDialog optionsDialog; + + private TitlePanel titlePanel; + + private JCheckBox autoDiscoverBox = new JCheckBox(); + + private JLabel portLabel = new JLabel(); + private JTextField portField = new JTextField(); + + private JLabel xmppHostLabel = new JLabel(); + private JTextField xmppHostField = new JTextField(); + + private JLabel timeOutLabel = new JLabel(); + private JTextField timeOutField = new JTextField(); + + private JLabel resourceLabel = new JLabel(); + private JTextField resourceField = new JTextField(); + + private JCheckBox autoLoginBox = new JCheckBox(); + + + private JCheckBox useSSLBox = new JCheckBox(); + private JLabel sslLabel = new JLabel(); + + private LocalPreferences localPreferences; + + private ProxyPanel proxyPanel; + + /** + * Empty Constructor. + */ + public LoginSettingDialog() { + localPreferences = SettingsManager.getLocalPreferences(); + proxyPanel = new ProxyPanel(); + } + + /** + * Invokes the OptionsDialog. + * + * @param owner the parent owner of this dialog. This is used for correct parenting. + * @return true if the options have been changed. + */ + public boolean invoke(JFrame owner) { + // Load local localPref + JTabbedPane tabbedPane = new JTabbedPane(); + + portField.setText(Integer.toString(localPreferences.getXmppPort())); + timeOutField.setText(Integer.toString(localPreferences.getTimeOut())); + autoLoginBox.setSelected(localPreferences.isAutoLogin()); + useSSLBox.setSelected(localPreferences.isSSL()); + xmppHostField.setText(localPreferences.getXmppHost()); + resourceField.setText(localPreferences.getResource()); + if (localPreferences.getResource() == null) { + resourceField.setText("spark"); + } + + final JPanel inputPanel = new JPanel(); + tabbedPane.addTab("General", inputPanel); + tabbedPane.addTab("Proxy", proxyPanel); + inputPanel.setLayout(new GridBagLayout()); + + ResourceUtils.resLabel(portLabel, portField, "&Port:"); + ResourceUtils.resLabel(timeOutLabel, timeOutField, "&Response Timeout (sec):"); + ResourceUtils.resButton(autoLoginBox, "&Auto Login"); + ResourceUtils.resLabel(sslLabel, useSSLBox, "&Use OLD SSL port method"); + ResourceUtils.resLabel(xmppHostLabel, xmppHostField, "&Host:"); + ResourceUtils.resButton(autoDiscoverBox, "&Automatically discover host and port"); + ResourceUtils.resLabel(resourceLabel, resourceField, "&Resource:"); + + inputPanel.add(autoDiscoverBox, new GridBagConstraints(0, 0, 2, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + + autoDiscoverBox.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + updateAutoDiscovery(); + } + }); + + autoDiscoverBox.setSelected(!localPreferences.isHostAndPortConfigured()); + updateAutoDiscovery(); + + final JPanel connectionPanel = new JPanel(); + connectionPanel.setLayout(new GridBagLayout()); + connectionPanel.setBorder(BorderFactory.createTitledBorder("Connection")); + + connectionPanel.add(xmppHostLabel, new GridBagConstraints(0, 0, 2, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + connectionPanel.add(xmppHostField, new GridBagConstraints(2, 0, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 200, 0)); + + connectionPanel.add(portLabel, new GridBagConstraints(0, 1, 2, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + connectionPanel.add(portField, new GridBagConstraints(2, 1, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 50, 0)); + + inputPanel.add(connectionPanel, new GridBagConstraints(0, 1, 3, 1, 1.0, 1.0, GridBagConstraints.WEST, GridBagConstraints.BOTH, new Insets(5, 5, 5, 5), 0, 0)); + + inputPanel.add(resourceLabel, new GridBagConstraints(0, 2, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + inputPanel.add(resourceField, new GridBagConstraints(1, 2, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 100, 0)); + + inputPanel.add(timeOutLabel, new GridBagConstraints(0, 3, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + inputPanel.add(timeOutField, new GridBagConstraints(1, 3, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 50, 0)); + + inputPanel.add(sslLabel, new GridBagConstraints(0, 4, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 0), 0, 0)); + inputPanel.add(useSSLBox, new GridBagConstraints(1, 4, 1, 1, 0.0, 1.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); + + // Create the title panel for this dialog + titlePanel = new TitlePanel("Advanced Connection Preferences", "", SparkRes.getImageIcon(SparkRes.BLANK_24x24), true); + + // Construct main panel w/ layout. + final JPanel mainPanel = new JPanel(); + mainPanel.setLayout(new BorderLayout()); + mainPanel.add(titlePanel, BorderLayout.NORTH); + + // The user should only be able to close this dialog. + Object[] options = {"Ok", "Cancel", "Use Default"}; + optionPane = new JOptionPane(tabbedPane, JOptionPane.PLAIN_MESSAGE, + JOptionPane.OK_CANCEL_OPTION, null, options, options[0]); + + mainPanel.add(optionPane, BorderLayout.CENTER); + + optionsDialog = new JDialog(owner, "Preference Window", true); + optionsDialog.setContentPane(mainPanel); + optionsDialog.pack(); + + + optionsDialog.setLocationRelativeTo(owner); + optionPane.addPropertyChangeListener(this); + + optionsDialog.setResizable(true); + optionsDialog.setVisible(true); + optionsDialog.toFront(); + optionsDialog.requestFocus(); + + return true; + } + + /** + * PropertyChangeEvent is called when the user either clicks the Cancel or + * OK button. + * + * @param e the property change event. + */ + public void propertyChange(PropertyChangeEvent e) { + String value = (String)optionPane.getValue(); + if ("Cancel".equals(value)) { + optionsDialog.setVisible(false); + } + else if ("Ok".equals(value)) { + String timeOut = timeOutField.getText(); + String port = portField.getText(); + String resource = resourceField.getText(); + + boolean errors = false; + + try { + Integer.valueOf(timeOut); + } + catch (NumberFormatException numberFormatException) { + JOptionPane.showMessageDialog(optionsDialog, "You must supply a valid Time out value.", + "Invalid Time Out", JOptionPane.ERROR_MESSAGE); + timeOutField.requestFocus(); + errors = true; + } + + try { + Integer.valueOf(port); + } + catch (NumberFormatException numberFormatException) { + JOptionPane.showMessageDialog(optionsDialog, "You must supply a valid Port.", + "Invalid Port", JOptionPane.ERROR_MESSAGE); + portField.requestFocus(); + errors = true; + } + + if (!ModelUtil.hasLength(resource)) { + JOptionPane.showMessageDialog(optionsDialog, "You must supply a resource", + "Invalid Resource", JOptionPane.ERROR_MESSAGE); + resourceField.requestFocus(); + errors = true; + } + + if (!errors) { + localPreferences.setTimeOut(Integer.parseInt(timeOut)); + localPreferences.setXmppPort(Integer.parseInt(port)); + localPreferences.setSSL(useSSLBox.isSelected()); + localPreferences.setXmppHost(xmppHostField.getText()); + optionsDialog.setVisible(false); + localPreferences.setResource(resource); + proxyPanel.save(); + } + else { + optionPane.removePropertyChangeListener(this); + optionPane.setValue(JOptionPane.UNINITIALIZED_VALUE); + optionPane.addPropertyChangeListener(this); + } + } + else { + localPreferences.setTimeOut(30); + localPreferences.setXmppPort(5222); + localPreferences.setSSL(false); + portField.setText("5222"); + timeOutField.setText("30"); + useSSLBox.setSelected(false); + optionPane.setValue(JOptionPane.UNINITIALIZED_VALUE); + } + } + + private class ProxyPanel extends JPanel { + private JCheckBox useProxyBox = new JCheckBox(); + private JComboBox protocolBox = new JComboBox(); + private JTextField hostField = new JTextField(); + private JTextField portField = new JTextField(); + private JTextField usernameField = new JTextField(); + private JPasswordField passwordField = new JPasswordField(); + + public ProxyPanel() { + JLabel protocolLabel = new JLabel(); + JLabel hostLabel = new JLabel(); + JLabel portLabel = new JLabel(); + JLabel usernameLabel = new JLabel(); + JLabel passwordLabel = new JLabel(); + + protocolBox.addItem("SOCKS"); + protocolBox.addItem("HTTP"); + + // Add ResourceUtils + ResourceUtils.resButton(useProxyBox, "&Use Proxy Server"); + ResourceUtils.resLabel(protocolLabel, protocolBox, "&Protocol:"); + ResourceUtils.resLabel(hostLabel, hostField, "&Host:"); + ResourceUtils.resLabel(portLabel, portField, "P&ort:"); + ResourceUtils.resLabel(usernameLabel, usernameField, "&Username:"); + ResourceUtils.resLabel(passwordLabel, passwordField, "P&assword:"); + + setLayout(new GridBagLayout()); + add(useProxyBox, new GridBagConstraints(0, 0, 2, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + + add(protocolLabel, new GridBagConstraints(0, 1, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + add(protocolBox, new GridBagConstraints(1, 1, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); + + add(hostLabel, new GridBagConstraints(0, 2, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + add(hostField, new GridBagConstraints(1, 2, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); + + add(portLabel, new GridBagConstraints(0, 3, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + add(portField, new GridBagConstraints(1, 3, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); + + add(usernameLabel, new GridBagConstraints(0, 4, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + add(usernameField, new GridBagConstraints(1, 4, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); + + add(passwordLabel, new GridBagConstraints(0, 5, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + add(passwordField, new GridBagConstraints(1, 5, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); + + + useProxyBox.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + enableFields(useProxyBox.isSelected()); + } + }); + + // Check localSettings + if (localPreferences.isProxyEnabled()) { + useProxyBox.setSelected(true); + } + + enableFields(useProxyBox.isSelected()); + + if (ModelUtil.hasLength(localPreferences.getHost())) { + hostField.setText(localPreferences.getHost()); + } + + if (ModelUtil.hasLength(localPreferences.getPort())) { + portField.setText(localPreferences.getPort()); + } + + if (ModelUtil.hasLength(localPreferences.getProxyPassword())) { + passwordField.setText(localPreferences.getProxyPassword()); + } + + if (ModelUtil.hasLength(localPreferences.getProxyUsername())) { + usernameField.setText(localPreferences.getProxyUsername()); + } + + if (ModelUtil.hasLength(localPreferences.getProtocol())) { + protocolBox.setSelectedItem(localPreferences.getProtocol()); + } + + } + + private void enableFields(boolean enable) { + Component[] comps = getComponents(); + for (int i = 0; i < comps.length; i++) { + if (comps[i] instanceof JTextField || comps[i] instanceof JComboBox) { + JComponent comp = (JComponent)comps[i]; + comp.setEnabled(enable); + } + } + } + + public boolean useProxy() { + return useProxyBox.isSelected(); + } + + public String getProtocol() { + return (String)protocolBox.getSelectedItem(); + } + + public String getHost() { + return hostField.getText(); + } + + public String getPort() { + return portField.getText(); + } + + public String getUsername() { + return usernameField.getText(); + } + + public String getPassword() { + return new String(passwordField.getPassword()); + } + + public void save() { + localPreferences.setProxyEnabled(useProxyBox.isSelected()); + if (ModelUtil.hasLength(getProtocol())) { + localPreferences.setProtocol(getProtocol()); + } + + if (ModelUtil.hasLength(getHost())) { + localPreferences.setHost(getHost()); + } + + if (ModelUtil.hasLength(getPort())) { + localPreferences.setPort(getPort()); + } + + if (ModelUtil.hasLength(getUsername())) { + localPreferences.setProxyUsername(getUsername()); + } + + if (ModelUtil.hasLength(getPassword())) { + localPreferences.setProxyPassword(getPassword()); + } + + if (!localPreferences.isProxyEnabled()) { + Properties props = System.getProperties(); + props.remove("socksProxyHost"); + props.remove("socksProxyPort"); + props.remove("http.proxyHost"); + props.remove("http.proxyPort"); + props.remove("http.proxySet"); + } + else { + String host = localPreferences.getHost(); + String port = localPreferences.getPort(); + String username = localPreferences.getProxyUsername(); + String password = localPreferences.getProxyPassword(); + String protocol = localPreferences.getProtocol(); + + if (protocol.equals("SOCKS")) { + System.setProperty("socksProxyHost", host); + System.setProperty("socksProxyPort", port); + } + else { + System.setProperty("http.proxySet", "true"); + System.setProperty("http.proxyHost", host); + System.setProperty("http.proxyPort", port); + } + } + + SettingsManager.saveSettings(); + } + } + + + private void updateAutoDiscovery() { + boolean isSelected = autoDiscoverBox.isSelected(); + xmppHostField.setEnabled(!isSelected); + portField.setEnabled(!isSelected); + localPreferences.setHostAndPortConfigured(!isSelected); + } +} + diff --git a/src/java/org/jivesoftware/MainWindow.java b/src/java/org/jivesoftware/MainWindow.java new file mode 100644 index 00000000..6e874c7f --- /dev/null +++ b/src/java/org/jivesoftware/MainWindow.java @@ -0,0 +1,497 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware; + +import org.jivesoftware.resource.Default; +import org.jivesoftware.resource.SparkRes; +import org.jivesoftware.smack.XMPPConnection; +import org.jivesoftware.smack.util.StringUtils; +import org.jivesoftware.smackx.debugger.EnhancedDebuggerWindow; +import org.jivesoftware.spark.SparkManager; +import org.jivesoftware.spark.util.BrowserLauncher; +import org.jivesoftware.spark.util.ResourceUtils; +import org.jivesoftware.spark.util.SwingWorker; +import org.jivesoftware.spark.util.log.Log; +import org.jivesoftware.sparkimpl.settings.JiveInfo; +import org.jivesoftware.sparkimpl.settings.local.SettingsManager; +import org.jivesoftware.sparkimpl.updater.CheckUpdates; + +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.ImageIcon; +import javax.swing.JFrame; +import javax.swing.JMenu; +import javax.swing.JMenuBar; +import javax.swing.JMenuItem; +import javax.swing.JOptionPane; +import javax.swing.JToolBar; + +import java.awt.BorderLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.awt.event.WindowFocusListener; +import java.io.IOException; +import java.sql.Date; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; +import java.util.Timer; +import java.util.TimerTask; + +/** + * The MainWindow class acts as both the DockableHolder and the proxy + * to the Workspace in Spark. + * + * @version 1.0, 03/12/14 + */ +public final class MainWindow extends JFrame implements ActionListener { + private final Set listeners = new HashSet(); + + private final JMenu connectMenu = new JMenu(); + private final JMenu contactsMenu = new JMenu(); + private final JMenu actionsMenu = new JMenu(); + private final JMenu pluginsMenu = new JMenu(); + private final JMenu helpMenu = new JMenu(); + + private JMenuItem preferenceMenuItem; + + private final JMenuItem menuAbout = new JMenuItem(SparkRes.getImageIcon(SparkRes.INFORMATION_IMAGE)); + private final JMenuItem helpMenuItem = new JMenuItem(SparkRes.getImageIcon(SparkRes.SMALL_QUESTION)); + + private final JMenuBar mainWindowBar = new JMenuBar(); + + private boolean focused; + + private JToolBar topBar = new JToolBar(); + + private static MainWindow singleton; + private static final Object LOCK = new Object(); + + /** + * Returns the singleton instance of MainWindow, + * creating it if necessary. + *

    + * + * @return the singleton instance of MainWindow + */ + public static MainWindow getInstance() { + // Synchronize on LOCK to ensure that we don't end up creating + // two singletons. + synchronized (LOCK) { + if (null == singleton) { + MainWindow controller = new MainWindow(Default.getString(Default.APPLICATION_NAME), SparkRes.getImageIcon(SparkRes.MAIN_IMAGE)); + singleton = controller; + return controller; + } + } + return singleton; + } + + + /** + * Constructs the UI for the MainWindow. The MainWindow UI is the container for the + * entire Spark application. + * + * @param title the title of the frame. + * @param icon the icon used in the frame. + */ + private MainWindow(String title, ImageIcon icon) { + // Initialize and dock the menus + configureMenu(); + + // Add Workspace Container + getContentPane().setLayout(new BorderLayout()); + + // Add menubar + this.setJMenuBar(mainWindowBar); + this.getContentPane().add(topBar, BorderLayout.NORTH); + + setTitle(title + " - " + StringUtils.parseName(SparkManager.getConnection().getUser())); + setIconImage(icon.getImage()); + + // Setup WindowListener to be the proxy to the actual window listener + // which cannot normally be used outside of the Window component because + // of protected access. + addWindowListener(new WindowAdapter() { + + /** + * This event fires when the window has become active. + * + * @param e WindowEvent is not used. + */ + public void windowActivated(WindowEvent e) { + fireWindowActivated(); + } + + /** + * Invoked when a window is de-activated. + */ + public void windowDeactivated(WindowEvent e) { + } + + /** + * This event fires whenever a user minimizes the window + * from the toolbar. + * + * @param e WindowEvent is not used. + */ + public void windowIconified(WindowEvent e) { + } + + /** + * This event fires when the application is closing. + * This allows Plugins to do any persistence or other + * work before exiting. + * + * @param e WindowEvent is never used. + */ + public void windowClosing(WindowEvent e) { + setVisible(false); + + // Show confirmation about hiding. + // SparkManager.getNotificationsEngine().confirmHidingIfNecessary(); + } + }); + + this.addWindowFocusListener(new Focuser()); + } + + /** + * Adds a MainWindow listener to {@link MainWindow}. The + * listener will be called when either the MainWindow has been minimized, maximized, + * or is shutting down. + * + * @param listener the MainWindowListener to register + */ + public void addMainWindowListener(MainWindowListener listener) { + listeners.add(listener); + } + + /** + * Removes the specified {@link MainWindowListener}. + * + * @param listener the MainWindowListener to remove. + */ + public void removeMainWindowListener(MainWindowListener listener) { + listeners.remove(listener); + } + + /** + * Notifies all {@link MainWindowListener}s that the MainWindow + * has been activated. + */ + private void fireWindowActivated() { + final Iterator iter = listeners.iterator(); + while (iter.hasNext()) { + ((MainWindowListener)iter.next()).mainWindowActivated(); + } + if (Spark.isMac()) { + setJMenuBar(mainWindowBar); + } + } + + /** + * Notifies all {@link MainWindowListener}s that the MainWindow + * is shutting down. + */ + private void fireWindowShutdown() { + final Iterator iter = listeners.iterator(); + while (iter.hasNext()) { + ((MainWindowListener)iter.next()).shutdown(); + } + } + + /** + * Invokes the Preferences Dialog. + * + * @param e the ActionEvent + */ + public void actionPerformed(ActionEvent e) { + if (e.getSource().equals(preferenceMenuItem)) { + SparkManager.getPreferenceManager().showPreferences(); + } + } + + /** + * Prepares Spark for shutting down by first calling all {@link MainWindowListener}s and + * setting the Agent to be offline. + */ + public void shutdown() { + // Notify all MainWindowListeners + try { + fireWindowShutdown(); + } + finally { + // Close application. + System.exit(1); + } + } + + /** + * Prepares Spark for shutting down by first calling all {@link MainWindowListener}s and + * setting the Agent to be offline. + */ + public void logout() { + // Set auto-login to false; + SettingsManager.getLocalPreferences().setAutoLogin(false); + + // Notify all MainWindowListeners + try { + fireWindowShutdown(); + + final XMPPConnection con = SparkManager.getConnection(); + if (con.isConnected()) { + con.close(); + } + } + finally { + + try { + String command = ""; + if (Spark.isWindows()) { + command = Spark.getBinDirectory().getParentFile().getCanonicalPath() + "\\Spark.exe"; + } + else if (Spark.isMac()) { + command = "open -a Spark"; + } + + Runtime.getRuntime().exec(command); + } + catch (IOException e) { + Log.error("Error starting Spark", e); + } + + System.exit(1); + } + } + + /** + * Setup the Main Toolbar with File, Tools and Help. + */ + private void configureMenu() { + // setup file menu + JMenuItem exitMenuItem = new JMenuItem("Exit"); + + // Setup ResourceUtils + ResourceUtils.resButton(connectMenu, "&" + Default.getString(Default.APPLICATION_NAME)); + ResourceUtils.resButton(contactsMenu, "Con&tacts"); + ResourceUtils.resButton(actionsMenu, "&Actions"); + ResourceUtils.resButton(exitMenuItem, "&Exit"); + ResourceUtils.resButton(pluginsMenu, "&Plugins"); + + exitMenuItem.setIcon(null); + + mainWindowBar.add(connectMenu); + mainWindowBar.add(contactsMenu); + mainWindowBar.add(actionsMenu); + //mainWindowBar.add(pluginsMenu); + mainWindowBar.add(helpMenu); + + + preferenceMenuItem = new JMenuItem(SparkRes.getImageIcon(SparkRes.PREFERENCES_IMAGE)); + preferenceMenuItem.setText("Spark Preferences"); + preferenceMenuItem.addActionListener(this); + connectMenu.add(preferenceMenuItem); + connectMenu.addSeparator(); + + JMenuItem logoutMenuItem = new JMenuItem("Log Out"); + ResourceUtils.resButton(logoutMenuItem, "L&og Out"); + logoutMenuItem.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + logout(); + } + }); + + if (Spark.isWindows()) { + connectMenu.add(logoutMenuItem); + } + + connectMenu.addSeparator(); + + connectMenu.add(exitMenuItem); + + Action showTrafficAction = new AbstractAction() { + public void actionPerformed(ActionEvent actionEvent) { + EnhancedDebuggerWindow window = EnhancedDebuggerWindow.getInstance(); + window.setVisible(true); + } + }; + showTrafficAction.putValue(Action.NAME, "Show Traffic Window"); + showTrafficAction.putValue(Action.SMALL_ICON, SparkRes.getImageIcon(SparkRes.TRAFFIC_LIGHT_IMAGE)); + + Action updateAction = new AbstractAction() { + public void actionPerformed(ActionEvent actionEvent) { + checkUpdate(true); + } + }; + + updateAction.putValue(Action.NAME, "Check For Updates"); + updateAction.putValue(Action.SMALL_ICON, SparkRes.getImageIcon(SparkRes.DOWNLOAD_16x16)); + + // Build Help Menu + helpMenu.setText("Help"); + //s helpMenu.add(helpMenuItem); + helpMenu.add(showTrafficAction); + helpMenu.add(updateAction); + helpMenu.addSeparator(); + helpMenu.add(menuAbout); + + // ResourceUtils - Adds mnemonics + ResourceUtils.resButton(preferenceMenuItem, "&Preferences"); + ResourceUtils.resButton(helpMenu, "&Help"); + ResourceUtils.resButton(menuAbout, "&About"); + ResourceUtils.resButton(helpMenuItem, "&Online Help"); + + // Register shutdown with the exit menu. + exitMenuItem.addActionListener(new AbstractAction() { + public void actionPerformed(ActionEvent e) { + shutdown(); + } + }); + + helpMenuItem.addActionListener(new AbstractAction() { + public void actionPerformed(ActionEvent e) { + try { + BrowserLauncher.openURL("http://www.jivesoftware.org/community/kbcategory.jspa?categoryID=23"); + } + catch (IOException browserException) { + Log.error("Error launching browser:", browserException); + } + } + }); + + // Show About Box + menuAbout.addActionListener(new AbstractAction() { + public void actionPerformed(ActionEvent e) { + showAboutBox(); + } + }); + + int numberOfMillisecondsInTheFuture = 15000; // 15 sec + Date timeToRun = new Date(System.currentTimeMillis() + numberOfMillisecondsInTheFuture); + Timer timer = new Timer(); + + timer.schedule(new TimerTask() { + public void run() { + checkUpdate(false); + } + }, timeToRun); + + } + + /** + * Returns the JMenuBar for the MainWindow. You would call this if you + * wished to add or remove menu items to the main menubar. (File | Tools | Help) + * + * @return the Jive Talker Main Window MenuBar + */ + public JMenuBar getMenu() { + return mainWindowBar; + } + + /** + * Returns the Menu in the JMenuBar by it's name. For example:

    + *

    +     * JMenu toolsMenu = getMenuByName("Tools");
    +     * 
    + *

    + * + * @param name the name of the Menu. + * @return the JMenu item with the requested name. + */ + public JMenu getMenuByName(String name) { + for (int i = 0; i < getMenu().getMenuCount(); i++) { + JMenu menu = getMenu().getMenu(i); + if (menu.getText().equals(name)) { + return menu; + } + } + return null; + } + + /** + * Returns true if the Spark window is in focus. + * + * @return true if the Spark window is in focus. + */ + public boolean isInFocus() { + return focused; + } + + private class Focuser implements WindowFocusListener { + long timer; + + public void windowGainedFocus(WindowEvent e) { + focused = true; + } + + public void windowLostFocus(WindowEvent e) { + focused = false; + } + } + + /** + * Return the top toolbar in the Main Window to allow for customization. + * + * @return the MainWindows top toolbar. + */ + public JToolBar getTopToolBar() { + return topBar; + } + + /** + * Checks for the latest update on the server. + * + * @param forced true if you want to bypass the normal checking security. + */ + private void checkUpdate(final boolean forced) { + final CheckUpdates updater = new CheckUpdates(); + try { + SwingWorker stopWorker = new SwingWorker() { + public Object construct() { + try { + Thread.sleep(50); + } + catch (InterruptedException e) { + Log.error(e); + } + return "ok"; + } + + public void finished() { + try { + updater.checkForUpdate(forced); + } + catch (Exception e) { + Log.error("There was an error while checking for a new update.", e); + } + } + }; + + stopWorker.start(); + + } + catch (Exception e) { + Log.warning("Error updating.", e); + } + } + + /** + * Displays the About Box for Spark. + */ + public void showAboutBox() { + JOptionPane.showMessageDialog(SparkManager.getMainWindow(), Default.getString(Default.APPLICATION_NAME) + " " + JiveInfo.getVersion(), + "About", JOptionPane.INFORMATION_MESSAGE); + } + +} \ No newline at end of file diff --git a/src/java/org/jivesoftware/MainWindowListener.java b/src/java/org/jivesoftware/MainWindowListener.java new file mode 100644 index 00000000..ba580149 --- /dev/null +++ b/src/java/org/jivesoftware/MainWindowListener.java @@ -0,0 +1,52 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware; + +/** + * The MainWindowListener interface is one of the interfaces extension + * writers use to add functionality to Spark. + *

    + * In general, you implement this interface in order to listen + * for Window events that could otherwise not be listened to by attaching + * to the MainWindow due to security restrictions. + */ +public interface MainWindowListener { + + /** + * Invoked by the MainWindow when it is about the shutdown. + * When invoked, the MainWindowListener + * should do anything necessary to persist their current state. + * MainWindowListeners authors should take care to ensure + * that any extraneous processing is not performed on this method, as it would + * cause a delay in the shutdown process. + * + * @see org.jivesoftware.MainWindow + */ + void shutdown(); + + /** + * Invoked by the MainWindow when it has been activated, such + * as when it is coming out of a minimized state. + * When invoked, the MainWindowListener + * should do anything necessary to smoothly transition back to the application. + * + * @see org.jivesoftware.MainWindow + */ + void mainWindowActivated(); + + /** + * Invoked by the MainWindow when it has been minimized in the toolbar. + * + * @see org.jivesoftware.MainWindow + */ + void mainWindowDeactivated(); + +} \ No newline at end of file diff --git a/src/java/org/jivesoftware/Spark.java b/src/java/org/jivesoftware/Spark.java new file mode 100644 index 00000000..a2590a33 --- /dev/null +++ b/src/java/org/jivesoftware/Spark.java @@ -0,0 +1,324 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware; + +import org.jivesoftware.resource.Default; +import org.jivesoftware.resource.SparkRes; +import org.jivesoftware.smack.XMPPConnection; +import org.jivesoftware.smackx.debugger.EnhancedDebuggerWindow; +import org.jivesoftware.spark.util.log.Log; + +import javax.swing.BorderFactory; +import javax.swing.JFrame; +import javax.swing.JOptionPane; +import javax.swing.SwingUtilities; +import javax.swing.UIManager; + +import java.awt.Color; +import java.awt.Font; +import java.awt.Insets; +import java.io.File; + +/** + * In many cases, you will need to know the structure of the Spark installation, such as the directory structures, what + * type of system Spark is running on, and also the arguments which were passed into Spark on startup. The Spark + * class provides some simple static calls to retrieve this information. + * + * @version 1.0, 11/17/2005 + */ +public final class Spark { + + private static final String USER_HOME = System.getProperties().getProperty("user.home"); + private static String argument; + + private static File RESOURCE_DIRECTORY; + private static File BIN_DIRECTORY; + private static File LOG_DIRECTORY; + + + /** + * Private constructor that invokes the LoginDialog and + * the Spark Main Application. + */ + private Spark() { + final LoginDialog dialog = new LoginDialog(); + dialog.invoke(new JFrame()); + } + + /** + * Invocation method. + * + * @param args - Will receive arguments from Java Web Start. + */ + public static void main(final String[] args) { + EnhancedDebuggerWindow.PERSISTED_DEBUGGER = true; + EnhancedDebuggerWindow.MAX_TABLE_ROWS = 10; + XMPPConnection.DEBUG_ENABLED = true; + + + String current = System.getProperty("java.library.path"); + String classPath = System.getProperty("java.class.path"); + + // Set UIManager properties for JTree + System.setProperty("apple.laf.useScreenMenuBar", "true"); + + /** Update Library Path **/ + StringBuffer buf = new StringBuffer(); + buf.append(current); + buf.append(";"); + + + final String workingDirectory = System.getProperty("appdir"); + if (workingDirectory == null) { + RESOURCE_DIRECTORY = new File(USER_HOME, "/Spark/resources").getAbsoluteFile(); + BIN_DIRECTORY = new File(USER_HOME, "/Spark/bin").getAbsoluteFile(); + LOG_DIRECTORY = new File(USER_HOME, "/Spark/logs").getAbsoluteFile(); + RESOURCE_DIRECTORY.mkdirs(); + LOG_DIRECTORY.mkdirs(); + if (!RESOURCE_DIRECTORY.exists() || !LOG_DIRECTORY.exists()) { + JOptionPane.showMessageDialog(new JFrame(), "Unable to create directories necessary for runtime.", "Spark Error", JOptionPane.ERROR_MESSAGE); + System.exit(1); + } + } + else { + // This is the installed executable. + File workingDir = new File(workingDirectory); + + RESOURCE_DIRECTORY = new File(workingDir, "resources").getAbsoluteFile(); + BIN_DIRECTORY = new File(workingDir, "bin").getAbsoluteFile(); + LOG_DIRECTORY = new File(USER_HOME, "/Spark/logs").getAbsoluteFile(); + LOG_DIRECTORY.mkdirs(); + buf.append(RESOURCE_DIRECTORY.getAbsolutePath()).append(";"); + } + + + buf.append(classPath); + + // Update System Properties + System.setProperty("java.library.path", buf.toString()); + + + System.setProperty("sun.java2d.noddraw", "true"); + + + SwingUtilities.invokeLater(new Runnable() { + public void run() { + // Start Application + new Spark(); + } + }); + + // Handle arguments + if (args.length > 0) { + argument = args[0]; + } + } + + + // Setup the look and feel of this application. + static { + try { + String classname = UIManager.getSystemLookAndFeelClassName(); + + if (classname.indexOf("Windows") != -1) { + try { + try { + + UIManager.setLookAndFeel(new com.jgoodies.looks.windows.WindowsLookAndFeel()); + + } + catch (Exception e) { + //Handling Exception + } + } + catch (Exception e) { + } + + } + else if (classname.indexOf("mac") != -1 || classname.indexOf("apple") != -1) { + UIManager.setLookAndFeel(new com.jgoodies.looks.plastic.Plastic3DLookAndFeel()); + + } + else { + UIManager.setLookAndFeel(new com.jgoodies.looks.plastic.Plastic3DLookAndFeel()); + } + + } + catch (Exception e) { + Log.error(e); + } + + UIManager.put("Tree.openIcon", SparkRes.getImageIcon(SparkRes.FOLDER)); + UIManager.put("Tree.closedIcon", SparkRes.getImageIcon(SparkRes.FOLDER_CLOSED)); + UIManager.put("Button.showMnemonics", Boolean.TRUE); + UIManager.put("CollapsiblePane.titleFont", new Font("Dialog", Font.BOLD, 11)); + UIManager.put("DockableFrameTitlePane.font", new Font("Verdana", Font.BOLD, 10)); + UIManager.put("DockableFrame.inactiveTitleForeground", Color.white); + UIManager.put("DockableFrame.inactiveTitleBackground", new Color(180, 176, 160)); + UIManager.put("DockableFrame.activeTitleBackground", new Color(105, 132, 188)); + UIManager.put("DockableFrame.activeTitleForeground", Color.white); + UIManager.put("CollapsiblePane.background", Color.white); + UIManager.put("TextField.font", new Font("Dialog", Font.PLAIN, 11)); + if (isWindows()) { + UIManager.put("DockableFrameTitlePane.titleBarComponent", Boolean.valueOf(true)); + } + else { + UIManager.put("DockableFrameTitlePane.titleBarComponent", Boolean.valueOf(false)); + } + + UIManager.put("SidePane.lineColor", Color.BLACK); + UIManager.put("SidePane.foreground", Color.BLACK); + + + Color menuBarColor = new Color(255, 255, 255);//235, 233, 237); + UIManager.put("MenuBar.background", menuBarColor); + UIManager.put("JideTabbedPane.tabInsets", new Insets(3, 10, 3, 10)); + UIManager.put("JideTabbedPane.contentBorderInsets", new Insets(0, 0, 0, 0)); + + installBaseUIProperties(); + + //com.install4j.api.launcher.StartupNotification.registerStartupListener(new SparkStartupListener()); + } + + /** + * Return if we are running on windows. + * + * @return true if we are running on windows, false otherwise. + */ + public static boolean isWindows() { + final String osName = System.getProperty("os.name").toLowerCase(); + return osName.startsWith("windows"); + } + + /** + * Return if we are running on a mac. + * + * @return true if we are running on a mac, false otherwise. + */ + public static boolean isMac() { + String lcOSName = System.getProperty("os.name").toLowerCase(); + return lcOSName.indexOf("mac") != -1; + } + + + /** + * Returns the value associated with a passed in argument. Spark + * accepts HTTP style attributes to allow for name-value pairing. + * ex. username=foo&password=pwd. + * To retrieve the value of username, you would do the following: + *

    +     * String value = Spark.getArgumentValue("username");
    +     * 
    + * + * @param argumentName the name of the argument to retrieve. + * @return the value of the argument. If no argument was found, null + * will be returned. + */ + public static String getArgumentValue(String argumentName) { + if (argument == null) { + return null; + } + + String arg = argumentName + "="; + + int index = argument.indexOf(arg); + if (index == -1) { + return null; + } + + String value = argument.substring(index + arg.length()); + int index2 = value.indexOf("&"); + if (index2 != -1) { + // Must be the last argument + value = value.substring(0, index2); + } + + + return value; + } + + /** + * Returns the bin directory of the Spark install. The bin directory contains the startup scripts needed + * to start Spark. + * + * @return the bin directory. + */ + public static File getBinDirectory() { + return BIN_DIRECTORY; + } + + /** + * Returns the resource directory of the Spark install. The resource directory contains all native + * libraries needed to run os specific operations, such as tray support. You may place other native + * libraries within this directory if you wish to have them placed into the system.library.path. + * + * @return the resource directory. + */ + public static File getResourceDirectory() { + return RESOURCE_DIRECTORY; + } + + /** + * Returns the log directory. The log directory contains all debugging and error files for Spark. + * + * @return the log directory. + */ + public static File getLogDirectory() { + return LOG_DIRECTORY; + } + + /** + * Returns the User specific directory for this Spark instance. The user home is where all user specific + * files are placed to run Spark within a multi-user system. + * + * @return the user home; + */ + public static String getUserHome() { + return USER_HOME; + } + + public static boolean isCustomBuild() { + return "true".equals(Default.getString("CUSTOM")); + } + + public static void installBaseUIProperties() { + UIManager.put("TextField.lightforeground", Color.BLACK); + UIManager.put("TextField.foreground", Color.BLACK); + UIManager.put("TextField.caretForeground", Color.black); + + UIManager.put("List.selectionBackground", new Color(217, 232, 250)); + UIManager.put("List.selectionForeground", Color.black); + UIManager.put("List.selectionBorder", new Color(187, 195, 215)); + UIManager.put("List.foreground", Color.black); + UIManager.put("List.background", Color.white); + UIManager.put("TextPane.foreground", Color.black); + UIManager.put("TextPane.background", Color.white); + UIManager.put("TextPane.inactiveForeground", Color.white); + UIManager.put("TextPane.caretForeground", Color.black); + UIManager.put("ChatInput.SelectedTextColor", Color.white); + UIManager.put("ChatInput.SelectionColor", new Color(209, 223, 242)); + UIManager.put("ContactItemNickname.foreground", Color.black); + UIManager.put("ContactItemDescription.foreground", Color.lightGray); + UIManager.put("ContactItem.background", Color.white); + UIManager.put("ContactItem.border", BorderFactory.createLineBorder(Color.white)); + UIManager.put("ContactItemOffline.color", Color.gray); + UIManager.put("Table.foreground", Color.black); + UIManager.put("Table.background", Color.white); + + // Chat Area Text Settings + UIManager.put("Link.foreground", Color.blue); + UIManager.put("User.foreground", Color.blue); + UIManager.put("OtherUser.foreground", Color.red); + UIManager.put("Notification.foreground", new Color(51, 153, 51)); + UIManager.put("Error.foreground", Color.red); + UIManager.put("Question.foreground", Color.red); + } +} \ No newline at end of file diff --git a/src/java/org/jivesoftware/SparkStartupListener.java b/src/java/org/jivesoftware/SparkStartupListener.java new file mode 100644 index 00000000..b1dfbdd7 --- /dev/null +++ b/src/java/org/jivesoftware/SparkStartupListener.java @@ -0,0 +1,103 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware; + +import org.jivesoftware.smack.packet.Message; +import org.jivesoftware.spark.ChatManager; +import org.jivesoftware.spark.SparkManager; +import org.jivesoftware.spark.UserManager; +import org.jivesoftware.spark.ui.ChatRoom; +import org.jivesoftware.spark.ui.conferences.ConferenceUtils; +import org.jivesoftware.spark.util.log.Log; + +/** + * Uses the Windows registry to perform URI XMPP mappings. + * + * @author Derek DeMoro + */ +public class SparkStartupListener implements com.install4j.api.launcher.StartupNotification.Listener { + + public void startupPerformed(String string) { + if (string.indexOf("xmpp") == -1) { + return; + } + + if (string.indexOf("?message") != -1) { + try { + handleJID(string); + } + catch (Exception e) { + Log.error(e); + } + } + else if (string.indexOf("?join") != -1) { + try { + handleConference(string); + } + catch (Exception e) { + Log.error(e); + } + } + + } + + /** + * Factory method to handle different types of URI Mappings. + * + * @param uriMapping the uri mapping string. + * @throws Exception thrown if an exception occurs. + */ + public void handleJID(String uriMapping) throws Exception { + int index = uriMapping.indexOf("xmpp:"); + int messageIndex = uriMapping.indexOf("?message"); + + int bodyIndex = uriMapping.indexOf("body="); + + String jid = uriMapping.substring(index + 5, messageIndex); + String body = null; + + // Find body + if (bodyIndex != -1) { + body = uriMapping.substring(bodyIndex + 5); + } + + UserManager userManager = SparkManager.getUserManager(); + String nickname = userManager.getUserNicknameFromJID(jid); + if (nickname == null) { + nickname = jid; + } + + ChatManager chatManager = SparkManager.getChatManager(); + ChatRoom chatRoom = chatManager.createChatRoom(jid, nickname, nickname); + if (body != null) { + Message message = new Message(); + message.setBody(body); + chatRoom.insertMessage(message); + chatRoom.sendMessage(message); + } + + chatManager.getChatContainer().activateChatRoom(chatRoom); + } + + /** + * Handles the URI Mapping to join a conference room. + * + * @param uriMapping the uri mapping. + * @throws Exception thrown if the conference cannot be joined. + */ + public void handleConference(String uriMapping) throws Exception { + int index = uriMapping.indexOf("xmpp:"); + int join = uriMapping.indexOf("?join"); + + String conference = uriMapping.substring(index + 5, join); + ConferenceUtils.autoJoinConferenceRoom(conference, conference, null); + } +} diff --git a/src/java/org/jivesoftware/Uninstaller.java b/src/java/org/jivesoftware/Uninstaller.java new file mode 100644 index 00000000..988577c1 --- /dev/null +++ b/src/java/org/jivesoftware/Uninstaller.java @@ -0,0 +1,38 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware; + +import com.install4j.api.Context; +import com.install4j.api.ProgressInterface; +import com.install4j.api.UninstallAction; +import com.install4j.api.windows.WinRegistry; + +import java.io.File; + +/** + * Performs registry operations on removal of the Spark client. This is a windows only function. + * + * @author Derek DeMoro + */ +public class Uninstaller extends UninstallAction { + + public int getPercentOfTotalInstallation() { + return 0; + } + + public boolean performAction(Context context, ProgressInterface progressInterface) { + return super.performAction(context, progressInterface); //To change body of overridden methods use File | Settings | File Templates. + } + + public void removeStartup(File dir) { + WinRegistry.deleteValue(WinRegistry.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Run", "Spark"); + } +} diff --git a/src/java/org/jivesoftware/resource/ConfigurationRes.java b/src/java/org/jivesoftware/resource/ConfigurationRes.java new file mode 100644 index 00000000..9dd94c8c --- /dev/null +++ b/src/java/org/jivesoftware/resource/ConfigurationRes.java @@ -0,0 +1,48 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.resource; + +import javax.swing.ImageIcon; + +import java.net.URL; +import java.util.PropertyResourceBundle; +import java.util.ResourceBundle; + +public class ConfigurationRes { + private static PropertyResourceBundle prb; + public static final String GLOBAL_ELEMENT_NAME = "GLOBAL_ELEMENT_NAME"; + public static final String DELETE_IMAGE = "DELETE_IMAGE"; + public static final String PERSONAL_NAMESPACE = "PERSONAL_NAMESPACE"; + public static final String HEADER_FILE = "HEADER_FILE"; + public static final String CHECK_IMAGE = "CHECK_IMAGE"; + + public static final String SPELLING_PROPERTIES = "SPELLING_PROPERTIES"; + public static final String PERSONAL_ELEMENT_NAME = "PERSONAL_ELEMENT_NAME"; + static ClassLoader cl = ConfigurationRes.class.getClassLoader(); + + static { + prb = (PropertyResourceBundle)ResourceBundle.getBundle("org/jivesoftware/resource/configuration"); + } + + public static final String getString(String propertyName) { + return prb.getString(propertyName); + } + + public static final ImageIcon getImageIcon(String imageName) { + final String iconURI = getString(imageName); + final URL imageURL = cl.getResource(iconURI); + return new ImageIcon(imageURL); + } + + public static final URL getURL(String propertyName) { + return cl.getResource(getString(propertyName)); + } +} \ No newline at end of file diff --git a/src/java/org/jivesoftware/resource/Default.java b/src/java/org/jivesoftware/resource/Default.java new file mode 100644 index 00000000..6e927200 --- /dev/null +++ b/src/java/org/jivesoftware/resource/Default.java @@ -0,0 +1,102 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.resource; + +import javax.swing.ImageIcon; + +import java.net.URL; +import java.util.HashMap; +import java.util.Map; +import java.util.PropertyResourceBundle; +import java.util.ResourceBundle; + +public class Default { + private static PropertyResourceBundle prb; + + private static Map customMap = new HashMap(); + + private static Map cache = new HashMap(); + + public static final String MAIN_IMAGE = "MAIN_IMAGE"; + public static final String APPLICATION_NAME = "APPLICATION_NAME"; + public static final String SHORT_NAME = "SHORT_NAME"; + public static final String LOGIN_DIALOG_BACKGROUND_IMAGE = "LOGIN_DIALOG_BACKGROUND_IMAGE"; + public static final String HOST_NAME = "HOST_NAME"; + public static final String SHOW_POWERED_BY = "SHOW_POWERED_BY"; + public static final String TOP_BOTTOM_BACKGROUND_IMAGE = "TOP_BOTTOM_BACKGROUND_IMAGE"; + public static final String BRANDED_IMAGE = "BRANDED_IMAGE"; + public static final String CUSTOM = "CUSTOM"; + public static final String SECONDARY_BACKGROUND_IMAGE = "SECONDARY_BACKGROUND_IMAGE"; + public static final String HOVER_TEXT_COLOR = "HOVER_TEXT_COLOR"; + public static final String TEXT_COLOR = "TEXT_COLOR"; + public static final String TAB_START_COLOR = "TAB_START_COLOR"; + public static final String TAB_END_COLOR = "TAB_END_COLOR"; + public static final String CONTACT_GROUP_START_COLOR = "CONTACT_GROUP_START_COLOR"; + public static final String CONTACT_GROUP_END_COLOR = "CONTACT_GROUP_END_COLOR"; + public static final String PROXY_HOST = "PROXY_HOST"; + public static final String PROXY_PORT = "PROXY_PORT"; + public static final String ACCOUNT_DISABLED = "ACCOUNT_DISABLED"; + + static ClassLoader cl = SparkRes.class.getClassLoader(); + + static { + prb = (PropertyResourceBundle)ResourceBundle.getBundle("org/jivesoftware/resource/default"); + } + + public static void putCustomValue(String value, Object object) { + customMap.put(value, object); + } + + public static void removeCustomValue(String value) { + customMap.remove(value); + } + + public static void clearCustomValues() { + customMap.clear(); + } + + public static final String getString(String propertyName) { + return prb.getString(propertyName); + } + + public static final ImageIcon getImageIcon(String imageName) { + // Check custom map + Object o = customMap.get(imageName); + if (o != null && o instanceof ImageIcon) { + return (ImageIcon)o; + } + + // Otherwise check cache + o = cache.get(imageName); + if (o != null && o instanceof ImageIcon) { + return (ImageIcon)o; + } + + // Otherwise, load and add to cache. + try { + final String iconURI = getString(imageName); + final URL imageURL = cl.getResource(iconURI); + + final ImageIcon icon = new ImageIcon(imageURL); + cache.put(imageName, icon); + return icon; + } + catch (Exception ex) { + System.out.println(imageName + " not found."); + } + return null; + } + + public static final URL getURL(String propertyName) { + return cl.getResource(getString(propertyName)); + } + +} \ No newline at end of file diff --git a/src/java/org/jivesoftware/resource/EmotionRes.java b/src/java/org/jivesoftware/resource/EmotionRes.java new file mode 100644 index 00000000..7e725e3d --- /dev/null +++ b/src/java/org/jivesoftware/resource/EmotionRes.java @@ -0,0 +1,79 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.resource; + +import javax.swing.ImageIcon; + +import java.net.URL; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; + +public class EmotionRes { + private static final Map emotionMap = new LinkedHashMap(); + static ClassLoader cl = EmotionRes.class.getClassLoader(); + + static { + emotionMap.put(":)", "images/emoticons/happy.gif"); + emotionMap.put(":-)", "images/emoticons/happy.gif"); + emotionMap.put(":(", "images/emoticons/sad.gif"); + emotionMap.put(":D", "images/emoticons/grin.gif"); + emotionMap.put(":x", "images/emoticons/love.gif"); + emotionMap.put(";\\", "images/emoticons/mischief.gif"); + emotionMap.put("B-)", "images/emoticons/cool.gif"); + emotionMap.put("]:)", "images/emoticons/devil.gif"); + emotionMap.put(":p", "images/emoticons/silly.gif"); + emotionMap.put("X-(", "images/emoticons/angry.gif"); + emotionMap.put(":^0", "images/emoticons/laugh.gif"); + emotionMap.put(";)", "images/emoticons/wink.gif"); + emotionMap.put(";-)", "images/emoticons/wink.gif"); + emotionMap.put(":8}", "images/emoticons/blush.gif"); + emotionMap.put(":_|", "images/emoticons/cry.gif"); + emotionMap.put("?:|", "images/emoticons/confused.gif"); + emotionMap.put(":0", "images/emoticons/shocked.gif"); + emotionMap.put(":|", "images/emoticons/plain.gif"); + emotionMap.put("8-)", "images/emoticons/eyeRoll.gif"); + emotionMap.put("|-)", "images/emoticons/sleepy.gif"); + emotionMap.put("<:o)", "images/emoticons/party.gif"); + } + + public static final ImageIcon getImageIcon(String face) { + final String value = (String)emotionMap.get(face); + if (value != null) { + final URL url = cl.getResource(value); + if (url != null) { + return new ImageIcon(url); + } + } + return null; + } + + public static final URL getURL(String face) { + final String value = (String)emotionMap.get(face); + if (value != null) { + final URL url = cl.getResource(value); + if (url != null) { + return url; + } + } + return null; + } + + public static Map getEmoticonMap() { + Map newMap = new HashMap(emotionMap); + newMap.remove("8-)"); + newMap.remove("|-)"); + newMap.remove("<:o)"); + newMap.remove(":-)"); + return newMap; + } + +} \ No newline at end of file diff --git a/src/java/org/jivesoftware/resource/SoundsRes.java b/src/java/org/jivesoftware/resource/SoundsRes.java new file mode 100644 index 00000000..e36c63b6 --- /dev/null +++ b/src/java/org/jivesoftware/resource/SoundsRes.java @@ -0,0 +1,46 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.resource; + +import javax.swing.ImageIcon; + +import java.net.URL; +import java.util.PropertyResourceBundle; +import java.util.ResourceBundle; + +public class SoundsRes { + private static PropertyResourceBundle prb; + public static final String INCOMING_USER = "INCOMING_USER"; + public static final String TRAY_SHOWING = "TRAY_SHOWING"; + public static final String OPENING = "OPENING"; + public static final String CLOSING = "CLOSING"; + + + static ClassLoader cl = SoundsRes.class.getClassLoader(); + + static { + prb = (PropertyResourceBundle)ResourceBundle.getBundle("org/jivesoftware/resource/sounds"); + } + + public static final String getString(String propertyName) { + return prb.getString(propertyName); + } + + public static final ImageIcon getImageIcon(String imageName) { + final String iconURI = getString(imageName); + final URL imageURL = cl.getResource(iconURI); + return new ImageIcon(imageURL); + } + + public static final URL getURL(String propertyName) { + return cl.getResource(getString(propertyName)); + } +} \ No newline at end of file diff --git a/src/java/org/jivesoftware/resource/SparkRes.java b/src/java/org/jivesoftware/resource/SparkRes.java new file mode 100644 index 00000000..5334a512 --- /dev/null +++ b/src/java/org/jivesoftware/resource/SparkRes.java @@ -0,0 +1,318 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.resource; + +import javax.swing.ImageIcon; +import javax.swing.JEditorPane; +import javax.swing.JFrame; +import javax.swing.JScrollPane; + +import java.awt.BorderLayout; +import java.io.File; +import java.net.URL; +import java.util.Enumeration; +import java.util.PropertyResourceBundle; +import java.util.ResourceBundle; + +public class SparkRes { + private static PropertyResourceBundle prb; + + public static final String NOTE_EDIT_16x16 = "NOTE_EDIT_16x16"; + public static final String MAGICIAN_IMAGE = "MAGICIAN_IMAGE"; + public static final String IM_AWAY = "IM_AWAY"; + public static final String RED_FLAG_16x16 = "RED_FLAG_16x16"; + public static final String LOGIN_DIALOG_USERNAME = "LOGIN_DIALOG_USERNAME"; + public static final String PREFERENCES_IMAGE = "PREFERENCES_IMAGE"; + public static final String CURRENT_AGENTS = "CURRENT_AGENTS"; + public static final String LOGIN_DIALOG_QUIT = "LOGIN_DIALOG_QUIT"; + public static final String RIGHT_ARROW_IMAGE = "RIGHT_ARROW_IMAGE"; + public static final String MEGAPHONE_16x16 = "MEGAPHONE_16x16"; + public static final String SMALL_DOCUMENT_ADD = "SMALL_DOCUMENT_ADD"; + public static final String ADD_TO_KB = "ADD_TO_KB"; + public static final String DATA_REFRESH_16x16 = "DATA_REFRESH_16x16"; + public static final String TEXT_ITALIC = "TEXT_ITALIC"; + public static final String NOTEBOOK_IMAGE = "NOTEBOOK_IMAGE"; + public static final String AVAILABLE_USER = "AVAILABLE_USER"; + public static final String SPARK_IMAGE = "SPARK_IMAGE"; + public static final String VIEW = "VIEW"; + public static final String TEXT_BOLD = "TEXT_BOLD"; + public static final String SMALL_USER1_MESSAGE = "SMALL_USER1_MESSAGE"; + public static final String SERVER_UNAVAILABLE = "SERVER_UNAVAILABLE"; + public static final String CONFERENCE_IMAGE_16x16 = "CONFERENCE_IMAGE_16x16"; + public static final String WORKGROUP_QUEUE = "WORKGROUP_QUEUE"; + public static final String SEARCH_IMAGE_32x32 = "SEARCH_IMAGE_32x32"; + public static final String ADD_BOOKMARK_ICON = "ADD_BOOKMARK_ICON"; + public static final String TEXT_UNDERLINE = "TEXT_UNDERLINE"; + public static final String TOOLBOX = "TOOLBOX"; + public static final String PROFILE_ICON = "PROFILE_ICON"; + public static final String SMALL_CURRENT_AGENTS = "SMALL_CURRENT_AGENTS"; + public static final String STAR_GREEN_IMAGE = "STAR_GREEN_IMAGE"; + public static final String QUESTIONS_ANSWERS = "QUESTIONS_ANSWERS"; + public static final String DOCUMENT_EXCHANGE_IMAGE = "DOCUMENT_EXCHANGE_IMAGE"; + public static final String MOBILE_PHONE_IMAGE = "MOBILE_PHONE_IMAGE"; + public static final String BLANK_IMAGE = "BLANK_IMAGE"; + public static final String LOCK_UNLOCK_16x16 = "LOCK_UNLOCK_16x16"; + public static final String BOOKMARK_ICON = "BOOKMARK_ICON"; + public static final String INFORMATION_ICO = "INFORMATION_ICO"; + public static final String VERSION = "VERSION"; + public static final String MAIL_INTO_16x16 = "MAIL_INTO_16x16"; + public static final String ERROR_INVALID_WORKGROUP = "ERROR_INVALID_WORKGROUP"; + public static final String SMALL_USER1_NEW = "SMALL_USER1_NEW"; + public static final String SMALL_USER_DELETE = "SMALL_USER_DELETE"; + public static final String CHATTING_AGENT_IMAGE = "CHATTING_AGENT_IMAGE"; + public static final String FORUM_TAB_TITLE = "FORUM_TAB_TITLE"; + public static final String DOCUMENT_16x16 = "DOCUMENT_16x16"; + public static final String KNOWLEDGE_BASE_TAB_TITLE = "KNOWLEDGE_BASE_TAB_TITLE"; + public static final String STAR_RED_IMAGE = "STAR_RED_IMAGE"; + public static final String USER1_BACK_16x16 = "USER1_BACK_16x16"; + public static final String SMALL_USER1_STOPWATCH = "SMALL_USER1_STOPWATCH"; + public static final String ADD_IMAGE_24x24 = "ADD_IMAGE_24x24"; + public static final String TRAFFIC_LIGHT_IMAGE = "TRAFFIC_LIGHT_IMAGE"; + public static final String GO = "GO"; + public static final String SMALL_USER1_INFORMATION = "SMALL_USER1_INFORMATION"; + public static final String DATA_DELETE_16x16 = "DATA_DELETE_16x16"; + public static final String SMALL_MESSAGE_IMAGE = "SMALL_MESSAGE_IMAGE"; + public static final String LOCK_16x16 = "LOCK_16x16"; + public static final String STAR_GREY_IMAGE = "STAR_GREY_IMAGE"; + public static final String MODERATOR_IMAGE = "MODERATOR_IMAGE"; + public static final String JOIN_GROUPCHAT_IMAGE = "JOIN_GROUPCHAT_IMAGE"; + public static final String UNRECOVERABLE_ERROR = "UNRECOVERABLE_ERROR"; + public static final String DOCUMENT_INFO_32x32 = "DOCUMENT_INFO_32x32"; + public static final String CREATE_FAQ_TITLE = "CREATE_FAQ_TITLE"; + public static final String SMALL_USER1_TIME = "SMALL_USER1_TIME"; + public static final String SMALL_ALL_AGENTS_IMAGE = "SMALL_ALL_AGENTS_IMAGE"; + public static final String PAWN_GLASS_WHITE = "PAWN_GLASS_WHITE"; + public static final String CURRENT_CHATS = "CURRENT_CHATS"; + public static final String INVALID_USERNAME_PASSWORD = "INVALID_USERNAME_PASSWORD"; + public static final String SMALL_DATA_FIND_IMAGE = "SMALL_DATA_FIND_IMAGE"; + public static final String CLOSE_IMAGE = "CLOSE_IMAGE"; + public static final String TEXT_NORMAL = "TEXT_NORMAL"; + public static final String STAR_YELLOW_IMAGE = "STAR_YELLOW_IMAGE"; + public static final String APP_NAME = "APP_NAME"; + public static final String ADD_CONTACT_IMAGE = "ADD_CONTACT_IMAGE"; + public static final String SEND_MAIL_IMAGE_16x16 = "SEND_MAIL_IMAGE_16x16"; + public static final String FOLDER = "FOLDER"; + public static final String LEFT_ARROW_IMAGE = "LEFT_ARROW_IMAGE"; + public static final String WELCOME = "WELCOME"; + public static final String BLUE_BALL = "BLUE_BALL"; + public static final String MESSAGE_DND = "MESSAGE_DND"; + public static final String DOCUMENT_FIND_16x16 = "DOCUMENT_FIND_16x16"; + public static final String RED_BALL = "RED_BALL"; + public static final String BRICKWALL_IMAGE = "BRICKWALL_IMAGE"; + public static final String MESSAGE_AWAY = "MESSAGE_AWAY"; + public static final String IM_DND = "IM_DND"; + public static final String SMALL_DELETE = "SMALL_DELETE"; + public static final String LINK_16x16 = "LINK_16x16"; + public static final String CALL_ICON = "CALL_ICON"; + public static final String MAIN_IMAGE = "MAIN_IMAGE"; + public static final String SMALL_CIRCLE_DELETE = "SMALL_CIRCLE_DELETE"; + public static final String SMALL_MESSAGE_EDIT_IMAGE = "SMALL_MESSAGE_EDIT_IMAGE"; + public static final String AWAY_USER = "AWAY_USER"; + public static final String SAVE_AS_16x16 = "SAVE_AS_16x16"; + public static final String FIND_TEXT_IMAGE = "FIND_TEXT_IMAGE"; + public static final String SEARCH = "SEARCH"; + public static final String STAR_BLUE_IMAGE = "STAR_BLUE_IMAGE"; + public static final String OFFLINE_ICO = "OFFLINE_ICO"; + public static final String SMALL_USER1_MOBILEPHONE = "SMALL_USER1_MOBILEPHONE"; + public static final String LOGIN_DIALOG_LOGIN_TITLE = "LOGIN_DIALOG_LOGIN_TITLE"; + public static final String ERASER_IMAGE = "ERASER_IMAGE"; + public static final String PRINTER_IMAGE_16x16 = "PRINTER_IMAGE_16x16"; + public static final String DOWNLOAD_16x16 = "DOWNLOAD_16x16"; + public static final String EARTH_LOCK_16x16 = "EARTH_LOCK_16x16"; + public static final String LOGIN_DIALOG_AUTHENTICATING = "LOGIN_DIALOG_AUTHENTICATING"; + public static final String HELP2_24x24 = "HELP2_24x24"; + public static final String MAIN_TITLE = "MAIN_TITLE"; + public static final String PROFILE_TAB_TITLE = "PROFILE_TAB_TITLE"; + public static final String CHAT_WORKSPACE = "CHAT_WORKSPACE"; + public static final String GREEN_FLAG_16x16 = "GREEN_FLAG_16x16"; + public static final String MAIN_IMAGE_ICO = "MAIN_IMAGE_ICO"; + public static final String TOOLBAR_BACKGROUND = "TOOLBAR_BACKGROUND"; + public static final String VIEW_IMAGE = "VIEW_IMAGE"; + public static final String CHATTING_CUSTOMER_IMAGE = "CHATTING_CUSTOMER_IMAGE"; + public static final String SMALL_ALARM_CLOCK = "SMALL_ALARM_CLOCK"; + public static final String INFORMATION_IMAGE = "INFORMATION_IMAGE"; + public static final String ACCEPT_CHAT = "ACCEPT_CHAT"; + public static final String SMALL_PIN_BLUE = "SMALL_PIN_BLUE"; + public static final String FONT_16x16 = "FONT_16x16"; + public static final String PAWN_GLASS_RED = "PAWN_GLASS_RED"; + public static final String LOGIN_DIALOG_WORKSPACE = "LOGIN_DIALOG_WORKSPACE"; + public static final String PAWN_GLASS_YELLOW = "PAWN_GLASS_YELLOW"; + public static final String ID_CARD_48x48 = "ID_CARD_48x48"; + public static final String DOWN_ARROW_IMAGE = "DOWN_ARROW_IMAGE"; + public static final String LOGIN_DIALOG_LOGIN = "LOGIN_DIALOG_LOGIN"; + public static final String HISTORY_16x16 = "HISTORY_16x16"; + public static final String SMALL_DOCUMENT_VIEW = "SMALL_DOCUMENT_VIEW"; + public static final String SMALL_AGENT_IMAGE = "SMALL_AGENT_IMAGE"; + public static final String SMALL_ALL_CHATS_IMAGE = "SMALL_ALL_CHATS_IMAGE"; + public static final String SERVER_ICON = "SERVER_ICON"; + public static final String SMALL_USER_ENTER = "SMALL_USER_ENTER"; + public static final String SMALL_CLOSE_BUTTON = "SMALL_CLOSE_BUTTON"; + public static final String ON_PHONE_IMAGE = "ON_PHONE_IMAGE"; + public static final String MINUS_SIGN = "MINUS_SIGN"; + public static final String PAWN_GLASS_GREEN = "PAWN_GLASS_GREEN"; + public static final String COPY_16x16 = "COPY_16x16"; + public static final String SMALL_WORKGROUP_QUEUE_IMAGE = "SMALL_WORKGROUP_QUEUE_IMAGE"; + public static final String CHAT_QUEUE = "CHAT_QUEUE"; + public static final String SEND = "SEND"; + public static final String USER_HEADSET_24x24 = "USER_HEADSET_24x24"; + public static final String BUSY_IMAGE = "BUSY_IMAGE"; + public static final String FUNNEL_DOWN_16x16 = "FUNNEL_DOWN_16x16"; + public static final String PUSH_URL_16x16 = "PUSH_URL_16x16"; + public static final String EARTH_VIEW_16x16 = "EARTH_VIEW_16x16"; + public static final String SMALL_QUESTION = "SMALL_QUESTION"; + public static final String SEND_FILE_ICON = "SEND_FILE_ICON"; + public static final String LOGIN_KEY_IMAGE = "LOGIN_KEY_IMAGE"; + public static final String CREATE_FAQ_ENTRY = "CREATE_FAQ_ENTRY"; + public static final String SPELL_CHECK_IMAGE = "SPELL_CHECK_IMAGE"; + public static final String GREEN_BALL = "GREEN_BALL"; + public static final String SMALL_BUSINESS_MAN_VIEW = "SMALL_BUSINESS_MAN_VIEW"; + public static final String BLANK_24x24 = "BLANK_24x24"; + public static final String USER1_32x32 = "USER1_32x32"; + public static final String DOOR_IMAGE = "DOOR_IMAGE"; + public static final String ALL_CHATS = "ALL_CHATS"; + public static final String SMALL_SCROLL_REFRESH = "SMALL_SCROLL_REFRESH"; + public static final String CO_BROWSER_TAB_TITLE = "CO_BROWSER_TAB_TITLE"; + public static final String PLUS_SIGN = "PLUS_SIGN"; + public static final String FIND_IMAGE = "FIND_IMAGE"; + public static final String USER1_MESSAGE_24x24 = "USER1_MESSAGE_24x24"; + public static final String SMALL_CHECK = "SMALL_CHECK"; + public static final String SEARCH_USER_16x16 = "SEARCH_USER_16x16"; + public static final String LOGIN_DIALOG_PASSWORD = "LOGIN_DIALOG_PASSWORD"; + public static final String TIME_LEFT = "TIME_LEFT"; + public static final String FAQ_TAB_TITLE = "FAQ_TAB_TITLE"; + public static final String ADD_TO_CHAT = "ADD_TO_CHAT"; + public static final String DELETE_BOOKMARK_ICON = "DELETE_BOOKMARK_ICON"; + public static final String FOLDER_CLOSED = "FOLDER_CLOSED"; + public static final String REJECT_CHAT = "REJECT_CHAT"; + public static final String YELLOW_FLAG_16x16 = "YELLOW_FLAG_16x16"; + public static final String ONLINE_ICO = "ONLINE_ICO"; + public static final String LINK_DELETE_16x16 = "LINK_DELETE_16x16"; + public static final String MAIL_FORWARD_16x16 = "MAIL_FORWARD_16x16"; + public static final String TELEPHONE_24x24 = "TELEPHONE_24x24"; + public static final String ADD_LINK_TO_CHAT = "ADD_LINK_TO_CHAT"; + public static final String SMALL_ABOUT_IMAGE = "SMALL_ABOUT_IMAGE"; + public static final String DESKTOP_IMAGE = "DESKTOP_IMAGE"; + public static final String MAIL_16x16 = "MAIL_16x16"; + public static final String MAIL_IMAGE_32x32 = "MAIL_IMAGE_32x32"; + public static final String ADDRESS_BOOK_16x16 = "ADDRESS_BOOK_16x16"; + public static final String YELLOW_BALL = "YELLOW_BALL"; + public static final String ERROR_DIALOG_TITLE = "ERROR_DIALOG_TITLE"; + public static final String REFRESH_IMAGE = "REFRESH_IMAGE"; + public static final String SMALL_ADD_IMAGE = "SMALL_ADD_IMAGE"; + public static final String SEND_FILE_24x24 = "SEND_FILE_24x24"; + public static final String PROFILE_IMAGE_24x24 = "PROFILE_IMAGE_24x24"; + public static final String SMALL_ENTRY = "SMALL_ENTRY"; + public static final String CLEAR_BALL_ICON = "CLEAR_BALL_ICON"; + public static final String CONFERENCE_IMAGE_24x24 = "CONFERENCE_IMAGE_24x24"; + public static final String BACKGROUND_IMAGE = "BACKGROUND_IMAGE"; + public static final String FREE_TO_CHAT_IMAGE = "FREE_TO_CHAT_IMAGE"; + public static final String SOUND_PREFERENCES_IMAGE = "SOUND_PREFERENCES_IMAGE"; + public static final String SPARK_LOGOUT_IMAGE = "SPARK_LOGOUT_IMAGE"; + public static final String PHOTO_IMAGE = "PHOTO_IMAGE"; + public static final String PLUGIN_IMAGE = "PLUGIN_IMAGE"; + public static final String SMALL_PROFILE_IMAGE = "SMALL_PROFILE_IMAGE"; + public static final String CHANGELOG_IMAGE = "CHANGELOG_IMAGE"; + public static final String README_IMAGE = "README_IMAGE"; + public static final String DOWN_OPTION_IMAGE = "DOWN_OPTION_IMAGE"; + public static final String FASTPATH_IMAGE_16x16 = "FASTPATH_IMAGE_16x16"; + public static final String FASTPATH_IMAGE_24x24 = "FASTPATH_IMAGE_24x24"; + public static final String FASTPATH_IMAGE_32x32 = "FASTPATH_IMAGE_32x32"; + public static final String FASTPATH_IMAGE_64x64 = "FASTPATH_IMAGE_64x64"; + public static final String CIRCLE_CHECK_IMAGE = "CIRCLE_CHECK_IMAGE"; + public static final String TRANSFER_IMAGE_24x24 = "TRANSFER_IMAGE_24x24"; + public static final String FASTPATH_OFFLINE_IMAGE_16x16 = "FASTPATH_OFFLINE_IMAGE_16x16"; + public static final String FASTPATH_OFFLINE_IMAGE_24x24 = "FASTPATH_OFFLINE_IMAGE_24x24"; + public static final String USER1_ADD_16x16 = "USER1_ADD_16x16"; + public static final String END_BUTTON_24x24 = "END_BUTTON_24x24"; + public static final String POWERED_BY_IMAGE = "POWERED_BY_IMAGE"; + public static final String STICKY_NOTE_IMAGE = "STICKY_NOTE_IMAGE"; + public static final String HISTORY_24x24_IMAGE = "HISTORY_24x24"; + public static final String PANE_UP_ARROW_IMAGE = "PANE_UP_ARROW_IMAGE"; + public static final String PANE_DOWN_ARROW_IMAGE = "PANE_DOWN_ARROW_IMAGE"; + public static final String CLOSE_DARK_X_IMAGE = "CLOSE_DARK_X_IMAGE"; + public static final String CLOSE_WHITE_X_IMAGE = "CLOSE_WHITE_X_IMAGE"; + + + static ClassLoader cl = SparkRes.class.getClassLoader(); + + static { + prb = (PropertyResourceBundle)ResourceBundle.getBundle("org/jivesoftware/resource/spark"); + } + + public static final String getString(String propertyName) { + return prb.getString(propertyName); + } + + public static final ImageIcon getImageIcon(String imageName) { + try { + final String iconURI = getString(imageName); + final URL imageURL = cl.getResource(iconURI); + return new ImageIcon(imageURL); + } + catch (Exception ex) { + System.out.println(imageName + " not found."); + } + return null; + } + + public static final URL getURL(String propertyName) { + return cl.getResource(getString(propertyName)); + } + + public static void main(String args[]) { + JFrame frame = new JFrame(); + frame.getContentPane().setLayout(new BorderLayout()); + + JEditorPane pane = new JEditorPane(); + frame.getContentPane().add(new JScrollPane(pane)); + + StringBuffer buf = new StringBuffer(); + Enumeration enumeration = prb.getKeys(); + while (enumeration.hasMoreElements()) { + String token = (String)enumeration.nextElement(); + String value = prb.getString(token).toLowerCase(); + if (value.endsWith(".gif") || value.endsWith(".png") || value.endsWith(".jpg") || value.endsWith("jpeg")) { + SparkRes.getImageIcon(token); + } + String str = "public static final String " + token + " = \"" + token + "\";\n"; + buf.append(str); + } + + checkImageDir(); + pane.setText(buf.toString()); + frame.pack(); + frame.setVisible(true); + } + + private static void checkImageDir() { + File[] files = new File("c:\\code\\liveassistant\\client\\resources\\images").listFiles(); + final int no = files != null ? files.length : 0; + for (int i = 0; i < no; i++) { + File imageFile = files[i]; + String name = imageFile.getName(); + + // Check to see if the name of the file exists + boolean exists = false; + Enumeration enumeration = prb.getKeys(); + while (enumeration.hasMoreElements()) { + String token = (String)enumeration.nextElement(); + String value = prb.getString(token); + if (value.endsWith(name)) { + exists = true; + } + } + + if (!exists) { + System.out.println(imageFile.getAbsolutePath() + " is not used."); + } + } + } +} \ No newline at end of file diff --git a/src/java/org/jivesoftware/resource/configuration.properties b/src/java/org/jivesoftware/resource/configuration.properties new file mode 100644 index 00000000..620ce10b --- /dev/null +++ b/src/java/org/jivesoftware/resource/configuration.properties @@ -0,0 +1,16 @@ +# Default configurations for Live Assistant +DEFAULT_APP_RESOURCE_NAME = Live Assistant + +#Images +CHECK_IMAGE = images/check.png +DELETE_IMAGE = images/delete.png +HEADER_FILE = images/header.png + +#Namespaces +PERSONAL_ELEMENT_NAME = private_macros +PERSONAL_NAMESPACE = liveassistant:private + +GLOBAL_ELEMENT_NAME = global_macros +GLOBAL_ELEMENT_NAME = liveassistant:global + +SPELLING_PROPERTIES = spelling/spelling.properties diff --git a/src/java/org/jivesoftware/resource/default.properties b/src/java/org/jivesoftware/resource/default.properties new file mode 100644 index 00000000..51239273 --- /dev/null +++ b/src/java/org/jivesoftware/resource/default.properties @@ -0,0 +1,31 @@ +MAIN_IMAGE = images/spark.png +APPLICATION_NAME = Spark +SHORT_NAME = spark +LOGIN_DIALOG_BACKGROUND_IMAGE = images/login_dialog_background.png +TOP_BOTTOM_BACKGROUND_IMAGE = images/top_bottom_background_image.png +SECONDARY_BACKGROUND_IMAGE = images/secondary_background_image.png + + +# Branding only +BRANDED_IMAGE = +HOST_NAME = +SHOW_POWERED_BY = +CUSTOM = + + +# Roster Management +HOVER_TEXT_COLOR = +TEXT_COLOR = + +CONTACT_GROUP_START_COLOR = +CONTACT_GROUP_END_COLOR = + +TAB_START_COLOR = +TAB_END_COLOR = + +# Proxy Settings +PROXY_HOST = +PROXY_PORT = + +# Account Disabled +ACCOUNT_DISABLED = \ No newline at end of file diff --git a/src/java/org/jivesoftware/resource/emotion.properties b/src/java/org/jivesoftware/resource/emotion.properties new file mode 100644 index 00000000..a99df304 --- /dev/null +++ b/src/java/org/jivesoftware/resource/emotion.properties @@ -0,0 +1,19 @@ +":)" = images/emoticons/happy.gif +:-) = images/emoticons/happy.gif +:( = images/emoticons/sad.gif +:-( = images/emoticons/sad.gif +:D = images/emoticons/grin.gif +:x = images/emoticons/love.gif +;\ = images/emoticons/mischief.gif +B-) = images/emoticons/cool.gif +]:) = images/emoticons/devil.gif +:p = images/emoticons/silly.gif +X-( = images/emoticons/angry.gif +:^0 = images/emoticons/laugh.gif +;) = images/emoticons/wink.gif +;-) = images/emoticons/wink.gif +:8} = images/emoticons/blush.gif +:_| = images/emoticons/cry.gif +?:| = images/emoticons/confused.gif +:0 = images/emoticons/shocked.gif +:| = images/emoticons/plain.gif diff --git a/src/java/org/jivesoftware/resource/sounds.properties b/src/java/org/jivesoftware/resource/sounds.properties new file mode 100644 index 00000000..14b65c5d --- /dev/null +++ b/src/java/org/jivesoftware/resource/sounds.properties @@ -0,0 +1,5 @@ +# List all sound files +INCOMING_USER = sounds/chat_request.wav +TRAY_SHOWING = sounds/bell.wav +OPENING = sounds/opening.wav +CLOSING = sounds/close.wav diff --git a/src/java/org/jivesoftware/resource/spark.properties b/src/java/org/jivesoftware/resource/spark.properties new file mode 100644 index 00000000..3ef8fe17 --- /dev/null +++ b/src/java/org/jivesoftware/resource/spark.properties @@ -0,0 +1,240 @@ +APP_NAME = Spark +VERSION = Version 1.0 Release + +SPARK_IMAGE = images/spark.png +WELCOME = Welcome +ID_CARD_48x48 = images/id_card.png + +# LOGIN DIALOG +LOGIN_DIALOG_LOGIN = &Login +LOGIN_DIALOG_QUIT = &Quit +LOGIN_DIALOG_USERNAME = &Username: +LOGIN_DIALOG_PASSWORD = &Password: +LOGIN_DIALOG_WORKSPACE = &Workgroup: +LOGIN_DIALOG_LOGIN_TITLE = Spark +LOGIN_DIALOG_AUTHENTICATING = Authenticating... + +#MainWindow +MAIN_IMAGE = images/message.png +MAIN_IMAGE_ICO = images/icon_16.ico +SMALL_CHECK = images/smallCheck.png +SMALL_DELETE = images/smallDelete.png +AVAILABLE_USER = images/availableUser.png +AWAY_USER = images/awayUser.png +FIND_IMAGE = images/find.png +SMALL_ADD_IMAGE = images/small_add.png +DOCUMENT_EXCHANGE_IMAGE = images/document_exchange.png +SMALL_DOCUMENT_ADD = images/document_add.png +SMALL_CIRCLE_DELETE = images/small_delete.png +SMALL_DOCUMENT_VIEW = images/document_view.png +SMALL_USER1_MESSAGE = images/user1_message-16x16.png +USER1_MESSAGE_24x24 = images/user1_message-24x24.png +SMALL_USER1_TIME = images/user1_time.png +SMALL_USER1_NEW = images/user1_new.png +SMALL_USER1_STOPWATCH = images/stopwatch_pause.png +SMALL_USER1_MOBILEPHONE = images/user1_mobilephone.png +SMALL_USER1_INFORMATION = images/user1_information.png +SMALL_ENTRY = images/small_entry.gif +SMALL_AGENT_IMAGE = images/small_agent.png +CHATTING_AGENT_IMAGE = images/user1.png +CHATTING_CUSTOMER_IMAGE = images/user2.png +INFORMATION_IMAGE = images/information.png +BLANK_IMAGE = images/blank.gif +USER_HEADSET_24x24 = images/user_headset24.png +FOLDER_CLOSED = images/folder_closed.png +FOLDER = images/folder.png +SMALL_PIN_BLUE = images/pin_blue.png +SMALL_DATA_FIND_IMAGE = images/data_find.png +SMALL_BUSINESS_MAN_VIEW = images/businessman_view.png +SMALL_ALL_AGENTS_IMAGE = images/user1_earth.png +SMALL_WORKGROUP_QUEUE_IMAGE = images/users_into.png +SMALL_ALL_CHATS_IMAGE = images/users_family.png +SMALL_CURRENT_AGENTS = images/users2.png +HELP2_24x24 = images/help_24x24.png +SMALL_ALARM_CLOCK = images/alarmclock.png +SMALL_SCROLL_REFRESH = images/scroll_refresh.png +SMALL_QUESTION = images/help_16x16.png +USER1_32x32 = images/User1_32x32.png +DATA_DELETE_16x16 = images/data_delete.png +DATA_REFRESH_16x16 = images/data_refresh.png +USER1_BACK_16x16 = images/user1_back.png +FONT_16x16 = images/font.png +DOCUMENT_FIND_16x16 = images/document_find.png +DOCUMENT_INFO_32x32 = images/document_info.png +DOCUMENT_16x16 = images/document.png +SMALL_CLOSE_BUTTON = images/deleteitem.gif +COPY_16x16 = images/copy.png +BLANK_24x24 = images/blank_24x24.png +PUSH_URL_16x16 = images/earth_connection-16x16.png +LINK_16x16 = images/link-16x16.png +LINK_DELETE_16x16 = images/link_delete.png +EARTH_LOCK_16x16 = images/earth_lock-16x16.png +LOCK_16x16 = images/lock-16x16.png +LOCK_UNLOCK_16x16 = images/lock_unlock-16x16.png +ONLINE_ICO = images/icon1.ico +OFFLINE_ICO = images/icon2.ico +INFORMATION_ICO = images/icon3.ico +SAVE_AS_16x16 = images/save_as.png +NOTE_EDIT_16x16 = images/note_edit.png +ADDRESS_BOOK_16x16 = images/address_book.png +MEGAPHONE_16x16 = images/megaphone.png +EARTH_VIEW_16x16 = images/earth_view.png +HISTORY_16x16 = images/history.png +DOWNLOAD_16x16 = images/download.png +RED_FLAG_16x16 = images/flag_red.png +GREEN_FLAG_16x16 = images/flag_green.png +YELLOW_FLAG_16x16 = images/flag_yellow.png +FUNNEL_DOWN_16x16 = images/funnel_down.png +MAIL_16x16 = images/mail_16x16.png +MAIL_FORWARD_16x16 = images/mail_forward_16x16.png +MAIL_INTO_16x16 = images/mail_into_16x16.png +RED_BALL = images/red-ball.png +GREEN_BALL = images/green-ball.png +BLUE_BALL = images/blue-ball.png +YELLOW_BALL = images/yellow-ball.png +PAWN_GLASS_GREEN = images/pawn_glass_green.png +PAWN_GLASS_RED = images/pawn_glass_red.png +PAWN_GLASS_WHITE = images/pawn_glass_white.png +PAWN_GLASS_YELLOW = images/pawn_glass_yellow.png +PLUS_SIGN = images/plus-sign.png +MINUS_SIGN = images/minus-sign.png +MAGICIAN_IMAGE = images/magician.png +FIND_TEXT_IMAGE = images/find_text.png +LOGIN_KEY_IMAGE = images/login-key.png +BRICKWALL_IMAGE = images/brickwall.png +MODERATOR_IMAGE = images/moderator.gif +DESKTOP_IMAGE = images/desktop.png +SPELL_CHECK_IMAGE = images/text_ok.png +BUSY_IMAGE = images/busy.gif +CLOSE_IMAGE = images/close.png +VIEW_IMAGE = images/view.png +MOBILE_PHONE_IMAGE = images/mobilephone.png +ON_PHONE_IMAGE = images/on-phone.png +REFRESH_IMAGE = images/refresh.png +CLOSE_WHITE_X_IMAGE = images/close_white.png +CLOSE_DARK_X_IMAGE = images/close_dark.png + + +# Global Values +ERROR_INVALID_WORKGROUP = The workgroup is not valid. Please use a valid workgroup. +ERROR_DIALOG_TITLE = Login Error +INVALID_USERNAME_PASSWORD = Invalid username or password. +SERVER_UNAVAILABLE = Can't connect to server: invalid name or server not reachable. +UNRECOVERABLE_ERROR = Invalid username or password. + +#Chat Window Images + + + +#Chat window text +SEND = &Send +ADD_TO_KB = You can assign any response as either a question or an answer add them directly to the knowledge base for future reference. + +#Kbase Window +GO = &Go +ADD_TO_CHAT = &Add document +ADD_LINK_TO_CHAT = Add &link +VIEW = &View +SEARCH = &Search: + +#Chat Queue +ACCEPT_CHAT = &Accept +REJECT_CHAT = &Refuse +TIME_LEFT = Time left: + +#MainWindow +MAIN_TITLE = Spark +TOOLBOX = Toolbox +CHAT_QUEUE = Chat Queue +CURRENT_CHATS = My Chats +CURRENT_AGENTS = Online Agents +ALL_CHATS = All Chats +WORKGROUP_QUEUE = Queues +CHAT_WORKSPACE = Chat Workspace + +#GroupChat +CREATE_FAQ_ENTRY = Create a new FAQ entry? +CREATE_FAQ_TITLE = Create New FAQ +FAQ_TAB_TITLE = Analyzer +KNOWLEDGE_BASE_TAB_TITLE = Search +PROFILE_TAB_TITLE = Profile +CO_BROWSER_TAB_TITLE = Co-Browser +FORUM_TAB_TITLE = Forums + +#Images +CONFERENCE_IMAGE_24x24 = images/conference_24x24.png +CONFERENCE_IMAGE_16x16 = images/conference_16x16.png +PROFILE_IMAGE_24x24 = images/profile_24x24.png +SEARCH_USER_16x16 = images/search_user_16x16.png +SEND_FILE_24x24 = images/send_file_24x24.png +ADD_IMAGE_24x24 = images/add_24x24.png +TELEPHONE_24x24 = images/telephone_24x24.png +IM_DND = images/im_dnd.png +IM_AWAY = images/im_away.png +MESSAGE_AWAY = images/message_away.png +MESSAGE_DND = images/message_dnd.png +ERASER_IMAGE = images/eraser.gif +TOOLBAR_BACKGROUND = images/toolbar.png +TRAFFIC_LIGHT_IMAGE = images/traffic-light.png +SERVER_ICON = images/server.png +BOOKMARK_ICON = images/bookmark.png +ADD_BOOKMARK_ICON = images/bookmark_add.png +DELETE_BOOKMARK_ICON = images/bookmark_delete.png +CLEAR_BALL_ICON = images/bullet_ball_glass_clear.png +CALL_ICON = images/call.png +PROFILE_ICON = images/profile.png +SEND_FILE_ICON = images/document_into.png +ADD_CONTACT_IMAGE = images/add_contact.png +JOIN_GROUPCHAT_IMAGE = images/join_groupchat.png +DOWN_ARROW_IMAGE = images/down_arrow.gif +PRINTER_IMAGE_16x16 = images/printer.png +SEND_MAIL_IMAGE_16x16 = images/sendmail.png +SEARCH_IMAGE_32x32 = images/search_32x32.png +MAIL_IMAGE_32x32 = images/mail_32x32.png +PREFERENCES_IMAGE = images/preferences.png +NOTEBOOK_IMAGE = images/notebook.png +TEXT_BOLD = images/text_bold.png +TEXT_ITALIC = images/text_italics.png +TEXT_UNDERLINE = images/text_underlined.png +TEXT_NORMAL = images/text_normal.png +SMALL_USER_ENTER = images/smallUserEnter.png +SMALL_USER_DELETE = images/smallUserDelete.png +QUESTIONS_ANSWERS = images/questionsAnswers.png +SMALL_MESSAGE_IMAGE = images/message.png +SMALL_MESSAGE_EDIT_IMAGE = images/message_edit.png +SMALL_ABOUT_IMAGE = images/about.png +DOOR_IMAGE = images/door.gif +STAR_BLUE_IMAGE = images/star_blue.png +STAR_RED_IMAGE = images/star_red.png +STAR_GREY_IMAGE = images/star_grey.png +STAR_YELLOW_IMAGE = images/star_yellow.png +STAR_GREEN_IMAGE = images/star_green.png +LEFT_ARROW_IMAGE = images/arrow_left_green.png +RIGHT_ARROW_IMAGE = images/arrow_right_green.png +BACKGROUND_IMAGE = images/background.png +FREE_TO_CHAT_IMAGE = images/im_free_chat.png +SOUND_PREFERENCES_IMAGE = images/text_loudspeaker.png +SPARK_LOGOUT_IMAGE = images/spark_100.jpg +PHOTO_IMAGE = images/photo_scenery.png +PLUGIN_IMAGE = images/plugin-16x16.gif +SMALL_PROFILE_IMAGE = images/small_profile.png +CHANGELOG_IMAGE = images/doc-changelog-16x16.gif +README_IMAGE = images/doc-readme-16x16.gif +DOWN_OPTION_IMAGE = images/option.png +STICKY_NOTE_IMAGE = images/sticky.png +HISTORY_24x24 = images/history-24x24.png +PANE_UP_ARROW_IMAGE = images/metallic_up.png +PANE_DOWN_ARROW_IMAGE = images/metallic_down.png + +#Fastpath Icons +FASTPATH_IMAGE_16x16 = images/fastpath16.png +FASTPATH_IMAGE_24x24 = images/fastpath24.png +FASTPATH_IMAGE_32x32 = images/fastpath32.png +FASTPATH_IMAGE-64x63 = images/fastpath64.png +CIRCLE_CHECK_IMAGE = images/check.png +TRANSFER_IMAGE_24x24 = images/transfer-24x24.png +FASTPATH_OFFLINE_IMAGE_16x16 = images/fastpath16_offline.png +FASTPATH_OFFLINE_IMAGE_24x24 = images/fastpath24_offline.png +USER1_ADD_16x16 = images/user1_add.png +END_BUTTON_24x24 = images/end_button_24x24.png +POWERED_BY_IMAGE = images/powered_by.png \ No newline at end of file diff --git a/src/java/org/jivesoftware/spark/AlertManager.java b/src/java/org/jivesoftware/spark/AlertManager.java new file mode 100644 index 00000000..8db90a94 --- /dev/null +++ b/src/java/org/jivesoftware/spark/AlertManager.java @@ -0,0 +1,103 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark; + +import org.jivesoftware.spark.util.ModelUtil; + +import java.awt.Window; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * The AlertManager handles the delegation of Alerting based. + * + * @author Derek DeMoro + */ +public class AlertManager { + + private List alerts = new ArrayList(); + + public AlertManager() { + + } + + /** + * Adds an alert. + * + * @param alerter the Alerter to add. + */ + public void addAlert(Alerter alerter) { + alerts.add(alerter); + } + + /** + * Removes an alerter. + * + * @param alerter the alerter to remove. + */ + public void removeAlert(Alerter alerter) { + alerts.remove(alerter); + } + + + /** + * Flash the given window. + * + * @param window the window to flash. + */ + public void flashWindow(Window window) { + final Iterator alertNotifier = ModelUtil.reverseListIterator(alerts.listIterator()); + while (alertNotifier.hasNext()) { + final Alerter alert = (Alerter)alertNotifier.next(); + boolean handle = alert.handleNotification(); + if (handle) { + alert.flashWindow(window); + break; + } + } + } + + /** + * Flash the given window, but stop flashing when the window takes focus. + * + * @param window the window to start flashing. + */ + public void flashWindowStopOnFocus(Window window) { + final Iterator alertNotifiers = ModelUtil.reverseListIterator(alerts.listIterator()); + while (alertNotifiers.hasNext()) { + final Alerter alert = (Alerter)alertNotifiers.next(); + boolean handle = alert.handleNotification(); + if (handle) { + alert.flashWindowStopWhenFocused(window); + break; + } + } + } + + /** + * Stop the flashing of the window. + * + * @param window the window to stop flashing. + */ + public void stopFlashing(Window window) { + final Iterator alertNotifiers = ModelUtil.reverseListIterator(alerts.listIterator()); + while (alertNotifiers.hasNext()) { + final Alerter alert = (Alerter)alertNotifiers.next(); + boolean handle = alert.handleNotification(); + if (handle) { + alert.stopFlashing(window); + break; + } + } + } + +} diff --git a/src/java/org/jivesoftware/spark/Alerter.java b/src/java/org/jivesoftware/spark/Alerter.java new file mode 100644 index 00000000..9bea6ae7 --- /dev/null +++ b/src/java/org/jivesoftware/spark/Alerter.java @@ -0,0 +1,51 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark; + +import java.awt.Window; + +/** + * Implementations of this interface define alert mechanisims based on the Operating System + * Spark is running on. + * + * @author Derek DeMoro + */ +public interface Alerter { + + /** + * Flash the window. + * + * @param window the window to flash. + */ + void flashWindow(Window window); + + /** + * Start the flashing of the given window, but stop flashing when the window takes focus. + * + * @param window the window to start flashing. + */ + void flashWindowStopWhenFocused(Window window); + + /** + * Stop the flashing of the given window. + * + * @param window the window to stop flashing. + */ + void stopFlashing(Window window); + + /** + * Return true if this Alerter should handle the alert request. + * + * @return true to handle. + */ + boolean handleNotification(); + +} diff --git a/src/java/org/jivesoftware/spark/ChatAreaSendField.java b/src/java/org/jivesoftware/spark/ChatAreaSendField.java new file mode 100644 index 00000000..bc00563e --- /dev/null +++ b/src/java/org/jivesoftware/spark/ChatAreaSendField.java @@ -0,0 +1,98 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark; + +import org.jivesoftware.spark.ui.ChatInputEditor; +import org.jivesoftware.spark.util.ResourceUtils; + +import javax.swing.JButton; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTextField; +import javax.swing.UIManager; + +import java.awt.Color; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; + +/** + * Creates a Firefox Search type box that allows for icons inside of a textfield. This + * could be used to build out your own search objects. + */ +public class ChatAreaSendField extends JPanel { + private ChatInputEditor textField; + private JButton button; + + /** + * Creates a new IconTextField with Icon. + * + * @param text the text to use on the button. + */ + public ChatAreaSendField(String text) { + setLayout(new GridBagLayout()); + setBackground((Color)UIManager.get("TextPane.background")); + + textField = new ChatInputEditor(); + textField.setBorder(null); + setBorder(new JTextField().getBorder()); + + button = new JButton(); + + ResourceUtils.resButton(button, text); + + add(button, new GridBagConstraints(1, 0, 1, 1, 0.0, 1.0, GridBagConstraints.EAST, GridBagConstraints.VERTICAL, new Insets(2, 2, 2, 2), 0, 0)); + + final JScrollPane pane = new JScrollPane(textField); + pane.setBorder(null); + add(pane, new GridBagConstraints(0, 0, 1, 1, 1.0, 1.0, GridBagConstraints.WEST, GridBagConstraints.BOTH, new Insets(5, 5, 5, 5), 0, 0)); + button.setEnabled(false); + } + + public JButton getButton() { + return button; + } + + public void enableSendButton(boolean enabled) { + button.setEnabled(enabled); + } + + + /** + * Sets the text of the textfield. + * + * @param text the text. + */ + public void setText(String text) { + textField.setText(text); + } + + /** + * Returns the text inside of the textfield. + * + * @return the text inside of the textfield. + */ + public String getText() { + return textField.getText(); + } + + public ChatInputEditor getChatInputArea() { + return textField; + } + +} + + + + + + + diff --git a/src/java/org/jivesoftware/spark/ChatManager.java b/src/java/org/jivesoftware/spark/ChatManager.java new file mode 100644 index 00000000..ee795ef9 --- /dev/null +++ b/src/java/org/jivesoftware/spark/ChatManager.java @@ -0,0 +1,261 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark; + +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.packet.Message; +import org.jivesoftware.smackx.Form; +import org.jivesoftware.smackx.muc.MultiUserChat; +import org.jivesoftware.spark.ui.ChatContainer; +import org.jivesoftware.spark.ui.ChatRoom; +import org.jivesoftware.spark.ui.ChatRoomListener; +import org.jivesoftware.spark.ui.ChatRoomNotFoundException; +import org.jivesoftware.spark.ui.ContactItem; +import org.jivesoftware.spark.ui.ContactList; +import org.jivesoftware.spark.ui.MessageFilter; +import org.jivesoftware.spark.ui.conferences.RoomInvitationListener; +import org.jivesoftware.spark.ui.rooms.ChatRoomImpl; +import org.jivesoftware.spark.ui.rooms.GroupChatRoom; +import org.jivesoftware.spark.util.log.Log; +import org.jivesoftware.sparkimpl.preference.chat.ChatPreference; +import org.jivesoftware.sparkimpl.preference.chat.ChatPreferences; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; + +/** + * Handles the Chat Management of each individual Workspace. The ChatManager is responsible + * for creation and removal of chat rooms, transcripts, and transfers and room invitations. + */ +public class ChatManager { + private List messageFilters = new ArrayList(); + private final ChatContainer chatContainer; + private List invitationListeners = new ArrayList(); + private String conferenceService; + + /** + * Create a new instance of ChatManager. + */ + public ChatManager() { + chatContainer = new ChatContainer(); + } + + + /** + * Used to listen for rooms opening, closing or being + * activated( already opened, but tabbed to ) + * + * @param listener the ChatRoomListener to add + */ + public void addChatRoomListener(ChatRoomListener listener) { + getChatContainer().addChatRoomListener(listener); + } + + /** + * Simplace facade for chatroom. Removes a listener + * + * @param listener the ChatRoomListener to remove + */ + public void removeChatRoomListener(ChatRoomListener listener) { + getChatContainer().removeChatRoomListener(listener); + } + + + /** + * Removes the personal 1 to 1 chat from the ChatFrame. + * + * @param chatRoom the ChatRoom to remove. + */ + public void removeChat(ChatRoom chatRoom) { + chatContainer.closeTab(chatRoom); + } + + + /** + * Returns all ChatRooms currently active. + * + * @return all ChatRooms. + */ + public ChatContainer getChatContainer() { + return chatContainer; + } + + /** + * Returns the MultiUserChat associated with the specified roomname. + * + * @param roomName the name of the chat room. + * @return the MultiUserChat found for that room. + */ + public GroupChatRoom getGroupChat(String roomName) throws ChatNotFoundException { + Iterator iter = getChatContainer().getAllChatRooms(); + while (iter.hasNext()) { + ChatRoom chatRoom = (ChatRoom)iter.next(); + if (chatRoom instanceof GroupChatRoom) { + GroupChatRoom groupChat = (GroupChatRoom)chatRoom; + if (groupChat.getRoomname().equals(roomName)) { + return groupChat; + } + } + + } + + throw new ChatNotFoundException("Could not locate Group Chat Room - " + roomName); + } + + + /** + * Creates and/or opens a chat room with the specified user. + * + * @param userJID the jid of the user to chat with. + * @param nickname the nickname to use for the user. + */ + public ChatRoom createChatRoom(String userJID, String nickname, String title) { + ChatRoom chatRoom = null; + try { + chatRoom = getChatContainer().getChatRoom(userJID); + } + catch (ChatRoomNotFoundException e) { + chatRoom = new ChatRoomImpl(userJID, nickname, title); + getChatContainer().addChatRoom(chatRoom); + } + + return chatRoom; + } + + /** + * Returns the ChatRoom for the giving jid. If the ChatRoom is not found, + * a new ChatRoom will be created. + * + * @param jid the jid of the user to chat with. + * @return the ChatRoom. + */ + public ChatRoom getChatRoom(String jid) { + ChatRoom chatRoom = null; + try { + chatRoom = getChatContainer().getChatRoom(jid); + } + catch (ChatRoomNotFoundException e) { + ContactList contactList = SparkManager.getWorkspace().getContactList(); + ContactItem item = contactList.getContactItemByJID(jid); + + String nickname = item.getNickname(); + chatRoom = new ChatRoomImpl(jid, nickname, nickname); + getChatContainer().addChatRoom(chatRoom); + } + + return chatRoom; + } + + + /** + * Creates a new public Conference Room. + * + * @param roomName the name of the room. + * @param serviceName the service name to use (ex.conference.jivesoftware.com) + * @return the new ChatRoom created. If an error occured, null will be returned. + */ + public ChatRoom createConferenceRoom(String roomName, String serviceName) { + final MultiUserChat chatRoom = new MultiUserChat(SparkManager.getConnection(), roomName + "@" + serviceName); + + final GroupChatRoom room = new GroupChatRoom(chatRoom); + + try { + ChatPreferences chatPref = (ChatPreferences)SparkManager.getPreferenceManager().getPreferenceData(ChatPreference.NAMESPACE); + chatRoom.create(chatPref.getNickname()); + + // Send an empty room configuration form which indicates that we want + // an instant room + chatRoom.sendConfigurationForm(new Form(Form.TYPE_SUBMIT)); + } + catch (XMPPException e1) { + Log.error("Unable to send conference room chat configuration form.", e1); + return null; + } + + getChatContainer().addChatRoom(room); + return room; + } + + /** + * Adds a new MessageFilter. + * + * @param filter the MessageFilter. + */ + public void addMessageFilter(MessageFilter filter) { + messageFilters.add(filter); + } + + /** + * Removes a MessageFilter. + * + * @param filter the MessageFilter. + */ + public void removeMessageFilter(MessageFilter filter) { + messageFilters.remove(filter); + } + + /** + * Returns a Collection of MessageFilters registered to Spark. + * + * @return the Collection of MessageFilters. + */ + public Collection getMessageFilters() { + return messageFilters; + } + + public void filterIncomingMessage(Message message) { + // Fire Message Filters + final ChatManager chatManager = SparkManager.getChatManager(); + Iterator filters = chatManager.getMessageFilters().iterator(); + while (filters.hasNext()) { + ((MessageFilter)filters.next()).filterIncoming(message); + } + } + + public void filterOutgoingMessage(Message message) { + // Fire Message Filters + final ChatManager chatManager = SparkManager.getChatManager(); + Iterator filters = chatManager.getMessageFilters().iterator(); + while (filters.hasNext()) { + ((MessageFilter)filters.next()).filterOutgoing(message); + } + } + + public void addInvitationListener(RoomInvitationListener listener) { + invitationListeners.add(listener); + } + + public void removeInvitationListener(RoomInvitationListener listener) { + invitationListeners.remove(listener); + } + + public Collection getInvitationListeners() { + return invitationListeners; + } + + public String getDefaultConferenceService() { + if (conferenceService == null) { + try { + Collection col = MultiUserChat.getServiceNames(SparkManager.getConnection()); + if (col.size() > 0) { + conferenceService = (String)col.iterator().next(); + } + } + catch (XMPPException e) { + Log.error(e); + } + } + + return conferenceService; + } +} \ No newline at end of file diff --git a/src/java/org/jivesoftware/spark/ChatNotFoundException.java b/src/java/org/jivesoftware/spark/ChatNotFoundException.java new file mode 100644 index 00000000..c1bf3ac2 --- /dev/null +++ b/src/java/org/jivesoftware/spark/ChatNotFoundException.java @@ -0,0 +1,29 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark; + + +/** + * Thrown when a Chat was not found. + * + * @author Derek DeMoro + */ +public class ChatNotFoundException extends Exception { + + public ChatNotFoundException() { + super(); + } + + public ChatNotFoundException(String msg) { + super(msg); + } + +} diff --git a/src/java/org/jivesoftware/spark/PluginManager.java b/src/java/org/jivesoftware/spark/PluginManager.java new file mode 100644 index 00000000..baf875c9 --- /dev/null +++ b/src/java/org/jivesoftware/spark/PluginManager.java @@ -0,0 +1,604 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark; + +import org.dom4j.Document; +import org.dom4j.DocumentException; +import org.dom4j.Element; +import org.dom4j.io.SAXReader; +import org.jivesoftware.MainWindowListener; +import org.jivesoftware.Spark; +import org.jivesoftware.spark.plugin.Plugin; +import org.jivesoftware.spark.plugin.PluginClassLoader; +import org.jivesoftware.spark.plugin.PublicPlugin; +import org.jivesoftware.spark.util.URLFileSystem; +import org.jivesoftware.spark.util.log.Log; + +import javax.swing.SwingUtilities; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.FilenameFilter; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.StringTokenizer; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.zip.ZipFile; + +/** + * This manager is responsible for the loading of all Plugins and Workspaces within Spark environment. + * + * @author Derek DeMoro + */ +public class PluginManager implements MainWindowListener { + private final List plugins = new LinkedList(); + private final List publicPlugins = new ArrayList(); + private static PluginManager singleton; + private static final Object LOCK = new Object(); + + /** + * The root Plugins Directory. + */ + public static File PLUGINS_DIRECTORY = new File(Spark.getBinDirectory().getParent(), "plugins").getAbsoluteFile(); + + private PluginClassLoader classLoader; + + + /** + * Returns the singleton instance of PluginManager, + * creating it if necessary. + *

    + * + * @return the singleton instance of PluginManager + */ + public static PluginManager getInstance() { + // Synchronize on LOCK to ensure that we don't end up creating + // two singletons. + synchronized (LOCK) { + if (null == singleton) { + PluginManager controller = new PluginManager(); + singleton = controller; + return controller; + } + } + return singleton; + } + + private PluginManager() { + SparkManager.getMainWindow().addMainWindowListener(this); + + // Create the extension directory if one does not exist. + if (!PLUGINS_DIRECTORY.exists()) { + PLUGINS_DIRECTORY.mkdirs(); + } + } + + /** + * Loads all {@link Plugin} from the agent plugins.xml and extension lib. + */ + public void loadPlugins() { + // Delete all old plugins + File[] oldFiles = PLUGINS_DIRECTORY.listFiles(); + final int no = oldFiles != null ? oldFiles.length : 0; + for (int i = 0; i < no; i++) { + File file = oldFiles[i]; + if (file.isDirectory()) { + // Check to see if it has an associated .jar + File jarFile = new File(PLUGINS_DIRECTORY, file.getName() + ".jar"); + if (!jarFile.exists()) { + uninstall(file); + } + } + } + + updateClasspath(); + + // At the moment, the plug list is hardcode internally until I begin + // using external property files. All depends on deployment. + final URL url = getClass().getClassLoader().getResource("META-INF/plugins.xml"); + try { + InputStreamReader reader = new InputStreamReader(url.openStream()); + loadInternalPlugins(reader); + } + catch (IOException e) { + Log.error("Could not load plugins.xml file."); + } + + // Load extension plugins + loadPublicPlugins(); + + // For development purposes, load the plugin specified by -Dplugin=... + String plugin = System.getProperty("plugin"); + if (plugin != null) { + StringTokenizer st = new StringTokenizer(plugin, ", ", false); + while (st.hasMoreTokens()) { + String token = st.nextToken(); + File pluginXML = new File(token); + loadPublicPlugin(pluginXML.getParentFile()); + } + } + } + + /** + * Loads public plugins. + * + * @param pluginDir the directory of the expanded public plugin. + * @return the new Plugin model for the Public Plugin. + */ + private Plugin loadPublicPlugin(File pluginDir) { + File pluginFile = new File(pluginDir, "plugin.xml"); + SAXReader saxReader = new SAXReader(); + Document pluginXML = null; + try { + pluginXML = saxReader.read(pluginFile); + } + catch (DocumentException e) { + Log.error(e); + } + + Plugin pluginClass = null; + + List plugins = pluginXML.selectNodes("/plugin"); + Iterator iter = plugins.iterator(); + while (iter.hasNext()) { + PublicPlugin publicPlugin = new PublicPlugin(); + + String clazz = null; + String name = null; + try { + Element plugin = (Element)iter.next(); + + String minSparkVersion = null; + try { + minSparkVersion = plugin.selectSingleNode("minSparkVersion").getText(); + } + catch (Exception e) { + return null; + } + + + name = plugin.selectSingleNode("name").getText(); + clazz = plugin.selectSingleNode("class").getText(); + publicPlugin.setPluginClass(clazz); + publicPlugin.setName(name); + + try { + String version = plugin.selectSingleNode("version").getText(); + publicPlugin.setVersion(version); + + String author = plugin.selectSingleNode("author").getText(); + publicPlugin.setAuthor(author); + + String email = plugin.selectSingleNode("email").getText(); + publicPlugin.setEmail(email); + + String description = plugin.selectSingleNode("description").getText(); + publicPlugin.setDescription(description); + + String homePage = plugin.selectSingleNode("homePage").getText(); + publicPlugin.setHomePage(homePage); + } + catch (Exception e) { + Log.debug("We can ignore these."); + } + + + try { + pluginClass = (Plugin)getParentClassLoader().loadClass(clazz).newInstance(); + Log.debug(name + " has been loaded."); + publicPlugin.setPluginDir(pluginDir); + publicPlugins.add(publicPlugin); + + + registerPlugin(pluginClass); + } + catch (Exception e) { + Log.error("Unable to load plugin " + clazz + ".", e); + } + } + catch (Exception ex) { + Log.error("Unable to load plugin " + clazz + ".", ex); + } + + + } + + return pluginClass; + } + + private void loadInternalPlugins(InputStreamReader reader) { + SAXReader saxReader = new SAXReader(); + Document pluginXML = null; + try { + pluginXML = saxReader.read(reader); + } + catch (DocumentException e) { + Log.error(e); + } + List plugins = pluginXML.selectNodes("/plugins/plugin"); + Iterator iter = plugins.iterator(); + while (iter.hasNext()) { + + + String clazz = null; + String name = null; + try { + Element plugin = (Element)iter.next(); + + + name = plugin.selectSingleNode("name").getText(); + clazz = plugin.selectSingleNode("class").getText(); + + + Plugin pluginClass = (Plugin)Class.forName(clazz).newInstance(); + Log.debug(name + " has been loaded. Internal plugin."); + + registerPlugin(pluginClass); + } + catch (Exception ex) { + Log.error("Unable to load plugin " + clazz + ".", ex); + } + } + } + + private void updateClasspath() { + try { + classLoader = new PluginClassLoader(getParentClassLoader(), PLUGINS_DIRECTORY); + } + catch (MalformedURLException e) { + Log.error("Error updating classpath.", e); + } + Thread.currentThread().setContextClassLoader(classLoader); + } + + /** + * Returns the plugin classloader. + * + * @return the plugin classloader. + */ + public ClassLoader getPluginClassLoader() { + return classLoader; + } + + /** + * Registers a plugin. + * + * @param plugin the plugin to register. + */ + public void registerPlugin(Plugin plugin) { + plugins.add(plugin); + } + + /** + * Removes a plugin from the plugin list. + * + * @param plugin the plugin to remove. + */ + public void removePlugin(Plugin plugin) { + plugins.remove(plugin); + } + + /** + * Returns a Collection of Plugins. + * + * @return a Collection of Plugins. + */ + public Collection getPlugins() { + return plugins; + } + + /** + * Returns the instance of the plugin class initialized during startup. + * + * @param communicatorPlugin the plugin to find. + * @return the instance of the plugin. + */ + public Plugin getPlugin(Class communicatorPlugin) { + Iterator iter = getPlugins().iterator(); + while (iter.hasNext()) { + Plugin plugin = (Plugin)iter.next(); + if (plugin.getClass() == communicatorPlugin) { + return plugin; + } + } + return null; + } + + /** + * Loads and initalizes all Plugins. + * + * @see Plugin + */ + public void initializePlugins() { + SwingUtilities.invokeLater(new Runnable() { + public void run() { + final Iterator iter = plugins.iterator(); + while (iter.hasNext()) { + long start = System.currentTimeMillis(); + Plugin plugin = (Plugin)iter.next(); + Log.debug("Trying to initialize " + plugin); + plugin.initialize(); + long end = System.currentTimeMillis(); + Log.debug("Took " + (end - start) + " ms. to load " + plugin); + } + } + }); + + } + + public void shutdown() { + final Iterator pluginIter = plugins.iterator(); + while (pluginIter.hasNext()) { + Plugin plugin = (Plugin)pluginIter.next(); + try { + plugin.shutdown(); + } + catch (Exception e) { + Log.warning("Exception on shutdown of plugin.", e); + } + } + } + + public void mainWindowActivated() { + } + + public void mainWindowDeactivated() { + } + + /** + * Locates the best class loader based on context (see class description). + * + * @return The best parent classloader to use + */ + private ClassLoader getParentClassLoader() { + ClassLoader parent = Thread.currentThread().getContextClassLoader(); + if (parent == null) { + parent = this.getClass().getClassLoader(); + if (parent == null) { + parent = ClassLoader.getSystemClassLoader(); + } + } + return parent; + } + + private void expandNewPlugins() { + File[] jars = PLUGINS_DIRECTORY.listFiles(new FilenameFilter() { + public boolean accept(File dir, String name) { + boolean accept = false; + String smallName = name.toLowerCase(); + if (smallName.endsWith(".jar")) { + accept = true; + } + return accept; + } + }); + + // Do nothing if no jar or zip files were found + if (jars == null) { + return; + } + + + for (int i = 0; i < jars.length; i++) { + if (jars[i].isFile()) { + File file = jars[i]; + + URL url = null; + try { + url = file.toURL(); + } + catch (MalformedURLException e) { + Log.error(e); + } + String name = URLFileSystem.getName(url); + File directory = new File(PLUGINS_DIRECTORY, name); + if (directory.exists() && directory.isDirectory()) { + // Check to see if directory contains the plugin.xml file. + // If not, delete directory. + File pluginXML = new File(directory, "plugin.xml"); + if (pluginXML.exists()) { + if (pluginXML.lastModified() < file.lastModified()) { + uninstall(directory); + unzipPlugin(file, directory); + return; + } + continue; + } + + uninstall(directory); + } + else { + // Unzip contents into directory + unzipPlugin(file, directory); + } + } + } + } + + private void loadPublicPlugins() { + // First, expand all plugins that have yet to be expanded. + expandNewPlugins(); + + + File[] files = PLUGINS_DIRECTORY.listFiles(new FilenameFilter() { + public boolean accept(File dir, String name) { + return dir.isDirectory(); + } + }); + + // Do nothing if no jar or zip files were found + if (files == null) { + return; + } + + for (int i = 0; i < files.length; i++) { + File file = files[i]; + + File pluginXML = new File(file, "plugin.xml"); + if (pluginXML.exists()) { + try { + classLoader.addPlugin(file); + } + catch (MalformedURLException e) { + Log.error("Unable to load dirs", e); + } + + loadPublicPlugin(file); + } + + + } + } + + /** + * Adds and installs a new plugin into Spark. + * + * @param plugin the plugin to install. + * @throws Exception thrown if there was a problem loading the plugin. + */ + public void addPlugin(PublicPlugin plugin) throws Exception { + expandNewPlugins(); + + URL url = new URL(plugin.getDownloadURL()); + String name = URLFileSystem.getName(url); + File pluginDownload = new File(PluginManager.PLUGINS_DIRECTORY, name); + + classLoader.addPlugin(pluginDownload); + Plugin pluginClass = loadPublicPlugin(pluginDownload); + Log.debug("Trying to initialize " + pluginClass); + pluginClass.initialize(); + + } + + /** + * Unzips a plugin from a JAR file into a directory. If the JAR file + * isn't a plugin, this method will do nothing. + * + * @param file the JAR file + * @param dir the directory to extract the plugin to. + */ + private void unzipPlugin(File file, File dir) { + try { + ZipFile zipFile = new JarFile(file); + // Ensure that this JAR is a plugin. + if (zipFile.getEntry("plugin.xml") == null) { + return; + } + dir.mkdir(); + for (Enumeration e = zipFile.entries(); e.hasMoreElements();) { + JarEntry entry = (JarEntry)e.nextElement(); + File entryFile = new File(dir, entry.getName()); + // Ignore any manifest.mf entries. + if (entry.getName().toLowerCase().endsWith("manifest.mf")) { + continue; + } + if (!entry.isDirectory()) { + entryFile.getParentFile().mkdirs(); + FileOutputStream out = new FileOutputStream(entryFile); + InputStream zin = zipFile.getInputStream(entry); + byte[] b = new byte[512]; + int len = 0; + while ((len = zin.read(b)) != -1) { + out.write(b, 0, len); + } + out.flush(); + out.close(); + zin.close(); + } + } + zipFile.close(); + zipFile = null; + } + catch (Exception e) { + Log.warning("Error unzipping plugin", e); + } + } + + /** + * Returns a collection of all public plugins. + * + * @return the collection of public plugins. + */ + public List getPublicPlugins() { + return publicPlugins; + } + + private void uninstall(File pluginDir) { + File[] files = pluginDir.listFiles(); + for (int i = 0; i < files.length; i++) { + File f = files[i]; + if (f.isFile()) { + f.delete(); + } + } + + File libDir = new File(pluginDir, "lib"); + + File[] libs = libDir.listFiles(); + final int no = libs != null ? libs.length : 0; + for (int i = 0; i < no; i++) { + File f = libs[i]; + f.delete(); + } + + libDir.delete(); + + pluginDir.delete(); + } + + /** + * Removes and uninstall a plugin from Spark. + * + * @param plugin the plugin to uninstall. + */ + public void removePlugin(PublicPlugin plugin) { + PluginManager pluginManager = PluginManager.getInstance(); + List plugins = pluginManager.getPublicPlugins(); + Iterator iter = new ArrayList(plugins).iterator(); + while (iter.hasNext()) { + PublicPlugin installedPlugin = (PublicPlugin)iter.next(); + if (plugin.getName().equals(installedPlugin.getName())) { + plugins.remove(installedPlugin); + } + } + } + + /** + * Returns true if the specified plugin is installed. + * + * @param plugin the plugin to check. + * @return true if installed. + */ + public static boolean isInstalled(PublicPlugin plugin) { + PluginManager pluginManager = PluginManager.getInstance(); + List plugins = pluginManager.getPublicPlugins(); + Iterator iter = plugins.iterator(); + while (iter.hasNext()) { + PublicPlugin installedPlugin = (PublicPlugin)iter.next(); + if (plugin.getName().equals(installedPlugin.getName())) { + return true; + } + } + return false; + } + + +} \ No newline at end of file diff --git a/src/java/org/jivesoftware/spark/SessionManager.java b/src/java/org/jivesoftware/spark/SessionManager.java new file mode 100644 index 00000000..a989734b --- /dev/null +++ b/src/java/org/jivesoftware/spark/SessionManager.java @@ -0,0 +1,358 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark; + +import org.jdesktop.jdic.systeminfo.SystemInfo; +import org.jivesoftware.Spark; +import org.jivesoftware.smack.ConnectionListener; +import org.jivesoftware.smack.XMPPConnection; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.packet.Presence; +import org.jivesoftware.smack.packet.StreamError; +import org.jivesoftware.smack.provider.ProviderManager; +import org.jivesoftware.smack.util.StringUtils; +import org.jivesoftware.smackx.PrivateDataManager; +import org.jivesoftware.smackx.ServiceDiscoveryManager; +import org.jivesoftware.smackx.packet.DiscoverItems; +import org.jivesoftware.spark.ui.ChatRoom; +import org.jivesoftware.spark.ui.PresenceListener; +import org.jivesoftware.spark.ui.status.StatusItem; +import org.jivesoftware.spark.util.log.Log; +import org.jivesoftware.sparkimpl.plugin.manager.Features; +import org.jivesoftware.sparkimpl.settings.local.LocalPreferences; +import org.jivesoftware.sparkimpl.settings.local.SettingsManager; + +import javax.swing.SwingUtilities; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Timer; +import java.util.TimerTask; + +/** + * This manager is responsible for the handling of the XMPPConnection used within Spark. This is used + * for the changing of the users presence, the handling of connection errors and the ability to add + * presence listeners and retrieve the connection used in Spark. + * + * @author Derek DeMoro + */ +public final class SessionManager implements ConnectionListener { + private XMPPConnection connection; + private PrivateDataManager personalDataManager; + + private String serverAddress; + private String username; + private String password; + + private String JID; + + private List presenceListeners = new ArrayList(); + + private String userBareAddress; + private boolean unavaliable = false; + private DiscoverItems discoverItems; + + + public SessionManager() { + } + + /** + * Initializes session. + * + * @param connection the XMPPConnection used in this session. + * @param username the agents username. + * @param password the agents password. + */ + public void initializeSession(XMPPConnection connection, String username, String password) { + this.connection = connection; + this.username = username; + this.password = password; + this.userBareAddress = StringUtils.parseBareAddress(connection.getUser()); + + // create workgroup session + personalDataManager = new PrivateDataManager(getConnection()); + + LocalPreferences localPref = SettingsManager.getLocalPreferences(); + // Start Idle listener + if (localPref.isIdleOn()) { + int delay = localPref.getSecondIdleTime() * 60000; + if (Spark.isWindows()) { + setIdleListener(delay); + } + } + + // Discover items + discoverItems(); + + ProviderManager.addExtensionProvider("event", "http://jabber.org/protocol/disco#info", new Features.Provider()); + } + + /** + * Does the initial service discovery. + */ + private void discoverItems() { + ServiceDiscoveryManager disco = ServiceDiscoveryManager.getInstanceFor(SparkManager.getConnection()); + try { + discoverItems = disco.discoverItems(SparkManager.getConnection().getServiceName()); + } + catch (XMPPException e) { + Log.error(e); + } + } + + /** + * Returns the XMPPConnection used for this session. + * + * @return the XMPPConnection used for this session. + */ + public XMPPConnection getConnection() { + return connection; + } + + + /** + * Returns the PrivateDataManager responsible for handling all private data for individual + * agents. + * + * @return the PrivateDataManager responsible for handling all private data for individual + * agents. + */ + public PrivateDataManager getPersonalDataManager() { + return personalDataManager; + } + + + /** + * Returns the host for this connection. + * + * @return the connection host. + */ + public String getServerAddress() { + return serverAddress; + } + + /** + * Set the server address + * + * @param address the address of the server. + */ + public void setServerAddress(String address) { + this.serverAddress = address; + } + + /** + * Notify agent the connection was closed due to an exception. + * + * @param ex the Exception that took place. + */ + public void connectionClosedOnError(final Exception ex) { + SwingUtilities.invokeLater(new Runnable() { + public void run() { + Log.error("Connection closed on error.", ex); + + String message = "Your connection was closed due to an error."; + + if (ex instanceof XMPPException) { + XMPPException xmppEx = (XMPPException)ex; + StreamError error = xmppEx.getStreamError(); + String reason = error.getCode(); + if ("conflict".equals(reason)) { + message = "Your connection was closed due to the same user logging in from another location."; + } + } + + Collection rooms = SparkManager.getChatManager().getChatContainer().getChatRooms(); + Iterator iter = rooms.iterator(); + while (iter.hasNext()) { + ChatRoom chatRoom = (ChatRoom)iter.next(); + chatRoom.getChatInputEditor().setEnabled(false); + chatRoom.getSendButton().setEnabled(false); + chatRoom.getTranscriptWindow().insertNotificationMessage(message); + } + + + } + }); + } + + /** + * Notify agent that the connection has been closed. + */ + public void connectionClosed() { + connectionClosedOnError(null); + } + + /** + * Return the username associated with this session. + * + * @return the username associated with this session. + */ + public String getUsername() { + return username; + } + + /** + * Return the password associated with this session. + * + * @return the password assoicated with this session. + */ + public String getPassword() { + return password; + } + + /** + * Update the current availability of the user + * + * @param presence the current presence of the user. + */ + public void changePresence(Presence presence) { + // Send Presence Packet + SparkManager.getConnection().sendPacket(presence); + + // Update Tray Icon + SparkManager.getNotificationsEngine().changePresence(presence); + + // Fire Presence Listeners + final Iterator presenceListeners = new ArrayList(this.presenceListeners).iterator(); + while (presenceListeners.hasNext()) { + ((PresenceListener)presenceListeners.next()).presenceChanged(presence); + } + } + + /** + * Returns the jid of the Spark user. + * + * @return the jid of the Spark user. + */ + public String getJID() { + return JID; + } + + /** + * Sets the jid of the current Spark user. + * + * @param jid the jid of the current Spark user. + */ + public void setJID(String jid) { + this.JID = jid; + } + + /** + * Adds a PresenceListener to Spark. PresenceListener's are used + * to allow notification of when the Spark users changes their presence. + * + * @param listener the listener. + */ + public void addPresenceListener(PresenceListener listener) { + presenceListeners.add(listener); + } + + /** + * Remove a PresenceListener from Spark. + * + * @param listener the listener. + */ + public void removePresenceListener(PresenceListener listener) { + presenceListeners.remove(listener); + } + + /** + * Returns the users bare address. A bare-address is the address without a resource (ex. derek@jivesoftware.com/spark would + * be derek@jivesoftware.com) + * + * @return the users bare address. + */ + public String getBareAddress() { + return userBareAddress; + } + + /** + * Sets the Idle Timeout for this instance of Spark. + * + * @param mill the timeout value in milliseconds. + */ + private void setIdleListener(final long mill) { + + final Timer timer = new Timer(); + + timer.scheduleAtFixedRate(new TimerTask() { + public void run() { + long idleTime = SystemInfo.getSessionIdleTime(); + boolean isLocked = SystemInfo.isSessionLocked(); + if (idleTime > mill) { + try { + // Handle if spark is not connected to the server. + if (SparkManager.getConnection() == null || !SparkManager.getConnection().isConnected()) { + return; + } + + // Change Status + Workspace workspace = SparkManager.getWorkspace(); + Presence presence = workspace.getStatusBar().getPresence(); + if (workspace != null && presence.getMode() == Presence.Mode.AVAILABLE) { + unavaliable = true; + StatusItem away = workspace.getStatusBar().getStatusItem("Away"); + Presence p = away.getPresence(); + if (isLocked) { + p.setStatus("User has locked their workstation."); + } + else { + p.setStatus("Away due to idle."); + } + SparkManager.getSessionManager().changePresence(p); + } + } + catch (Exception e) { + Log.error("Error with IDLE status.", e); + timer.cancel(); + } + } + else { + if (unavaliable) { + setAvailableIfActive(); + } + } + } + }, 1000, 1000); + } + + private void setAvailableIfActive() { + + // Handle if spark is not connected to the server. + if (SparkManager.getConnection() == null || !SparkManager.getConnection().isConnected()) { + return; + } + + // Change Status + Workspace workspace = SparkManager.getWorkspace(); + if (workspace != null) { + Presence presence = workspace.getStatusBar().getStatusItem("Online").getPresence(); + SparkManager.getSessionManager().changePresence(presence); + unavaliable = false; + } + } + + /** + * Returns the Discovered Items. + * + * @return the discovered items found on startup. + */ + public DiscoverItems getDiscoveredItems() { + return discoverItems; + } + + public void setConnection(XMPPConnection con) { + this.connection = con; + } + +} \ No newline at end of file diff --git a/src/java/org/jivesoftware/spark/SoundManager.java b/src/java/org/jivesoftware/spark/SoundManager.java new file mode 100644 index 00000000..5adac831 --- /dev/null +++ b/src/java/org/jivesoftware/spark/SoundManager.java @@ -0,0 +1,134 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark; + +import org.jivesoftware.resource.SoundsRes; +import org.jivesoftware.spark.util.log.Log; + +import java.applet.Applet; +import java.applet.AudioClip; +import java.io.File; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.HashMap; +import java.util.Map; + +/** + * This manager is responsible for the playing, stopping and caching of sounds within Spark. You would + * use this manager when you wish to play audio without adding too much memory overhead. + */ +public class SoundManager { + + private final Map clipMap = new HashMap(); + private final Map fileMap = new HashMap(); + + /** + * Default constructor + */ + public SoundManager() { + } + + /** + * Plays an audio clip of local clips deployed with Spark. + * + * @param clip the properties value found in la.properties. + * @return the AudioClip found. If no audio clip was found, returns null. + */ + public AudioClip getClip(String clip) { + if (!clipMap.containsKey(clip)) { + // Add new clip + final AudioClip newClip = loadClipForURL(clip); + if (newClip != null) { + clipMap.put(clip, newClip); + } + } + + return (AudioClip)clipMap.get(clip); + } + + /** + * Plays an AudioClip. + * + * @param clip the audioclip to play. + */ + public void playClip(AudioClip clip) { + try { + clip.play(); + } + catch (Exception ex) { + System.err.println("Unable to load sound file"); + } + } + + /** + * Plays an AudioClip. + * + * @param clipToPlay the properties value found in la.properties. + */ + public void playClip(String clipToPlay) { + AudioClip clip = getClip(clipToPlay); + try { + clip.play(); + } + catch (Exception ex) { + System.err.println("Unable to load sound file"); + } + } + + /** + * Plays a sound file. + * + * @param soundFile the File object representing the wav file. + */ + public void playClip(final File soundFile) { + Thread soundThread = new Thread(new Runnable() { + public void run() { + try { + final URL url = soundFile.toURL(); + AudioClip ac = (AudioClip)fileMap.get(url); + if (ac == null) { + ac = Applet.newAudioClip(url); + fileMap.put(url, ac); + } + ac.play(); + } + catch (MalformedURLException e) { + Log.error(e); + } + } + }); + + soundThread.start(); + } + + /** + * Creates an AudioClip from a URL. + * + * @param clipOfURL the url of the AudioClip to play. We only support .wav files at the moment. + * @return the AudioFile found. If no audio file was found,returns null. + */ + private AudioClip loadClipForURL(String clipOfURL) { + final URL url = SoundsRes.getURL(clipOfURL); + AudioClip clip = null; + + try { + clip = Applet.newAudioClip(url); + + } + catch (Exception e) { + Log.error("Unable to load sound url: " + url + "\n\t: " + e); + } + + return clip; + } + + +} \ No newline at end of file diff --git a/src/java/org/jivesoftware/spark/SparkManager.java b/src/java/org/jivesoftware/spark/SparkManager.java new file mode 100644 index 00000000..b47b4276 --- /dev/null +++ b/src/java/org/jivesoftware/spark/SparkManager.java @@ -0,0 +1,344 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark; + +import org.jivesoftware.MainWindow; +import org.jivesoftware.Spark; +import org.jivesoftware.smack.XMPPConnection; +import org.jivesoftware.smackx.MessageEventManager; +import org.jivesoftware.spark.component.Notifications; +import org.jivesoftware.spark.filetransfer.SparkTransferManager; +import org.jivesoftware.spark.preference.PreferenceManager; +import org.jivesoftware.spark.search.SearchManager; +import org.jivesoftware.spark.ui.ChatPrinter; +import org.jivesoftware.spark.ui.ChatRoom; +import org.jivesoftware.spark.ui.TranscriptWindow; +import org.jivesoftware.spark.util.log.Log; +import org.jivesoftware.sparkimpl.profile.VCardManager; + +import java.awt.Toolkit; +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.StringSelection; +import java.awt.datatransfer.Transferable; +import java.io.File; +import java.text.SimpleDateFormat; + +/** + * Used as the System Manager for the Spark IM client. The SparkManager is responsible for + * the loading of other system managers on an as needed basis to prevent too much upfront loading + * of resources. Some of the Managers and components you can access from here are: + *

    + *

    + *

    + *

    Managers

    + * ChatManager - Used for adding, removing and appending listeners to Chat Rooms. + *
    + * PreferenceManager - Used for adding and removing Preferences. + *
    + * SoundManager - Used for playing sounds within Spark. + *
    + * SearchManager - Used for adding own search objects to Spark. + *
    + * SparkTransferManager - Used for all file transfer operations within Spark. + *
    + * ChatAssistantManager - Used to add ChatRoom plugins. ChatRoomPlugins are installed in their own pane on the + * right side of the ChatRoom. + *
    + * VCardManager - Handles all profile handling within Spark. Use this to retrieve profile information on users. + *
    + *

    Windows and Components

    + *
    + * MainWindow - The frame containing the Spark client. Use for updating menus, and referencing parent frames. + *
    + * Workspace - The inner pane of the Spark client. Use for adding or removing tabs to the main Spark panel. + *
    + * Notifications - Use to display tray icon notifications (system specific), such as toaster popups or changing + * the icon of the system tray. + * + * @author Derek DeMoro + * @version 1.0, 03/12/14 + */ + + +public final class SparkManager { + + /** + * The Date Formatter to use in Spark. + */ + public static final SimpleDateFormat DATE_SECOND_FORMATTER = new SimpleDateFormat("EEE MM/dd/yyyy h:mm:ss a"); + + private static SessionManager sessionManager; + private static SoundManager soundManager; + private static PreferenceManager preferenceManager; + private static MessageEventManager messageEventManager; + private static UserManager userManager; + private static ChatManager chatManager; + private static Notifications notifications; + private static VCardManager vcardManager; + private static AlertManager alertManager; + + + private SparkManager() { + // Do not allow initialization + } + + + /** + * Gets the {@link MainWindow} instance. The MainWindow is the frame container used + * to hold the Workspace container and Menubar of the Spark Client. + * + * @return MainWindow instance. + */ + public static MainWindow getMainWindow() { + return MainWindow.getInstance(); + } + + + /** + * Gets the {@link SessionManager} instance. + * + * @return the SessionManager instance. + */ + public static SessionManager getSessionManager() { + if (sessionManager == null) { + sessionManager = new SessionManager(); + } + return sessionManager; + } + + /** + * Gets the {@link SoundManager} instance. + * + * @return the SoundManager instance + */ + public static SoundManager getSoundManager() { + if (soundManager == null) { + soundManager = new SoundManager(); + } + return soundManager; + } + + /** + * Gets the {@link PreferenceManager} instance. + * + * @return the PreferenceManager instance. + */ + public static PreferenceManager getPreferenceManager() { + if (preferenceManager == null) { + preferenceManager = new PreferenceManager(); + } + return preferenceManager; + } + + /** + * Gets the {@link XMPPConnection} instance. + * + * @return the {@link XMPPConnection} associated with this session. + */ + public static XMPPConnection getConnection() { + return sessionManager.getConnection(); + } + + /** + * Returns the UserManager for LiveAssistant. The UserManager + * keeps track of all users in current chats. + * + * @return the UserManager for LiveAssistant. + */ + public static UserManager getUserManager() { + if (userManager == null) { + userManager = new UserManager(); + } + return userManager; + } + + + /** + * Returns the ChatManager. The ChatManager is responsible for creation and removal of + * chat rooms, transcripts, and transfers and room invitations. + * + * @return the ChatManager for this instance. + */ + public static ChatManager getChatManager() { + if (chatManager == null) { + chatManager = new ChatManager(); + } + return chatManager; + } + + /** + * Retrieves the inner container for Spark. The Workspace is the container for all plugins into the Spark + * install. Plugins would use this for the following: + *

    + *

      + *
    • Add own tab to the main tabbed pane. ex. + *

      + *

      + * Workspace workspace = SparkManager.getWorkspace(); + * JButton button = new JButton("HELLO SPARK USERS"); + * workspace.getWorkspacePane().addTab("MyPlugin", button); + *

      + *

      + *

    • Retrieve the ContactList. + */ + public static Workspace getWorkspace() { + return Workspace.getInstance(); + } + + + /** + * Returns the Notification System to handle general notification in either + * the system tray or "toaster" popups. You could use the notification engine + * to alert users to incoming messages, new emails, or forum posts. + * + * @return the Notification system. + */ + public static Notifications getNotificationsEngine() { + if (notifications == null) { + notifications = new Notifications(); + } + return notifications; + } + + /** + * Returns the MessageEventManager used in Spark. The MessageEventManager is responsible + * for XMPP specific operations such as notifying users that you have received their message or + * inform a users that you are typing a message to them. + * + * @return the MessageEventManager used in Spark. + */ + public static MessageEventManager getMessageEventManager() { + if (messageEventManager == null) { + messageEventManager = new MessageEventManager(getConnection()); + } + return messageEventManager; + } + + /** + * Returns the VCardManager. The VCardManager is responsible for handling all users profiles and updates + * to their profiles. Use the VCardManager to access a users profile based on their Jabber User ID (JID). + * + * @return the VCardManager. + */ + public static VCardManager getVCardManager() { + if (vcardManager == null) { + vcardManager = new VCardManager(); + } + return vcardManager; + } + + /** + * Returns the AlertManager. The AlertManager allows for flashing of Windows within Spark. + * + * @return the AlertManager. + */ + public static AlertManager getAlertManager() { + if (alertManager == null) { + alertManager = new AlertManager(); + } + + return alertManager; + } + + + /** + * Prints the transcript of a given chat room. + * + * @param room the chat room that contains the transcript to print. + */ + public static void printChatRoomTranscript(ChatRoom room) { + final ChatPrinter printer = new ChatPrinter(); + final TranscriptWindow currentWindow = room.getTranscriptWindow(); + if (currentWindow != null) { + printer.print(currentWindow); + } + } + + /** + * Returns the String in the system clipboard. If not string is found, + * null will be returned. + * + * @return the contents of the system clipboard. If none found, null is returned. + */ + public static String getClipboard() { + Transferable t = Toolkit.getDefaultToolkit().getSystemClipboard().getContents(null); + + try { + if (t != null && t.isDataFlavorSupported(DataFlavor.stringFlavor)) { + return (String)t.getTransferData(DataFlavor.stringFlavor); + } + } + catch (Exception e) { + Log.error("Could not retrieve info from clipboard.", e); + } + return null; + } + + /** + * Adds a string to the system clipboard. + * + * @param str the string to add the clipboard. + */ + public static void setClipboard(String str) { + StringSelection ss = new StringSelection(str); + Toolkit.getDefaultToolkit().getSystemClipboard().setContents(ss, null); + } + + /** + * Displays a print dialog to print the transcript found in a TranscriptWindow + * + * @param transcriptWindow the TranscriptWindow containing the transcript. + */ + public static void printChatTranscript(TranscriptWindow transcriptWindow) { + final ChatPrinter printer = new ChatPrinter(); + printer.print(transcriptWindow); + } + + + /** + * Returns the SparkTransferManager. This is used + * for any transfer operations within Spark. You may use the manager to + * intercept file transfers for filtering of transfers or own plugin operations + * with the File Transfer object. + * + * @return the SpartTransferManager. + */ + public static SparkTransferManager getTransferManager() { + return SparkTransferManager.getInstance(); + } + + /** + * Returns the SearchManager. This is used to allow + * plugins to register their own search service. + * + * @return the SearchManager. + */ + public static SearchManager getSearchManager() { + return SearchManager.getInstance(); + } + + /** + * Returns the User Directory to used by individual users. This allows for + * Multi-User Support. + * + * @return the UserDirectory for Spark. + */ + public static File getUserDirectory() { + final String bareJID = sessionManager.getBareAddress(); + File userDirectory = new File(Spark.getUserHome(), "Spark/user/" + bareJID); + if (!userDirectory.exists()) { + userDirectory.mkdirs(); + } + return userDirectory; + } + + +} \ No newline at end of file diff --git a/src/java/org/jivesoftware/spark/UserManager.java b/src/java/org/jivesoftware/spark/UserManager.java new file mode 100644 index 00000000..26b66e6d --- /dev/null +++ b/src/java/org/jivesoftware/spark/UserManager.java @@ -0,0 +1,290 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark; + +import org.jivesoftware.smack.Roster; +import org.jivesoftware.smack.packet.Presence; +import org.jivesoftware.smackx.muc.Occupant; +import org.jivesoftware.smackx.packet.VCard; +import org.jivesoftware.spark.ui.ChatRoom; +import org.jivesoftware.spark.ui.ContactItem; +import org.jivesoftware.spark.ui.ContactList; +import org.jivesoftware.spark.ui.rooms.GroupChatRoom; +import org.jivesoftware.spark.util.ModelUtil; +import org.jivesoftware.spark.util.log.Log; +import org.jivesoftware.sparkimpl.profile.VCardManager; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * Handles all users in the agent application. Each user or chatting user can be referenced from the User + * Manager. You would use the UserManager to get visitors in a chat room or secondary agents. + */ +public class UserManager { + + public UserManager() { + } + + public String getNickname() { + final VCardManager vCardManager = SparkManager.getVCardManager(); + VCard vcard = vCardManager.getVCard(); + if (vcard == null) { + return SparkManager.getSessionManager().getUsername(); + } + else { + String nickname = vcard.getNickName(); + if (ModelUtil.hasLength(nickname)) { + return nickname; + } + else { + String firstName = vcard.getFirstName(); + if (ModelUtil.hasLength(firstName)) { + return firstName; + } + } + } + + // Default to node if nothing. + return SparkManager.getSessionManager().getUsername(); + } + + + /** + * Return a Collection of all user jids found in the specified room. + * + * @param room the name of the chatroom + * @param fullJID set to true if you wish to have the full jid with resource, otherwise false + * for the bare jid. + * @return a Collection of jids found in the room. + */ + public Collection getUserJidsInRoom(String room, boolean fullJID) { + final List returnList = new ArrayList(); + + + return returnList; + } + + /** + * Checks to see if the user is an owner of the specified room. + * + * @param groupChatRoom the group chat room. + * @param nickname the user's nickname. + * @return true if the user is an owner. + */ + public boolean isOwner(GroupChatRoom groupChatRoom, String nickname) { + Occupant occupant = getOccupant(groupChatRoom, nickname); + if (occupant != null) { + String affiliation = occupant.getAffiliation(); + if ("owner".equals(affiliation)) { + return true; + } + } + return false; + } + + /** + * Checks to see if the Occupant is the owner of the room. + * + * @param occupant the occupant of a room. + * @return true if the user is an owner. + */ + public boolean isOwner(Occupant occupant) { + if (occupant != null) { + String affiliation = occupant.getAffiliation(); + if ("owner".equals(affiliation)) { + return true; + } + } + return false; + } + + /** + * Checks to see if the Occupant is a moderator. + * + * @param groupChatRoom the group chat room. + * @param nickname the nickname of the user. + * @return true if the user is a moderator. + */ + public boolean isModerator(GroupChatRoom groupChatRoom, String nickname) { + Occupant occupant = getOccupant(groupChatRoom, nickname); + if (occupant != null) { + String role = occupant.getRole(); + if ("moderator".equals(role)) { + return true; + } + } + return false; + } + + /** + * Checks to see if the Occupant is a moderator. + * + * @param occupant the Occupant of a room. + * @return true if the user is a moderator. + */ + public boolean isModerator(Occupant occupant) { + if (occupant != null) { + String role = occupant.getRole(); + if ("moderator".equals(role)) { + return true; + } + } + return false; + } + + /** + * Checks to see if the user is either an owner or admin of a room. + * + * @param groupChatRoom the group chat room. + * @param nickname the user's nickname. + * @return true if the user is either an owner or admin of the room. + */ + public boolean isOwnerOrAdmin(GroupChatRoom groupChatRoom, String nickname) { + Occupant occupant = getOccupant(groupChatRoom, nickname); + if (occupant != null) { + String affiliation = occupant.getAffiliation(); + if ("owner".equals(affiliation) || "admin".equals(affiliation)) { + return true; + } + } + return false; + } + + /** + * Checks to see if the user is either an owner or admin of the given room. + * + * @param occupant the Occupant to check. + * @return true if the user is either an owner or admin of the room. + */ + public boolean isOwnerOrAdmin(Occupant occupant) { + if (occupant != null) { + String affiliation = occupant.getAffiliation(); + if ("owner".equals(affiliation) || "admin".equals(affiliation)) { + return true; + } + } + return false; + } + + /** + * Returns the occupant of the room identified by their nickname. + * + * @param groupChatRoom the GroupChatRoom. + * @param nickname the users nickname. + * @return the Occupant found. + */ + public Occupant getOccupant(GroupChatRoom groupChatRoom, String nickname) { + String userJID = groupChatRoom.getRoomname() + "/" + nickname; + Occupant occ = null; + try { + occ = groupChatRoom.getMultiUserChat().getOccupant(userJID); + } + catch (Exception e) { + Log.error(e); + } + return occ; + } + + /** + * Checks the nickname of a user in a room and determines if they are an + * administrator of the room. + * + * @param groupChatRoom the GroupChatRoom. + * @param nickname the nickname of the user. Note: In MultiUserChats, users nicknames + * are defined by the resource(ex.theroom@conference.jivesoftware.com/derek) would have + * derek as a nickname. + * @return true if the user is an admin. + */ + public boolean isAdmin(GroupChatRoom groupChatRoom, String nickname) { + Occupant occupant = getOccupant(groupChatRoom, nickname); + if (occupant != null) { + String affiliation = occupant.getAffiliation(); + if ("admin".equals(affiliation)) { + return true; + } + } + return false; + } + + public boolean hasVoice(GroupChatRoom groupChatRoom, String nickname) { + Occupant occupant = getOccupant(groupChatRoom, nickname); + if (occupant != null) { + String role = occupant.getRole(); + if ("visitor".equals(role)) { + return false; + } + } + return true; + } + + + /** + * Returns a Collection of all ChatUsers in a ChatRoom. + * + * @param chatRoom the ChatRoom to inspect. + * @return the Collection of all ChatUsers. + * @see ChatUser + */ + public Collection getAllParticipantsInRoom(ChatRoom chatRoom) { + final String room = chatRoom.getRoomname(); + final List returnList = new ArrayList(); + + + return returnList; + } + + + public String getUserNicknameFromJID(String jid) { + ContactList contactList = SparkManager.getWorkspace().getContactList(); + ContactItem item = contactList.getContactItemByJID(jid); + if (item != null) { + return item.getNickname(); + } + + return null; + } + + /** + * Returns the full jid w/ resource of a user by their nickname + * in the ContactList. + * + * @param nickname the nickname of the user. + * @return the full jid w/ resource of the user. + */ + public String getJIDFromNickname(String nickname) { + ContactList contactList = SparkManager.getWorkspace().getContactList(); + ContactItem item = contactList.getContactItemByNickname(nickname); + if (item != null) { + return getFullJID(item.getFullJID()); + } + + return null; + } + + /** + * Returns the full jid (with resource) based on the user's jid. + * + * @param jid the users bare jid. + * @return the full jid with resource. + */ + public String getFullJID(String jid) { + Roster roster = SparkManager.getConnection().getRoster(); + Presence presence = roster.getPresence(jid); + if (presence != null) { + return presence.getFrom(); + } + + return null; + + } +} \ No newline at end of file diff --git a/src/java/org/jivesoftware/spark/Workspace.java b/src/java/org/jivesoftware/spark/Workspace.java new file mode 100644 index 00000000..b8cb43c9 --- /dev/null +++ b/src/java/org/jivesoftware/spark/Workspace.java @@ -0,0 +1,408 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark; + +import org.jivesoftware.MainWindow; +import org.jivesoftware.MainWindowListener; +import org.jivesoftware.smack.PacketListener; +import org.jivesoftware.smack.filter.PacketFilter; +import org.jivesoftware.smack.filter.PacketTypeFilter; +import org.jivesoftware.smack.packet.Message; +import org.jivesoftware.smack.packet.Packet; +import org.jivesoftware.smack.packet.Presence; +import org.jivesoftware.smack.util.StringUtils; +import org.jivesoftware.smackx.debugger.EnhancedDebuggerWindow; +import org.jivesoftware.smackx.packet.DelayInformation; +import org.jivesoftware.spark.filetransfer.SparkTransferManager; +import org.jivesoftware.spark.search.SearchManager; +import org.jivesoftware.spark.ui.ChatRoom; +import org.jivesoftware.spark.ui.ChatRoomNotFoundException; +import org.jivesoftware.spark.ui.ContactItem; +import org.jivesoftware.spark.ui.ContactList; +import org.jivesoftware.spark.ui.conferences.Conferences; +import org.jivesoftware.spark.ui.status.StatusBar; +import org.jivesoftware.spark.util.SwingWorker; +import org.jivesoftware.spark.util.log.Log; +import org.jivesoftware.sparkimpl.plugin.manager.Enterprise; +import org.jivesoftware.sparkimpl.plugin.transcripts.ChatTranscriptPlugin; + +import javax.swing.AbstractAction; +import javax.swing.JComponent; +import javax.swing.JPanel; +import javax.swing.JTabbedPane; +import javax.swing.KeyStroke; +import javax.swing.SwingUtilities; + +import java.awt.Color; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.event.ActionEvent; +import java.util.ArrayList; +import java.util.Date; +import java.util.Iterator; +import java.util.List; +import java.util.Timer; +import java.util.TimerTask; + + +/** + * The inner Container for Spark. The Workspace is the container for all plugins into the Spark + * install. Plugins would use this for the following: + *

      + *

        + *
      • Add own tab to the main tabbed pane. ex. + *

        + *

        + * Workspace workspace = SparkManager.getWorkspace(); + * JButton button = new JButton("HELLO SPARK USERS"); + * workspace.getWorkspacePane().addTab("MyPlugin", button); + *

        + *

        + *

      • Retrieve the ContactList. + */ +public class Workspace extends JPanel implements PacketListener { + private JTabbedPane workspacePane; + private final StatusBar statusBox = new StatusBar(); + private ContactList contactList; + private Conferences conferences; + + private static Workspace singleton; + private static final Object LOCK = new Object(); + private List offlineMessages = new ArrayList(); + + + /** + * Returns the singleton instance of Workspace, + * creating it if necessary. + *

        + * + * @return the singleton instance of Workspace + */ + public static Workspace getInstance() { + // Synchronize on LOCK to ensure that we don't end up creating + // two singletons. + synchronized (LOCK) { + if (null == singleton) { + Workspace controller = new Workspace(); + singleton = controller; + return controller; + } + } + return singleton; + } + + + /** + * Creates the instance of the SupportChatWorkspace. + */ + private Workspace() { + MainWindow mainWindow = SparkManager.getMainWindow(); + + // Add MainWindow listener + mainWindow.addMainWindowListener(new MainWindowListener() { + public void shutdown() { + // Close all Chats. + final Iterator chatRooms = SparkManager.getChatManager().getChatContainer().getAllChatRooms(); + while (chatRooms.hasNext()) { + final ChatRoom chatRoom = (ChatRoom)chatRooms.next(); + + // Leave ChatRoom + SparkManager.getChatManager().getChatContainer().leaveChatRoom(chatRoom); + } + + conferences.shutdown(); + } + + public void mainWindowActivated() { + + } + + public void mainWindowDeactivated() { + + } + }); + + // Initialize workspace pane, defaulting the tabs to the bottom. + workspacePane = new JTabbedPane(JTabbedPane.BOTTOM); + //workspacePane.setBoldActiveTab(true); + //workspacePane.setHideOneTab(true); + + // Build default workspace + this.setLayout(new GridBagLayout()); + add(workspacePane, new GridBagConstraints(0, 9, 1, 1, 1.0, 1.0, GridBagConstraints.WEST, GridBagConstraints.BOTH, new Insets(4, 4, 4, 4), 0, 0)); + add(statusBox, new GridBagConstraints(0, 0, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(4, 4, 4, 4), 0, 0)); + + + this.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("F12"), "showDebugger"); + this.getActionMap().put("showDebugger", new AbstractAction("showDebugger") { + public void actionPerformed(ActionEvent evt) { + EnhancedDebuggerWindow window = EnhancedDebuggerWindow.getInstance(); + window.setVisible(true); + } + }); + + // Set background + Color menuBarColor = new Color(235, 233, 237); + setBackground(menuBarColor); + } + + /** + * Builds the Workspace layout. + */ + public void buildLayout() { + new Enterprise(); + + // Initilaize tray + SparkManager.getNotificationsEngine(); + + // Initialize Contact List + contactList = new ContactList(); + contactList.initialize(); + + conferences = new Conferences(); + conferences.initialize(); + + // Initialize Search Service + SearchManager.getInstance(); + + // Initialise TransferManager + SparkTransferManager.getInstance(); + + ChatTranscriptPlugin transcriptPlugin = new ChatTranscriptPlugin(); + transcriptPlugin.initialize(); + } + + /** + * Starts the Loading of all Spark Plugins. + */ + public void loadPlugins() { + // Add presence and message listeners + // we listen for these to force open a 1-1 peer chat window from other operators if + // one isn't already open + PacketFilter workspaceMessageFilter = new PacketTypeFilter(Message.class); + + // Add the packetListener to this instance + SparkManager.getSessionManager().getConnection().addPacketListener(this, workspaceMessageFilter); + + // Make presence available to anonymous requests, if from anonymous user in the system. + PacketListener workspacePresenceListener = new PacketListener() { + public void processPacket(Packet packet) { + Presence presence = (Presence)packet; + if (presence != null && presence.getProperty("anonymous") != null) { + boolean isAvailable = statusBox.getPresence().getMode() == Presence.Mode.AVAILABLE; + Presence reply = new Presence(Presence.Type.AVAILABLE); + if (!isAvailable) { + reply.setType(Presence.Type.UNAVAILABLE); + } + reply.setTo(presence.getFrom()); + SparkManager.getSessionManager().getConnection().sendPacket(reply); + } + } + }; + + SparkManager.getSessionManager().getConnection().addPacketListener(workspacePresenceListener, new PacketTypeFilter(Presence.class)); + + // Send Available status + final Presence presence = SparkManager.getWorkspace().getStatusBar().getPresence(); + SparkManager.getSessionManager().changePresence(presence); + + // Load Plugins + SwingWorker worker = new SwingWorker() { + public Object construct() { + try { + Thread.sleep(100); + } + catch (InterruptedException e) { + Log.error("Unable to sleep thread.", e); + } + return "ok"; + } + + public void finished() { + final PluginManager pluginManager = PluginManager.getInstance(); + pluginManager.loadPlugins(); + pluginManager.initializePlugins(); + } + }; + worker.start(); + + + int numberOfMillisecondsInTheFuture = 5000; // 5 sec + Date timeToRun = new Date(System.currentTimeMillis() + numberOfMillisecondsInTheFuture); + Timer timer = new Timer(); + + timer.schedule(new TimerTask() { + public void run() { + final Iterator offlineMessage = offlineMessages.iterator(); + while (offlineMessage.hasNext()) { + Message offline = (Message)offlineMessage.next(); + handleOfflineMessage(offline); + } + } + }, timeToRun); + + } + + + /** + * Returns the status box for the User. + * + * @return the status box for the user. + */ + public StatusBar getStatusBar() { + return statusBox; + } + + /** + * This is to handle agent to agent conversations. + * + * @param packet the smack packet to process. + */ + public void processPacket(final Packet packet) { + SwingUtilities.invokeLater(new Runnable() { + public void run() { + handleIncomingPacket(packet); + } + }); + } + + + private void handleIncomingPacket(Packet packet) { + // We only handle message packets here. + if (packet instanceof Message) { + final Message message = (Message)packet; + boolean isGroupChat = message.getType() == Message.Type.GROUP_CHAT; + + // Check if Conference invite. If so, do not handle here. + if (message.getExtension("x", "jabber:x:conference") != null) { + return; + } + + final String body = message.getBody(); + boolean broadcast = message.getProperty("broadcast") != null; + + if (body == null || isGroupChat || broadcast || message.getType() == Message.Type.NORMAL) { + return; + } + + // Create new chat room for Agent Invite. + final String from = packet.getFrom(); + final String host = SparkManager.getSessionManager().getServerAddress(); + + // Don't allow workgroup notifications to come through here. + final String bareJID = StringUtils.parseBareAddress(from); + if (host.equalsIgnoreCase(from) || from == null) { + return; + } + + + ChatRoom room = null; + try { + room = SparkManager.getChatManager().getChatContainer().getChatRoom(bareJID); + } + catch (ChatRoomNotFoundException e) { + // Ignore + } + + // Handle offline message. + DelayInformation offlineInformation = (DelayInformation)message.getExtension("x", "jabber:x:delay"); + + if (offlineInformation != null && (Message.Type.CHAT == message.getType() || Message.Type.NORMAL == message.getType())) { + offlineMessages.add(message); + } + + // Check for anonymous user. + else if (room == null) { + createOneToOneRoom(bareJID, message); + } + } + } + + /** + * Creates a new room if necessary and inserts an offline message. + * + * @param message The Offline message. + */ + private void handleOfflineMessage(Message message) { + DelayInformation offlineInformation = (DelayInformation)message.getExtension("x", "jabber:x:delay"); + String bareJID = StringUtils.parseBareAddress(message.getFrom()); + ContactItem contact = contactList.getContactItemByJID(bareJID); + String nickname = StringUtils.parseName(bareJID); + if (contact != null) { + nickname = contact.getNickname(); + } + + // Create the room if it does not exist. + ChatRoom room = SparkManager.getChatManager().createChatRoom(bareJID, nickname, nickname); + + // Insert offline message + String offlineMessage = "(Offline) " + message.getBody(); + room.getTranscriptWindow().insertOthersMessage(nickname, offlineMessage, offlineInformation.getStamp()); + room.addToTranscript(message, true); + + // Send display and notified message back. + SparkManager.getMessageEventManager().sendDeliveredNotification(message.getFrom(), message.getPacketID()); + SparkManager.getMessageEventManager().sendDisplayedNotification(message.getFrom(), message.getPacketID()); + } + + /** + * Creates a new room based on an anonymous user. + * + * @param bareJID the bareJID of the anonymous user. + * @param message the message from the anonymous user. + */ + private void createOneToOneRoom(String bareJID, Message message) { + ContactItem contact = contactList.getContactItemByJID(bareJID); + String nickname = StringUtils.parseName(bareJID); + if (contact != null) { + nickname = contact.getNickname(); + } + + SparkManager.getChatManager().createChatRoom(bareJID, nickname, nickname); + try { + insertMessage(bareJID, message); + } + catch (ChatRoomNotFoundException e) { + Log.error("Could not find chat room.", e); + } + } + + + private void insertMessage(final String bareJID, final Message message) throws ChatRoomNotFoundException { + ChatRoom chatRoom = SparkManager.getChatManager().getChatContainer().getChatRoom(bareJID); + chatRoom.insertMessage(message); + int chatLength = chatRoom.getTranscriptWindow().getDocument().getLength(); + chatRoom.getTranscriptWindow().setCaretPosition(chatLength); + chatRoom.getChatInputEditor().requestFocusInWindow(); + } + + + /** + * Returns the Workspace TabbedPane. If you wish to add your + * component, simply use addTab( name, icon, component ) call. + * + * @return the workspace JideTabbedPane + */ + public JTabbedPane getWorkspacePane() { + return workspacePane; + } + + + /** + * Returns the ContactList associated with this workspace. + * + * @return the ContactList associated with this workspace. + */ + public ContactList getContactList() { + return contactList; + } +} \ No newline at end of file diff --git a/src/java/org/jivesoftware/spark/component/BackgroundPanel.java b/src/java/org/jivesoftware/spark/component/BackgroundPanel.java new file mode 100644 index 00000000..48625d19 --- /dev/null +++ b/src/java/org/jivesoftware/spark/component/BackgroundPanel.java @@ -0,0 +1,42 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.component; + +import org.jivesoftware.resource.Default; + +import javax.swing.JPanel; + +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Image; +import java.awt.geom.AffineTransform; + +/** + * An implementation of a colored background panel. Allows implementations + * to specify an image to use in the background of the panel. + */ +public class BackgroundPanel extends JPanel { + + /** + * Creates a background panel using the default Spark background image. + */ + public BackgroundPanel() { + } + + + public void paintComponent(Graphics g) { + final Image backgroundImage = Default.getImageIcon(Default.SECONDARY_BACKGROUND_IMAGE).getImage(); + double scaleX = getWidth() / (double)backgroundImage.getWidth(null); + double scaleY = getHeight() / (double)backgroundImage.getHeight(null); + AffineTransform xform = AffineTransform.getScaleInstance(scaleX, scaleY); + ((Graphics2D)g).drawImage(backgroundImage, xform, this); + } +} diff --git a/src/java/org/jivesoftware/spark/component/CheckBoxList.java b/src/java/org/jivesoftware/spark/component/CheckBoxList.java new file mode 100644 index 00000000..23fa9c6a --- /dev/null +++ b/src/java/org/jivesoftware/spark/component/CheckBoxList.java @@ -0,0 +1,68 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.component; + +import javax.swing.JCheckBox; +import javax.swing.JPanel; +import javax.swing.JScrollPane; + +import java.awt.BorderLayout; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +/** + * Constructs a selection list with Checkboxes. + */ +public class CheckBoxList extends JPanel { + private Map valueMap = new HashMap(); + private JPanel internalPanel = new JPanel(); + + /** + * Create the CheckBoxList UI. + */ + public CheckBoxList() { + setLayout(new BorderLayout()); + internalPanel.setLayout(new VerticalFlowLayout(VerticalFlowLayout.TOP, 5, 5, true, false)); + add(new JScrollPane(internalPanel), BorderLayout.CENTER); + } + + /** + * Add a checkbox with an associated value. + * + * @param box the checkbox. + * @param value the value bound to the checkbox. + */ + public void addCheckBox(JCheckBox box, String value) { + internalPanel.add(box); + valueMap.put(box, value); + } + + /** + * Returns a list of selected checkbox values. + * + * @return list of selected checkbox values. + */ + public List getSelectedValues() { + List list = new ArrayList(); + Iterator iter = valueMap.keySet().iterator(); + while (iter.hasNext()) { + JCheckBox checkbox = (JCheckBox)iter.next(); + if (checkbox.isSelected()) { + String value = (String)valueMap.get(checkbox); + list.add(value); + } + } + return list; + } +} diff --git a/src/java/org/jivesoftware/spark/component/CheckNode.java b/src/java/org/jivesoftware/spark/component/CheckNode.java new file mode 100644 index 00000000..54751e0c --- /dev/null +++ b/src/java/org/jivesoftware/spark/component/CheckNode.java @@ -0,0 +1,151 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.component; + +import java.util.Enumeration; + +/** + * Creates one tree node with a check box. + */ +public class CheckNode extends JiveTreeNode { + /** + * Mode to use if the node should not expand when selected. + */ + public static final int SINGLE_SELECTION = 0; + + /** + * Mode to use if the node should be expaned if selected and if possible. + */ + public static final int DIG_IN_SELECTION = 4; + + private int selectionMode; + private boolean isSelected; + private String fullName; + private Object associatedObject; + + /** + * Construct an empty node. + */ + public CheckNode() { + this(null); + } + + /** + * Creates a new CheckNode with the specified name. + * + * @param userObject the name to use. + */ + public CheckNode(Object userObject) { + this(userObject, true, false); + } + + /** + * Constructs a new CheckNode. + * + * @param userObject the name to use. + * @param allowsChildren true if it allows children. + * @param isSelected true if it is to be selected. + */ + public CheckNode(Object userObject, boolean allowsChildren, boolean isSelected) { + super(userObject, allowsChildren); + this.isSelected = isSelected; + setSelectionMode(DIG_IN_SELECTION); + } + + /** + * Constructs a new CheckNode. + * + * @param userObject the name to use. + * @param allowsChildren true if it allows children. + * @param isSelected true if it is selected. + * @param name the identifier name. + */ + public CheckNode(Object userObject, boolean allowsChildren, boolean isSelected, String name) { + super(userObject, allowsChildren); + this.isSelected = isSelected; + setSelectionMode(DIG_IN_SELECTION); + fullName = name; + } + + /** + * Returns the full name of the node. + * + * @return the full name of the node. + */ + public String getFullName() { + return fullName; + } + + /** + * Sets the selection mode. + * + * @param mode the selection mode to use. + */ + public void setSelectionMode(int mode) { + selectionMode = mode; + } + + /** + * Returns the selection mode. + * + * @return the selection mode. + */ + public int getSelectionMode() { + return selectionMode; + } + + /** + * Selects or deselects node. + * + * @param isSelected true if the node should be selected, false otherwise. + */ + public void setSelected(boolean isSelected) { + this.isSelected = isSelected; + + if (selectionMode == DIG_IN_SELECTION + && children != null) { + Enumeration nodeEnum = children.elements(); + while (nodeEnum.hasMoreElements()) { + CheckNode node = (CheckNode)nodeEnum.nextElement(); + node.setSelected(isSelected); + } + } + } + + /** + * Returns true if the node is selected. + * + * @return true if the node is selected. + */ + public boolean isSelected() { + return isSelected; + } + + /** + * Returns the associated object of this node. + * + * @return the associated object. + */ + public Object getAssociatedObject() { + return associatedObject; + } + + /** + * Sets an assoicated object for this node. + * + * @param associatedObject the associated object set. + */ + public void setAssociatedObject(Object associatedObject) { + this.associatedObject = associatedObject; + } +} + + diff --git a/src/java/org/jivesoftware/spark/component/CheckRenderer.java b/src/java/org/jivesoftware/spark/component/CheckRenderer.java new file mode 100644 index 00000000..8bcc45c6 --- /dev/null +++ b/src/java/org/jivesoftware/spark/component/CheckRenderer.java @@ -0,0 +1,173 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.component; + +import javax.swing.Icon; +import javax.swing.JCheckBox; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTree; +import javax.swing.UIManager; +import javax.swing.plaf.ColorUIResource; +import javax.swing.tree.TreeCellRenderer; + +import java.awt.Color; +import java.awt.Component; +import java.awt.Dimension; +import java.awt.Graphics; + + +/** + * Swing Renderer for CheckNode. + */ +public class CheckRenderer extends JPanel implements TreeCellRenderer { + private JCheckBox check; + private TreeLabel label; + + /** + * Create new CheckRenderer. + */ + public CheckRenderer() { + setLayout(null); + add(check = new JCheckBox()); + add(label = new TreeLabel()); + check.setBackground(UIManager.getColor("Tree.textBackground")); + label.setForeground(UIManager.getColor("Tree.textForeground")); + } + + public Component getTreeCellRendererComponent(JTree tree, Object value, + boolean isSelected, boolean expanded, + boolean leaf, int row, boolean hasFocus) { + String stringValue = tree.convertValueToText(value, isSelected, + expanded, leaf, row, hasFocus); + setEnabled(tree.isEnabled()); + check.setSelected(((CheckNode)value).isSelected()); + label.setFont(tree.getFont()); + label.setText(stringValue); + label.setSelected(isSelected); + label.setFocus(hasFocus); + if (leaf) { + label.setIcon(UIManager.getIcon("Tree.leafIcon")); + } + else if (expanded) { + label.setIcon(UIManager.getIcon("Tree.openIcon")); + } + else { + label.setIcon(UIManager.getIcon("Tree.closedIcon")); + } + return this; + } + + public Dimension getPreferredSize() { + Dimension d_check = new Dimension(30, 30);//check.getPreferredSize(); + Dimension d_label = label.getPreferredSize(); + return new Dimension(d_check.width + d_label.width, + d_check.height < d_label.height ? d_label.height : d_check.height); + } + + public void doLayout() { + Dimension d_check = check.getPreferredSize(); + Dimension d_label = label.getPreferredSize(); + int y_check = 0; + int y_label = 0; + if (d_check.height < d_label.height) { + y_check = (d_label.height - d_check.height) / 2; + } + else { + y_label = (d_check.height - d_label.height) / 2; + } + check.setLocation(0, y_check); + check.setBounds(0, y_check, d_check.width, d_check.height); + label.setLocation(d_check.width, y_label); + label.setBounds(d_check.width, y_label, d_label.width, d_label.height); + } + + + public void setBackground(Color color) { + if (color instanceof ColorUIResource) + color = null; + super.setBackground(color); + } + + + /** + * Represents one UI node for the checkbox node. + */ + public class TreeLabel extends JLabel { + boolean isSelected; + boolean hasFocus; + + /** + * Empty Constructor. + */ + public TreeLabel() { + } + + public void setBackground(Color color) { + if (color instanceof ColorUIResource) + color = null; + super.setBackground(color); + } + + public void paint(Graphics g) { + String str; + if ((str = getText()) != null) { + if (0 < str.length()) { + if (isSelected) { + g.setColor(UIManager.getColor("Tree.selectionBackground")); + } + else { + g.setColor(UIManager.getColor("Tree.textBackground")); + } + Dimension d = getPreferredSize(); + int imageOffset = 0; + Icon currentI = getIcon(); + if (currentI != null) { + imageOffset = currentI.getIconWidth() + Math.max(0, getIconTextGap() - 1); + } + g.fillRect(imageOffset, 0, d.width - 1 - imageOffset, d.height); + if (hasFocus) { + g.setColor(UIManager.getColor("Tree.selectionBorderColor")); + g.drawRect(imageOffset, 0, d.width - 1 - imageOffset, d.height - 1); + } + } + } + super.paint(g); + } + + public Dimension getPreferredSize() { + Dimension retDimension = super.getPreferredSize(); + if (retDimension != null) { + retDimension = new Dimension(retDimension.width + 3, + retDimension.height); + } + return retDimension; + } + + /** + * Set to true to select the node. + * + * @param isSelected true if the node should be selected. + */ + public void setSelected(boolean isSelected) { + this.isSelected = isSelected; + } + + /** + * Sets focus on the node. + * + * @param hasFocus true if the node has focus. + */ + public void setFocus(boolean hasFocus) { + this.hasFocus = hasFocus; + } + } +} diff --git a/src/java/org/jivesoftware/spark/component/CheckTree.java b/src/java/org/jivesoftware/spark/component/CheckTree.java new file mode 100644 index 00000000..04fd17ec --- /dev/null +++ b/src/java/org/jivesoftware/spark/component/CheckTree.java @@ -0,0 +1,132 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.component; + +import javax.swing.JPanel; +import javax.swing.JTextArea; +import javax.swing.JTree; +import javax.swing.tree.DefaultTreeModel; +import javax.swing.tree.TreeNode; +import javax.swing.tree.TreePath; +import javax.swing.tree.TreeSelectionModel; + +import java.awt.BorderLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.util.Enumeration; + +/** + * UI to show CheckBox trees. + */ +public class CheckTree extends JPanel { + private JTree tree; + private CheckNode rootNode; + + /** + * Constructs a new CheckBox tree. + */ + public CheckTree(CheckNode rootNode) { + this.rootNode = rootNode; + + tree = new JTree(rootNode); + tree.setCellRenderer(new CheckRenderer()); + tree.setRowHeight(18); + tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION); + tree.setToggleClickCount(1000); + tree.putClientProperty("JTree.lineStyle", "Angled"); + tree.addMouseListener(new NodeSelectionListener(tree)); + + setLayout(new BorderLayout()); + add(tree, BorderLayout.CENTER); + } + + + class NodeSelectionListener extends MouseAdapter { + JTree tree; + + NodeSelectionListener(JTree tree) { + this.tree = tree; + } + + public void mouseClicked(MouseEvent e) { + int x = e.getX(); + int y = e.getY(); + int row = tree.getRowForLocation(x, y); + TreePath path = tree.getPathForRow(row); + if (path != null) { + CheckNode node = (CheckNode)path.getLastPathComponent(); + boolean isSelected = !node.isSelected(); + node.setSelected(isSelected); + if (node.getSelectionMode() == CheckNode.DIG_IN_SELECTION) { + if (isSelected) { + //tree.expandPath(path); + } + else { + //tree.collapsePath(path); + } + } + ((DefaultTreeModel)tree.getModel()).nodeChanged(node); + // I need revalidate if node is root. but why? + if (row == 0) { + tree.revalidate(); + tree.repaint(); + } + } + } + } + + /** + * Closes the CheckTree. + */ + public void close() { + final Enumeration nodeEnum = rootNode.breadthFirstEnumeration(); + while (nodeEnum.hasMoreElements()) { + CheckNode node = (CheckNode)nodeEnum.nextElement(); + if (node.isSelected()) { + String fullname = node.getFullName(); + } + } + } + + + class ButtonActionListener implements ActionListener { + CheckNode root; + JTextArea textArea; + + ButtonActionListener(CheckNode root, JTextArea textArea) { + this.root = root; + this.textArea = textArea; + } + + public void actionPerformed(ActionEvent e) { + Enumeration nodeEnum = root.breadthFirstEnumeration(); + while (nodeEnum.hasMoreElements()) { + CheckNode node = (CheckNode)nodeEnum.nextElement(); + if (node.isSelected()) { + TreeNode[] nodes = node.getPath(); + textArea.append("\n" + nodes[0].toString()); + for (int i = 1; i < nodes.length; i++) { + textArea.append("/" + nodes[i].toString()); + } + } + } + } + } + + public JTree getTree() { + return tree; + } + + +} + diff --git a/src/java/org/jivesoftware/spark/component/ConfirmDialog.java b/src/java/org/jivesoftware/spark/component/ConfirmDialog.java new file mode 100644 index 00000000..71bea016 --- /dev/null +++ b/src/java/org/jivesoftware/spark/component/ConfirmDialog.java @@ -0,0 +1,141 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.component; + +import org.jivesoftware.spark.util.ResourceUtils; + +import javax.swing.Icon; +import javax.swing.JButton; +import javax.swing.JDialog; +import javax.swing.JFrame; +import javax.swing.JLabel; + +import java.awt.BorderLayout; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; + +/** + * Implementation of a Confirm Dialog to replace the modal JOptionPane.confirm. This is intended + * for use as a yes - no dialog. + */ +public class ConfirmDialog extends BackgroundPanel { + private JLabel message; + private JLabel iconLabel; + private JButton yesButton; + private JButton noButton; + + private ConfirmListener listener = null; + private JDialog dialog; + + /** + * Creates the base confirm Dialog. + */ + public ConfirmDialog() { + setLayout(new GridBagLayout()); + + message = new JLabel(); + iconLabel = new JLabel(); + yesButton = new JButton(); + noButton = new JButton(); + + add(iconLabel, new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + add(message, new GridBagConstraints(1, 0, 4, 1, 1.0, 0.0, GridBagConstraints.NORTHWEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); + add(yesButton, new GridBagConstraints(3, 1, 1, 1, 1.0, 0.0, GridBagConstraints.EAST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + add(noButton, new GridBagConstraints(4, 1, 1, 1, 0.0, 0.0, GridBagConstraints.EAST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + + yesButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent actionEvent) { + if (listener != null) { + listener.yesOption(); + } + dialog.dispose(); + } + }); + + noButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent actionEvent) { + dialog.dispose(); + } + }); + + + } + + /** + * Creates and displays the new confirm dialog. + * + * @param parent the parent dialog. + * @param title the title of this dialog. + * @param text the main text to display. + * @param yesText the text to use on the OK or Yes button. + * @param noText the text to use on the No button. + * @param icon the icon to use for graphical represenation. + */ + public void showConfirmDialog(JFrame parent, String title, String text, String yesText, String noText, Icon icon) { + message.setText("" + text + ""); + iconLabel.setIcon(icon); + + ResourceUtils.resButton(yesButton, yesText); + ResourceUtils.resButton(noButton, noText); + + dialog = new JDialog(parent, title, false); + dialog.getContentPane().setLayout(new BorderLayout()); + dialog.getContentPane().add(this); + dialog.pack(); + dialog.setLocationRelativeTo(parent); + dialog.setVisible(true); + + dialog.addWindowListener(new WindowAdapter() { + public void windowClosed(WindowEvent windowEvent) { + if (listener != null) { + listener.noOption(); + } + } + }); + } + + public void setDialogSize(int width, int height) { + dialog.setSize(width, height); + dialog.pack(); + dialog.validate(); + } + + /** + * Sets the ConfirmListener to use with this dialog instance. + * + * @param listener the ConfirmListener to use with this instance. + */ + public void setConfirmListener(ConfirmListener listener) { + this.listener = listener; + } + + /** + * Used to handle yes/no selection in dialog. You would use this simply to + * be notified when a user has either clicked on the yes or no dialog. + */ + public interface ConfirmListener { + + /** + * Fired when the Yes button has been clicked. + */ + void yesOption(); + + /** + * Fired when the No button has been clicked. + */ + void noOption(); + } +} diff --git a/src/java/org/jivesoftware/spark/component/DroppableFrame.java b/src/java/org/jivesoftware/spark/component/DroppableFrame.java new file mode 100644 index 00000000..8a6f1196 --- /dev/null +++ b/src/java/org/jivesoftware/spark/component/DroppableFrame.java @@ -0,0 +1,126 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.component; + +import org.jivesoftware.spark.util.log.Log; + +import javax.swing.JFrame; + +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.Transferable; +import java.awt.dnd.DnDConstants; +import java.awt.dnd.DragGestureEvent; +import java.awt.dnd.DragGestureListener; +import java.awt.dnd.DragSource; +import java.awt.dnd.DragSourceDragEvent; +import java.awt.dnd.DragSourceDropEvent; +import java.awt.dnd.DragSourceEvent; +import java.awt.dnd.DragSourceListener; +import java.awt.dnd.DropTarget; +import java.awt.dnd.DropTargetDragEvent; +import java.awt.dnd.DropTargetDropEvent; +import java.awt.dnd.DropTargetEvent; +import java.awt.dnd.DropTargetListener; +import java.io.File; +import java.util.Iterator; +import java.util.List; + +/** + * Creates a DroppableFrame. A droppable frame allows for DnD of file objects from the OS + * onto the actual frame via File object. + */ +public abstract class DroppableFrame extends JFrame implements DropTargetListener, DragSourceListener, DragGestureListener { + private DropTarget dropTarget = new DropTarget(this, this); + private DragSource dragSource = DragSource.getDefaultDragSource(); + + /** + * Creates an Instance of the Droppable Frame. + */ + protected DroppableFrame() { + dragSource.createDefaultDragGestureRecognizer(this, DnDConstants.ACTION_COPY_OR_MOVE, this); + } + + public void dragDropEnd(DragSourceDropEvent DragSourceDropEvent) { + } + + public void dragEnter(DragSourceDragEvent DragSourceDragEvent) { + } + + public void dragExit(DragSourceEvent DragSourceEvent) { + } + + public void dragOver(DragSourceDragEvent DragSourceDragEvent) { + } + + public void dropActionChanged(DragSourceDragEvent DragSourceDragEvent) { + } + + public void dragEnter(DropTargetDragEvent dropTargetDragEvent) { + dropTargetDragEvent.acceptDrag(DnDConstants.ACTION_COPY_OR_MOVE); + } + + public void dragExit(DropTargetEvent dropTargetEvent) { + } + + public void dragOver(DropTargetDragEvent dropTargetDragEvent) { + } + + public void dropActionChanged(DropTargetDragEvent dropTargetDragEvent) { + } + + public void drop(DropTargetDropEvent dropTargetDropEvent) { + try { + Transferable transferable = dropTargetDropEvent.getTransferable(); + if (transferable.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) { + dropTargetDropEvent.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE); + List fileList = (List)transferable.getTransferData(DataFlavor.javaFileListFlavor); + Iterator iterator = fileList.iterator(); + while (iterator.hasNext()) { + File file = (File)iterator.next(); + if (file.isFile()) { + fileDropped(file); + } + + if (file.isDirectory()) { + directoryDropped(file); + } + } + dropTargetDropEvent.getDropTargetContext().dropComplete(true); + } + else { + dropTargetDropEvent.rejectDrop(); + } + } + catch (Exception io) { + Log.error(io); + dropTargetDropEvent.rejectDrop(); + } + + } + + public void dragGestureRecognized(DragGestureEvent dragGestureEvent) { + + } + + /** + * Notified when a file has been dropped onto the frame. + * + * @param file the file that has been dropped. + */ + public abstract void fileDropped(File file); + + /** + * Notified when a directory has been dropped onto the frame. + * + * @param file the directory that has been dropped. + */ + public abstract void directoryDropped(File file); +} \ No newline at end of file diff --git a/src/java/org/jivesoftware/spark/component/HTMLViewer.java b/src/java/org/jivesoftware/spark/component/HTMLViewer.java new file mode 100644 index 00000000..2fab51c8 --- /dev/null +++ b/src/java/org/jivesoftware/spark/component/HTMLViewer.java @@ -0,0 +1,105 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.component; + + +import org.jivesoftware.spark.util.log.Log; + +import javax.swing.JEditorPane; +import javax.swing.JPanel; +import javax.swing.event.HyperlinkListener; +import javax.swing.text.html.HTMLEditorKit; + +import java.awt.Dimension; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; + +/** + * Creates a new CoBrowser component. The CoBrowser is ChatRoom specific and is used + * to control the end users browser. Using the CoBrowser allows you to assist end customers + * by directing them to the appropriate site. + */ +public class HTMLViewer extends JPanel { + private JEditorPane browser; + + + /** + * Creates a new CoBrowser object to be used with the specifid ChatRoom. + */ + public HTMLViewer() { + final JPanel mainPanel = new JPanel(); + browser = new JEditorPane(); + browser.setEditorKit(new HTMLEditorKit()); + + setLayout(new GridBagLayout()); + + this.add(mainPanel, new GridBagConstraints(0, 2, 1, 1, 1.0, 1.0, GridBagConstraints.WEST, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0)); + } + + /** + * Sets the HTML content of the viewer. + * + * @param text the html content. + */ + public void setHTMLContent(String text) { + browser.setText(text); + } + + /** + * Loads a URL into the Viewer. + * + * @param url the url. + */ + public void loadURL(String url) { + try { + if (url.startsWith("www")) { + url = "http://" + url; + } + browser.setPage(url); + } + catch (Exception ex) { + Log.error(ex); + } + } + + + /** + * Returns the selected text contained in this TextComponent. If the selection is null or the document empty, returns null. + * + * @return the text. + */ + public String getSelectedText() { + return browser.getSelectedText(); + } + + + /** + * Let's make sure that the panel doesn't strech past the + * scrollpane view pane. + * + * @return the preferred dimension + */ + public Dimension getPreferredSize() { + final Dimension size = super.getPreferredSize(); + size.width = 0; + return size; + } + + /** + * Adds a hyperlink listener for notification of any changes, for example when a link is selected and entered. + * + * @param listener the listener + */ + public void setHyperlinkListener(HyperlinkListener listener) { + browser.addHyperlinkListener(listener); + } +} diff --git a/src/java/org/jivesoftware/spark/component/IconTextField.java b/src/java/org/jivesoftware/spark/component/IconTextField.java new file mode 100644 index 00000000..6cf793f9 --- /dev/null +++ b/src/java/org/jivesoftware/spark/component/IconTextField.java @@ -0,0 +1,122 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.component; + +import org.jivesoftware.resource.SparkRes; + +import javax.swing.Icon; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextField; +import javax.swing.UIManager; + +import java.awt.Color; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; + +/** + * Creates a Firefox Search type box that allows for icons inside of a textfield. This + * could be used to build out your own search objects. + */ +public class IconTextField extends JPanel { + private JTextField textField; + private JLabel imageComponent; + private JLabel downOption; + + /** + * Creates a new IconTextField with Icon. + * + * @param icon the icon. + */ + public IconTextField(Icon icon) { + setLayout(new GridBagLayout()); + setBackground((Color)UIManager.get("TextField.background")); + + textField = new JTextField(); + textField.setBorder(null); + setBorder(new JTextField().getBorder()); + + imageComponent = new JLabel(icon); + downOption = new JLabel(SparkRes.getImageIcon(SparkRes.DOWN_OPTION_IMAGE)); + + add(downOption, new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.SOUTHEAST, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0)); + add(imageComponent, new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0)); + add(textField, new GridBagConstraints(2, 0, 1, 1, 1.0, 1.0, GridBagConstraints.WEST, GridBagConstraints.BOTH, new Insets(0, 5, 0, 0), 0, 0)); + + downOption.setVisible(false); + } + + public void enableDropdown(boolean enable) { + downOption.setVisible(enable); + } + + /** + * Sets the text of the textfield. + * + * @param text the text. + */ + public void setText(String text) { + textField.setText(text); + } + + /** + * Returns the text inside of the textfield. + * + * @return the text inside of the textfield. + */ + public String getText() { + return textField.getText(); + } + + /** + * Sets the icon to use inside of the textfield. + * + * @param icon the icon. + */ + public void setIcon(Icon icon) { + imageComponent.setIcon(icon); + } + + /** + * Returns the current icon used in the textfield. + * + * @return the icon used in the textfield. + */ + public Icon getIcon() { + return imageComponent.getIcon(); + } + + /** + * Returns the component that holds the icon. + * + * @return the component that is the container for the icon. + */ + public JComponent getImageComponent() { + return imageComponent; + } + + /** + * Returns the text component used. + * + * @return the text component used. + */ + public JTextField getTextComponent() { + return textField; + } +} + + + + + + diff --git a/src/java/org/jivesoftware/spark/component/ImageTitlePanel.java b/src/java/org/jivesoftware/spark/component/ImageTitlePanel.java new file mode 100644 index 00000000..abc85c7e --- /dev/null +++ b/src/java/org/jivesoftware/spark/component/ImageTitlePanel.java @@ -0,0 +1,163 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.component; + +import javax.swing.ImageIcon; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextArea; + +import java.awt.Color; +import java.awt.Font; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Image; +import java.awt.Insets; +import java.awt.geom.AffineTransform; + +/** + * Fancy title panel that displays gradient colors, text and components. + */ +public class ImageTitlePanel extends JPanel { + private Image backgroundImage; + private final JLabel titleLabel = new JLabel(); + private final JLabel iconLabel = new JLabel(); + private final GridBagLayout gridBagLayout = new GridBagLayout(); + private final WrappedLabel descriptionLabel = new WrappedLabel(); + + /** + * Creates a new ImageTitlePanel. + * + * @param title the title to use for this label. + */ + public ImageTitlePanel(String title) { + ImageIcon icons = new ImageIcon(getClass().getResource("/images/header-stretch.gif")); + backgroundImage = icons.getImage(); + + init(); + + titleLabel.setText(title); + titleLabel.setForeground(Color.white); + titleLabel.setFont(new Font("Verdana", Font.BOLD, 11)); + } + + /** + * Creates a new ImageTitlePanel object. + */ + public ImageTitlePanel() { + ImageIcon icons = new ImageIcon(getClass().getResource("/images/header-stretch.gif")); + backgroundImage = icons.getImage(); + + init(); + + titleLabel.setForeground(Color.white); + titleLabel.setFont(new Font("Verdana", Font.BOLD, 11)); + } + + public void paintComponent(Graphics g) { + double scaleX = getWidth() / (double)backgroundImage.getWidth(null); + double scaleY = getHeight() / (double)backgroundImage.getHeight(null); + AffineTransform xform = AffineTransform.getScaleInstance(scaleX, scaleY); + ((Graphics2D)g).drawImage(backgroundImage, xform, this); + } + + private void init() { + setLayout(gridBagLayout); + add(titleLabel, new GridBagConstraints(0, 0, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); + } + + /** + * Set the description for the label. + * + * @param description the description for the label. + */ + public void setDescription(String description) { + descriptionLabel.setText(description); + add(descriptionLabel, new GridBagConstraints(0, 1, 3, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); + } + + /** + * Set the font of the description label. + * + * @param font the font to use in the description label. + */ + public void setDescriptionFont(Font font) { + descriptionLabel.setFont(font); + } + + /** + * Returns the description label. + * + * @return the description label. + */ + public JTextArea getDescriptionLabel() { + return descriptionLabel; + } + + /** + * Sets the title to use in the label. + * + * @param title the title to use. + */ + public void setTitle(String title) { + titleLabel.setText(title); + } + + /** + * Returns the title label. + * + * @return the title label. + */ + public JLabel getTitleLabel() { + return titleLabel; + } + + /** + * Set the font of the title label. + * + * @param font the font to use for title label. + */ + public void setTitleFont(Font font) { + titleLabel.setFont(font); + } + + /** + * Specify a component to use on this label. + * + * @param component the component to use with this label. + */ + public void setComponent(JComponent component) { + add(new JLabel(), + new GridBagConstraints(1, 0, 1, 1, 1.0, 0.0, GridBagConstraints.CENTER, + GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); + add(component, + new GridBagConstraints(2, 0, 1, 1, 0.0, 0.0, GridBagConstraints.EAST, + GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); + } + + /** + * Specify the icon to use with this label. + * + * @param icon the icon to use with this label. + */ + public void setIcon(ImageIcon icon) { + add(new JLabel(), + new GridBagConstraints(1, 0, 1, 1, 1.0, 0.0, GridBagConstraints.CENTER, + GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); + iconLabel.setIcon(icon); + add(iconLabel, + new GridBagConstraints(2, 0, 1, 1, 1.0, 0.0, GridBagConstraints.EAST, + GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); + } +} \ No newline at end of file diff --git a/src/java/org/jivesoftware/spark/component/InputDialog.java b/src/java/org/jivesoftware/spark/component/InputDialog.java new file mode 100644 index 00000000..bf0c5164 --- /dev/null +++ b/src/java/org/jivesoftware/spark/component/InputDialog.java @@ -0,0 +1,162 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.component; + +import org.jivesoftware.spark.SparkManager; + +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.Icon; +import javax.swing.JDialog; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTextArea; + +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.event.ActionEvent; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; + + +/** + * InputDialog class is used to retrieve information from a user. + * + * @version 1.0, 06/03/2005 + */ +public final class InputDialog implements PropertyChangeListener { + private JTextArea textArea; + private JOptionPane optionPane; + private JDialog dialog; + + private String stringValue; + private int width = 400; + private int height = 250; + + /** + * Empty Constructor. + */ + public InputDialog() { + } + + /** + * Returns the input from a user. + * + * @param title the title of the dialog. + * @param description the dialog description. + * @param icon the icon to use. + * @param width the dialog width + * @param height the dialog height + * @return the users input. + */ + public String getInput(String title, String description, Icon icon, int width, int height) { + this.width = width; + this.height = height; + + return getInput(title, description, icon, SparkManager.getMainWindow()); + } + + /** + * Prompt and return input. + * + * @param title the title of the dialog. + * @param description the dialog description. + * @param icon the icon to use. + * @param parent the parent to use. + * @return the user input. + */ + public String getInput(String title, String description, Icon icon, Component parent) { + textArea = new JTextArea(); + textArea.setLineWrap(true); + + TitlePanel titlePanel = new TitlePanel(title, description, icon, true); + + // Construct main panel w/ layout. + final JPanel mainPanel = new JPanel(); + mainPanel.setLayout(new BorderLayout()); + mainPanel.add(titlePanel, BorderLayout.NORTH); + + // The user should only be able to close this dialog. + final Object[] options = {"Ok", "Cancel"}; + optionPane = new JOptionPane(new JScrollPane(textArea), JOptionPane.PLAIN_MESSAGE, + JOptionPane.OK_CANCEL_OPTION, null, options, options[0]); + + mainPanel.add(optionPane, BorderLayout.CENTER); + + // Let's make sure that the dialog is modal. Cannot risk people + // losing this dialog. + JOptionPane p = new JOptionPane(); + dialog = p.createDialog(parent, title); + dialog.setModal(true); + dialog.pack(); + dialog.setSize(width, height); + dialog.setContentPane(mainPanel); + dialog.setLocationRelativeTo(parent); + optionPane.addPropertyChangeListener(this); + + // Add Key Listener to Send Field + textArea.addKeyListener(new KeyAdapter() { + public void keyPressed(KeyEvent e) { + if (e.getKeyChar() == KeyEvent.VK_TAB) { + optionPane.requestFocus(); + } + else if (e.getKeyChar() == KeyEvent.VK_ESCAPE) { + dialog.dispose(); + } + } + }); + + textArea.requestFocus(); + + + dialog.setVisible(true); + return stringValue; + } + + /** + * Move to focus forward action. + */ + public Action nextFocusAction = new AbstractAction("Move Focus Forwards") { + public void actionPerformed(ActionEvent evt) { + ((Component)evt.getSource()).transferFocus(); + } + }; + + /** + * Moves the focus backwards in the dialog. + */ + public Action prevFocusAction = new AbstractAction("Move Focus Backwards") { + public void actionPerformed(ActionEvent evt) { + ((Component)evt.getSource()).transferFocusBackward(); + } + }; + + public void propertyChange(PropertyChangeEvent e) { + String value = (String)optionPane.getValue(); + if ("Cancel".equals(value)) { + stringValue = null; + dialog.setVisible(false); + } + else if ("Ok".equals(value)) { + stringValue = textArea.getText(); + if (stringValue.trim().length() == 0) { + stringValue = null; + } + else { + stringValue = stringValue.trim(); + } + dialog.setVisible(false); + } + } +} \ No newline at end of file diff --git a/src/java/org/jivesoftware/spark/component/JiveSortableTable.java b/src/java/org/jivesoftware/spark/component/JiveSortableTable.java new file mode 100644 index 00000000..0faba25c --- /dev/null +++ b/src/java/org/jivesoftware/spark/component/JiveSortableTable.java @@ -0,0 +1,443 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.component; + +import org.jdesktop.swingx.JXTable; +import org.jivesoftware.spark.util.GraphicUtils; + +import javax.swing.BorderFactory; +import javax.swing.DefaultCellEditor; +import javax.swing.Icon; +import javax.swing.JButton; +import javax.swing.JComboBox; +import javax.swing.JLabel; +import javax.swing.JTable; +import javax.swing.JTextArea; +import javax.swing.ListSelectionModel; +import javax.swing.border.Border; +import javax.swing.table.DefaultTableModel; +import javax.swing.table.TableCellRenderer; + +import java.awt.Color; +import java.awt.Component; +import java.awt.Font; +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; +import java.awt.event.MouseEvent; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +/** + * JiveTable class can be used to maintain quality look and feel + * throughout the product. This is mainly from the rendering capabilities. + * + * @version 1.0, 03/12/14 + */ +public abstract class JiveSortableTable extends JXTable { + private Table.JiveTableModel tableModel; + + /** + * Define the color of row and column selections. + */ + public static final Color SELECTION_COLOR = new Color(166, 202, 240); + + /** + * Define the color used in the tooltips. + */ + public static final Color TOOLTIP_COLOR = new Color(166, 202, 240); + + private final Map objectMap = new HashMap(); + + /** + * Empty Constructor. + */ + protected JiveSortableTable() { + } + + public String getToolTipText(MouseEvent e) { + int r = rowAtPoint(e.getPoint()); + int c = columnAtPoint(e.getPoint()); + Object value = null; + try { + value = getValueAt(r, c); + } + catch (Exception e1) { + // If we encounter a row that should not actually exist and therefore + // has a null value. Just return an empty string for the tooltip. + return ""; + } + + String tooltipValue = null; + + if (value instanceof JLabel) { + tooltipValue = ((JLabel)value).getToolTipText(); + } + + if (value instanceof JLabel && tooltipValue == null) { + tooltipValue = ((JLabel)value).getText(); + } + else if (value != null && tooltipValue == null) { + tooltipValue = value.toString(); + } + else if (tooltipValue == null) { + tooltipValue = ""; + } + + return GraphicUtils.createToolTip(tooltipValue); + } + + // Handle image rendering correctly + public TableCellRenderer getCellRenderer(int row, int column) { + Object o = getValueAt(row, column); + if (o != null) { + if (o instanceof JLabel) { + return new JLabelRenderer(false); + } + } + return super.getCellRenderer(row, column); + } + + /** + * Creates a table using the specified table headers. + * + * @param headers the table headers to use. + */ + protected JiveSortableTable(String[] headers) { + tableModel = new Table.JiveTableModel(headers, 0, false); + + + getTableHeader().setReorderingAllowed(false); + setGridColor(Color.white); + setRowHeight(20); + getColumnModel().setColumnMargin(0); + setSelectionBackground(SELECTION_COLOR); + setSelectionForeground(Color.black); + setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + + this.addKeyListener(new KeyListener() { + public void keyPressed(KeyEvent e) { + if (e.getKeyChar() == KeyEvent.VK_ENTER) { + e.consume(); + enterPressed(); + } + } + + public void keyReleased(KeyEvent e) { + } + + public void keyTyped(KeyEvent e) { + + } + }); + } + + /** + * Adds a list to the table model. + * + * @param list the list to add to the model. + */ + public void add(List list) { + final Iterator iter = list.iterator(); + while (iter.hasNext()) { + Object[] newRow = (Object[])iter.next(); + tableModel.addRow(newRow); + } + } + + /** + * Get the object array of a row. + * + * @return the object array of a row. + */ + public Object[] getSelectedRowObject() { + return getRowObject(getSelectedRow()); + } + + /** + * Returns the object[] of a row. + * + * @param selectedRow the row to retrieve. + * @return the object[] of a row. + */ + public Object[] getRowObject(int selectedRow) { + if (selectedRow < 0) { + return null; + } + + int columnCount = getColumnCount(); + + Object[] obj = new Object[columnCount]; + for (int j = 0; j < columnCount; j++) { + obj[j] = tableModel.getValueAt(selectedRow, j); + } + + return obj; + } + + /** + * Removes all columns and rows from table. + */ + public void clearTable() { + int rowCount = getRowCount(); + for (int i = 0; i < rowCount; i++) { + getTableModel().removeRow(0); + } + } + + /** + * The internal Table Model. + */ + public static class JiveTableModel extends DefaultTableModel { + private boolean isEditable; + + /** + * Use the JiveTableModel in order to better handle the table. This allows + * for consistency throughout the product. + * + * @param columnNames - String array of columnNames + * @param numRows - initial number of rows + * @param isEditable - true if the cells are editable, false otherwise. + */ + public JiveTableModel(Object[] columnNames, int numRows, boolean isEditable) { + super(columnNames, numRows); + this.isEditable = isEditable; + } + + /** + * Returns true if cell is editable. + * + * @param row the row to check. + * @param column the column to check. + * @return true if the cell is editable. + */ + public boolean isCellEditable(int row, int column) { + return isEditable; + } + } + + /** + * A swing renderer used to display labels within a table. + */ + public class JLabelRenderer extends JLabel implements TableCellRenderer { + Border unselectedBorder; + Border selectedBorder; + boolean isBordered = true; + + /** + * JLabelConstructor to build ui. + * + * @param isBordered true if the border should be shown. + */ + public JLabelRenderer(boolean isBordered) { + setOpaque(true); + this.isBordered = isBordered; + } + + public Component getTableCellRendererComponent(JTable table, Object color, boolean isSelected, boolean hasFocus, int row, int column) { + final String text = ((JLabel)color).getText(); + if (text != null) { + setText(" " + text); + } + final Icon icon = ((JLabel)color).getIcon(); + setIcon(icon); + + if (isSelected) { + setForeground(table.getSelectionForeground()); + setBackground(table.getSelectionBackground()); + } + else { + setForeground(Color.black); + setBackground(Color.white); + if (row % 2 == 0) { + //setBackground( new Color( 156, 207, 255 ) ); + } + } + + if (isBordered) { + if (isSelected) { + if (selectedBorder == null) { + selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5, + table.getSelectionBackground()); + } + setBorder(selectedBorder); + } + else { + if (unselectedBorder == null) { + unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5, + table.getBackground()); + } + setBorder(unselectedBorder); + } + } + return this; + } + } + + /** + * A swing renderer to dispaly Textareas within a table. + */ + public class TextAreaCellRenderer extends JTextArea implements TableCellRenderer { + + /** + * Create new renderer with font. + * + * @param font the font to use in the renderer. + */ + public TextAreaCellRenderer(Font font) { + setLineWrap(true); + setWrapStyleWord(true); + setFont(font); + } + + public Component getTableCellRendererComponent(JTable jTable, Object obj, boolean isSelected, boolean hasFocus, + int row, int column) { + // set color & border here + setText(obj == null ? "" : obj.toString()); + setSize(jTable.getColumnModel().getColumn(column).getWidth(), + getPreferredSize().height); + if (jTable.getRowHeight(row) != getPreferredSize().height) { + jTable.setRowHeight(row, getPreferredSize().height); + } + return this; + } + } + + /** + * A swing renderer used to display Buttons within a table. + */ + public class JButtonRenderer extends JButton implements TableCellRenderer { + Border unselectedBorder; + Border selectedBorder; + boolean isBordered = true; + + /** + * Empty Constructor. + */ + public JButtonRenderer() { + } + + + public Component getTableCellRendererComponent(JTable table, Object color, boolean isSelected, boolean hasFocus, int row, int column) { + final String text = ((JButton)color).getText(); + setText(text); + + final Icon icon = ((JButton)color).getIcon(); + setIcon(icon); + + if (isSelected) { + setForeground(table.getSelectionForeground()); + setBackground(table.getSelectionBackground()); + } + else { + setForeground(Color.black); + setBackground(Color.white); + if (row % 2 == 0) { + //setBackground( new Color( 156, 207, 255 ) ); + } + } + + if (isBordered) { + if (isSelected) { + if (selectedBorder == null) { + selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5, + table.getSelectionBackground()); + } + setBorder(selectedBorder); + } + else { + if (unselectedBorder == null) { + unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5, + table.getBackground()); + } + setBorder(unselectedBorder); + } + } + return this; + } + } + + public class ComboBoxRenderer extends JComboBox implements TableCellRenderer { + public ComboBoxRenderer() { + + } + + public ComboBoxRenderer(String[] items) { + super(items); + } + + public Component getTableCellRendererComponent(JTable table, Object value, + boolean isSelected, boolean hasFocus, int row, int column) { + if (isSelected) { + setForeground(table.getSelectionForeground()); + super.setBackground(table.getSelectionBackground()); + } + else { + setForeground(table.getForeground()); + setBackground(table.getBackground()); + } + + // Select the current value + setSelectedItem(value); + return this; + } + } + + public class MyComboBoxEditor extends DefaultCellEditor { + public MyComboBoxEditor(String[] items) { + super(new JComboBox(items)); + } + } + + /** + * Returns the table model. + * + * @return the table model. + */ + public Table.JiveTableModel getTableModel() { + return tableModel; + } + + /** + * Clears all objects from map. + */ + public void clearObjectMap() { + objectMap.clear(); + } + + /** + * Associate an object with a row. + * + * @param row - the current row + * @param object - the object to associate with the row. + */ + public void addObject(int row, Object object) { + objectMap.put(new Integer(row), object); + } + + /** + * Returns the associated row object. + * + * @param row - the row associated with the object. + * @return The object associated with the row. + */ + public Object getObject(int row) { + return objectMap.get(new Integer(row)); + } + + /** + * Override to handle when enter is pressed. + */ + public void enterPressed() { + } + +} \ No newline at end of file diff --git a/src/java/org/jivesoftware/spark/component/JiveTable.java b/src/java/org/jivesoftware/spark/component/JiveTable.java new file mode 100644 index 00000000..2cfd0d18 --- /dev/null +++ b/src/java/org/jivesoftware/spark/component/JiveTable.java @@ -0,0 +1,217 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.component; + +import javax.swing.BorderFactory; +import javax.swing.Icon; +import javax.swing.JButton; +import javax.swing.JLabel; +import javax.swing.JTable; +import javax.swing.ListSelectionModel; +import javax.swing.border.Border; +import javax.swing.table.DefaultTableModel; +import javax.swing.table.TableCellRenderer; + +import java.awt.Color; +import java.awt.Component; +import java.util.Iterator; +import java.util.List; + +/** + * JiveTable class can be used to maintain quality look and feel + * throughout the product. This is mainly from the rendering capabilities. + * + * @version 1.0, 03/12/14 + */ +public class JiveTable extends JTable { + private JiveTable.JiveTableModel _tableModel; + + + public JiveTable(String[] headers, Integer[] columnsToUseRenderer) { + _tableModel = new JiveTable.JiveTableModel(headers, 0, false); + this.setModel(_tableModel); + + getTableHeader().setReorderingAllowed(false); + setGridColor(java.awt.Color.white); + setRowHeight(20); + getColumnModel().setColumnMargin(0); + setSelectionBackground(new java.awt.Color(57, 109, 206)); + setSelectionForeground(java.awt.Color.white); + setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + setRowSelectionAllowed(false); + + } + + public TableCellRenderer getCellRenderer(int row, int column) { + if (column == 3 || column == 4) { + return new JiveTable.JButtonRenderer(false); + } + else if (column == 1) { + return new JiveTable.JLabelRenderer(false); + } + else { + return super.getCellRenderer(row, column); + } + } + + public void add(List list) { + final Iterator iter = list.iterator(); + while (iter.hasNext()) { + Object[] newRow = (Object[])iter.next(); + _tableModel.addRow(newRow); + } + } + + public Object[] getSelectedObject() { + int selectedRow = getSelectedRow(); + if (selectedRow < 0) { + return null; + } + + int columnCount = getColumnCount(); + + Object[] obj = new Object[columnCount]; + for (int j = 0; j < columnCount; j++) { + Object objs = _tableModel.getValueAt(selectedRow, j); + obj[j] = objs; + } + + return obj; + } + + public class JiveTableModel extends DefaultTableModel { + private boolean _isEditable; + + /** + * Use the JiveTableModel in order to better handle the table. This allows + * for consistency throughout the product. + * + * @param columnNames - String array of columnNames + * @param numRows - initial number of rows + * @param isEditable - true if the cells are editable, false otherwise. + */ + public JiveTableModel(Object[] columnNames, int numRows, boolean isEditable) { + super(columnNames, numRows); + _isEditable = isEditable; + } + + /** + * Returns true if cell is editable. + */ + public boolean isCellEditable(int row, int column) { + return _isEditable; + } + } + + class JLabelRenderer extends JLabel implements TableCellRenderer { + Border unselectedBorder = null; + Border selectedBorder = null; + boolean isBordered = true; + + public JLabelRenderer(boolean isBordered) { + super(); + } + + public Component getTableCellRendererComponent(JTable table, Object color, boolean isSelected, boolean hasFocus, int row, int column) { + final String text = ((JLabel)color).getText(); + setText(text); + + final Icon icon = ((JLabel)color).getIcon(); + setIcon(icon); + + if (isSelected) { + setForeground(table.getSelectionForeground()); + setBackground(table.getSelectionBackground()); + } + else { + setForeground(Color.black); + setBackground(Color.white); + if (row % 2 == 0) { + //setBackground( new Color( 156, 207, 255 ) ); + } + } + + if (isBordered) { + if (isSelected) { + if (selectedBorder == null) { + selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5, + table.getSelectionBackground()); + } + setBorder(selectedBorder); + } + else { + if (unselectedBorder == null) { + unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5, + table.getBackground()); + } + setBorder(unselectedBorder); + } + } + return this; + } + } + + + class JButtonRenderer extends JButton implements TableCellRenderer { + Border unselectedBorder = null; + Border selectedBorder = null; + boolean isBordered = true; + + public JButtonRenderer(boolean isBordered) { + super(); + //this.isBordered = isBordered; + //setOpaque(true); //MUST do this for background to show up. + } + + public Component getTableCellRendererComponent(JTable table, Object color, boolean isSelected, boolean hasFocus, int row, int column) { + final String text = ((JButton)color).getText(); + setText(text); + + final Icon icon = ((JButton)color).getIcon(); + setIcon(icon); + + if (isSelected) { + setForeground(table.getSelectionForeground()); + setBackground(table.getSelectionBackground()); + } + else { + setForeground(Color.black); + setBackground(Color.white); + if (row % 2 == 0) { + //setBackground( new Color( 156, 207, 255 ) ); + } + } + + if (isBordered) { + if (isSelected) { + if (selectedBorder == null) { + selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5, + table.getSelectionBackground()); + } + setBorder(selectedBorder); + } + else { + if (unselectedBorder == null) { + unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5, + table.getBackground()); + } + setBorder(unselectedBorder); + } + } + return this; + } + } + + public JiveTable.JiveTableModel getTableModel() { + return _tableModel; + } + +} \ No newline at end of file diff --git a/src/java/org/jivesoftware/spark/component/JiveTreeCellRenderer.java b/src/java/org/jivesoftware/spark/component/JiveTreeCellRenderer.java new file mode 100644 index 00000000..e10a7e7f --- /dev/null +++ b/src/java/org/jivesoftware/spark/component/JiveTreeCellRenderer.java @@ -0,0 +1,95 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.component; + +import javax.swing.Icon; +import javax.swing.JTree; +import javax.swing.tree.DefaultTreeCellRenderer; + +import java.awt.Component; +import java.awt.Font; + +/** + * JiveTreeCellRenderer class is a bare bone + * TreeCellRenderer to easily add icons and not much else. + * + * @version 1.0, 03/12/14 + */ +public class JiveTreeCellRenderer extends DefaultTreeCellRenderer { + private Object value; + private boolean isExpanded; + + /** + * Empty Constructor. + */ + public JiveTreeCellRenderer() { + } + + public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) { + this.value = value; + + final Component c = super.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus); + + isExpanded = expanded; + + setIcon(getCustomIcon()); + + // Root Nodes are always bold + JiveTreeNode node = (JiveTreeNode)value; + if (node.getAllowsChildren()) { + setFont(new Font("Arial", Font.BOLD, 11)); + } + else { + setFont(new Font("Arial", Font.PLAIN, 11)); + } + + + return c; + } + + private Icon getCustomIcon() { + if (value instanceof JiveTreeNode) { + JiveTreeNode node = (JiveTreeNode)value; + if (isExpanded) { + return node.getOpenIcon(); + } + return node.getClosedIcon(); + } + return null; + } + + public Icon getClosedIcon() { + return getCustomIcon(); + } + + public Icon getDefaultClosedIcon() { + return getCustomIcon(); + } + + public Icon getDefaultLeafIcon() { + return getCustomIcon(); + } + + public Icon getDefaultOpenIcon() { + return getCustomIcon(); + } + + public Icon getLeafIcon() { + return getCustomIcon(); + } + + public Icon getOpenIcon() { + return getCustomIcon(); + } + +} + + diff --git a/src/java/org/jivesoftware/spark/component/JiveTreeNode.java b/src/java/org/jivesoftware/spark/component/JiveTreeNode.java new file mode 100644 index 00000000..9ca724cb --- /dev/null +++ b/src/java/org/jivesoftware/spark/component/JiveTreeNode.java @@ -0,0 +1,238 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.component; + +import org.jivesoftware.resource.SparkRes; + +import javax.swing.Icon; +import javax.swing.tree.DefaultMutableTreeNode; + +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.Transferable; +import java.awt.datatransfer.UnsupportedFlavorException; +import java.io.IOException; + +/** + * JiveTreeNode class is a better implementation than using the + * DefaultMutableTree node. This allows better searching of children/parents as well + * as handling of icons and drag and drop events. + * + * @version 1.0, 01/01/2006 + */ + +public class JiveTreeNode extends DefaultMutableTreeNode implements Transferable { + private Icon closedImage = null; + private Icon openImage = null; + + /** + * Default Drag and Drop to use for Node detection. + */ + public static final DataFlavor[] DATA_FLAVORS = {new DataFlavor(JiveTreeNode.class, "JiveTreeNodeFlavor")}; + private Object associatedObject; + + /* + * Create node with closedImage. + * @param userObject Name to display + * @param allowsChildren Specify if node allows children + * @param img Specify closedImage to use. + */ + public JiveTreeNode(TreeFolder folder) { + super(folder.getDisplayName(), true); + closedImage = SparkRes.getImageIcon(SparkRes.FOLDER_CLOSED); + openImage = SparkRes.getImageIcon(SparkRes.FOLDER); + associatedObject = folder; + } + + /** + * Create parent node. + * + * @param name the name of the node. + * @param allowsChildren true if the node allows children. + */ + public JiveTreeNode(String name, boolean allowsChildren) { + super(name, allowsChildren); + if (allowsChildren) { + closedImage = SparkRes.getImageIcon(SparkRes.FOLDER_CLOSED); + openImage = SparkRes.getImageIcon(SparkRes.FOLDER); + } + } + + /** + * Creates a new JiveTreeNode. + * + * @param o the object to use. + * @param allowsChildren true if it allows children. + */ + public JiveTreeNode(Object o, boolean allowsChildren) { + super(o, allowsChildren); + } + + /** + * Creates a new JiveTreeNode from a TreeItem. + * + * @param item the TreeItem + */ + public JiveTreeNode(TreeItem item) { + super(item.getDisplayName(), false); + associatedObject = item; + } + + /** + * Creates a new JiveTreeNode from a TreeFolder. + * + * @param folder the TreeFolder. + * @param img the image to use in the node. + */ + public JiveTreeNode(TreeFolder folder, Icon img) { + this(folder); + closedImage = img; + } + + /** + * Createa new JiveTreeNode from a TreeItem and Image. + * + * @param item the TreeItem to use. + * @param img the image to use in the node. + */ + public JiveTreeNode(TreeItem item, Icon img) { + this(item); + closedImage = img; + } + + /** + * Creates a new JiveTreeNode. + * + * @param userobject the object to use in the node. Note: By default, the node + * will not allow children. + */ + public JiveTreeNode(String userobject) { + super(userobject); + } + + /** + * Creates a new JiveTreeNode. + * + * @param userObject the userObject to use. + * @param allowChildren true if it allows children. + * @param icon the image to use in the node. + */ + public JiveTreeNode(String userObject, boolean allowChildren, Icon icon) { + super(userObject, allowChildren); + closedImage = icon; + openImage = icon; + } + + /** + * Returns the default image used. + * + * @return the default image used. + */ + public Icon getIcon() { + return closedImage; + } + + /** + * Return the icon that is displayed when the node is expanded. + * + * @return the open icon. + */ + public Icon getOpenIcon() { + return openImage; + } + + /** + * Returns the icon that is displayed when the node is collapsed. + * + * @return the closed icon. + */ + public Icon getClosedIcon() { + return closedImage; + } + + /** + * Sets the default icon. + * + * @param icon the icon. + */ + public void setIcon(Icon icon) { + closedImage = icon; + } + + /** + * Returns the associated object used. The associated object is used to store associated data objects + * along with the node. + * + * @return the object. + */ + public Object getAssociatedObject() { + return associatedObject; + } + + /** + * Returns the associated object. + * + * @param o the associated object. + */ + public void setAssociatedObject(Object o) { + this.associatedObject = o; + } + + /** + * Returns true if a parent with the specified name is found. + * + * @param parentName the name of the parent. + * @return true if parent found. + */ + public final boolean hasParent(String parentName) { + JiveTreeNode parent = (JiveTreeNode)getParent(); + while (true) { + if (parent.getAssociatedObject() == null) { + break; + } + final TreeFolder folder = (TreeFolder)parent.getAssociatedObject(); + if (folder.getDisplayName().equals(parentName)) { + return true; + } + parent = (JiveTreeNode)parent.getParent(); + } + return false; + } + + + /** + * Transferable implementation + */ + public DataFlavor[] getTransferDataFlavors() { + return DATA_FLAVORS; + } + + public boolean isDataFlavorSupported(DataFlavor flavor) { + if (flavor == DATA_FLAVORS[0]) { + return true; + } + + return false; + } + + public Object getTransferData(DataFlavor flavor) + throws UnsupportedFlavorException, IOException { + if (this.isDataFlavorSupported(flavor)) { + return this; + } + + throw new UnsupportedFlavorException(flavor); + } + + +} + + + diff --git a/src/java/org/jivesoftware/spark/component/LinkLabel.java b/src/java/org/jivesoftware/spark/component/LinkLabel.java new file mode 100644 index 00000000..521dd694 --- /dev/null +++ b/src/java/org/jivesoftware/spark/component/LinkLabel.java @@ -0,0 +1,99 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.component; + +import org.jivesoftware.spark.util.BrowserLauncher; +import org.jivesoftware.spark.util.log.Log; + +import javax.swing.JLabel; + +import java.awt.Color; +import java.awt.Cursor; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.io.IOException; + +/** + * The LinkLabel class is a JLabel subclass + * to mimic an html link. When clicked, it launches the specified url + * in the default browser. + * + * @author Derek DeMoro + */ +final public class LinkLabel extends JLabel implements MouseListener { + // cursors used in url-link related displays and default display + private Cursor DEFAULT_CURSOR = new Cursor(Cursor.DEFAULT_CURSOR); + private Cursor LINK_CURSOR = new Cursor(Cursor.HAND_CURSOR); + private Color rolloverTextColor; + private Color foregroundTextColor; + private String labelURL; + + private boolean invokeBrowser; + + /** + * @param text the text for the label. + * @param url the url to launch when this label is clicked on. + * @param foregroundColor the text color for the label. + * @param rolloverColor the text color to be used when the mouse is over the label. + */ + public LinkLabel(String text, + String url, + Color foregroundColor, + Color rolloverColor) { + super(text); + + rolloverTextColor = rolloverColor; + foregroundTextColor = foregroundColor; + labelURL = url; + + this.addMouseListener(this); + + this.setForeground(foregroundTextColor); + this.setVerticalTextPosition(JLabel.TOP); + } + + public void setInvokeBrowser(boolean invoke) { + invokeBrowser = invoke; + } + + /** + * MouseListener implementation. + * + * @param me the MouseEvent. + */ + public void mouseClicked(MouseEvent me) { + if (invokeBrowser) { + try { + BrowserLauncher.openURL(labelURL); + } + catch (IOException e) { + Log.error(e); + } + } + } + + public void mousePressed(MouseEvent me) { + } + + public void mouseReleased(MouseEvent me) { + } + + public void mouseEntered(MouseEvent me) { + this.setForeground(rolloverTextColor); + this.setCursor(LINK_CURSOR); + } + + public void mouseExited(MouseEvent me) { + this.setForeground(foregroundTextColor); + this.setCursor(DEFAULT_CURSOR); + } + +} diff --git a/src/java/org/jivesoftware/spark/component/MessageDialog.java b/src/java/org/jivesoftware/spark/component/MessageDialog.java new file mode 100644 index 00000000..50c509cd --- /dev/null +++ b/src/java/org/jivesoftware/spark/component/MessageDialog.java @@ -0,0 +1,255 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.component; + +import org.jivesoftware.MainWindow; +import org.jivesoftware.resource.SparkRes; +import org.jivesoftware.spark.SparkManager; + +import javax.swing.Icon; +import javax.swing.JComponent; +import javax.swing.JDialog; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTextPane; + +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.Font; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.io.Writer; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; + + +/** + * MessageDialog class is used to easily display the most commonly used dialogs. + */ +public final class MessageDialog { + + private MessageDialog() { + } + + /** + * Display a dialog with an exception. + * + * @param throwable the throwable object to display. + */ + public static void showErrorDialog(Throwable throwable) { + JTextPane textPane; + final JOptionPane pane; + final JDialog dlg; + + TitlePanel titlePanel; + + textPane = new JTextPane(); + textPane.setFont(new Font("Dialog", Font.PLAIN, 12)); + textPane.setEditable(false); + + String message = getStackTrace(throwable); + textPane.setText(message); + // Create the title panel for this dialog + titlePanel = new TitlePanel("An error has been detected. Please report to support@jivesoftware.com.", null, SparkRes.getImageIcon(SparkRes.SMALL_DELETE), true); + + // Construct main panel w/ layout. + final JPanel mainPanel = new JPanel(); + mainPanel.setLayout(new BorderLayout()); + mainPanel.add(titlePanel, BorderLayout.NORTH); + + // The user should only be able to close this dialog. + Object[] options = {"Close"}; + pane = new JOptionPane(new JScrollPane(textPane), JOptionPane.PLAIN_MESSAGE, JOptionPane.OK_CANCEL_OPTION, null, options, options[0]); + + mainPanel.add(pane, BorderLayout.CENTER); + + MainWindow mainWindow = SparkManager.getMainWindow(); + dlg = new JDialog(mainWindow, "Spark Error", false); + dlg.pack(); + dlg.setSize(600, 400); + dlg.setContentPane(mainPanel); + dlg.setLocationRelativeTo(mainWindow); + + PropertyChangeListener changeListener = new PropertyChangeListener() { + public void propertyChange(PropertyChangeEvent e) { + String value = (String)pane.getValue(); + if ("Close".equals(value)) { + dlg.setVisible(false); + } + } + }; + + pane.addPropertyChangeListener(changeListener); + + dlg.setVisible(true); + dlg.toFront(); + dlg.requestFocus(); + } + + /** + * Display an alert dialog. + * + * @param message the message to display. + * @param header the header/title of the dialog. + */ + public static void showAlert(String message, String header, Icon icon) { + JTextPane textPane; + final JOptionPane pane; + final JDialog dlg; + + TitlePanel titlePanel; + + textPane = new JTextPane(); + textPane.setFont(new Font("Dialog", Font.PLAIN, 12)); + textPane.setEditable(false); + textPane.setText(message); + + // Create the title panel for this dialog + titlePanel = new TitlePanel(header, null, icon, true); + + // Construct main panel w/ layout. + final JPanel mainPanel = new JPanel(); + mainPanel.setLayout(new BorderLayout()); + mainPanel.add(titlePanel, BorderLayout.NORTH); + + // The user should only be able to close this dialog. + Object[] options = {"Close"}; + pane = new JOptionPane(new JScrollPane(textPane), JOptionPane.PLAIN_MESSAGE, JOptionPane.OK_CANCEL_OPTION, null, options, options[0]); + + mainPanel.add(pane, BorderLayout.CENTER); + + MainWindow mainWindow = SparkManager.getMainWindow(); + dlg = new JDialog(mainWindow, "Broadcast Message", false); + dlg.pack(); + dlg.setSize(300, 300); + dlg.setContentPane(mainPanel); + dlg.setLocationRelativeTo(SparkManager.getMainWindow()); + + PropertyChangeListener changeListener = new PropertyChangeListener() { + public void propertyChange(PropertyChangeEvent e) { + String value = (String)pane.getValue(); + if ("Close".equals(value)) { + dlg.setVisible(false); + } + } + }; + + pane.addPropertyChangeListener(changeListener); + + dlg.setVisible(true); + dlg.toFront(); + dlg.requestFocus(); + } + + /** + * Display a dialog with a specified component. + * + * @param title the title of the dialog. + * @param description the description to display. + * @param icon the icon. + * @param comp the component to display. + * @param parent the parent of this dialog. + * @param width the width of this dialog. + * @param height the height of this dialog. + * @param modal true if it is modal. + * @return the JDialog created. + */ + public static JDialog showComponent(String title, String description, Icon icon, JComponent comp, Component parent, int width, int height, boolean modal) { + final JOptionPane pane; + final JDialog dlg; + + TitlePanel titlePanel; + + // Create the title panel for this dialog + titlePanel = new TitlePanel(title, description, icon, true); + + // Construct main panel w/ layout. + final JPanel mainPanel = new JPanel(); + mainPanel.setLayout(new BorderLayout()); + mainPanel.add(titlePanel, BorderLayout.NORTH); + + // The user should only be able to close this dialog. + Object[] options = {"Close"}; + pane = new JOptionPane(comp, JOptionPane.PLAIN_MESSAGE, JOptionPane.OK_CANCEL_OPTION, null, options, options[0]); + + mainPanel.add(pane, BorderLayout.CENTER); + + JOptionPane p = new JOptionPane(); + dlg = p.createDialog(parent, title); + dlg.setModal(modal); + + dlg.pack(); + dlg.setSize(width, height); + dlg.setResizable(true); + dlg.setContentPane(mainPanel); + dlg.setLocationRelativeTo(parent); + + PropertyChangeListener changeListener = new PropertyChangeListener() { + public void propertyChange(PropertyChangeEvent e) { + String value = (String)pane.getValue(); + if ("Close".equals(value)) { + dlg.setVisible(false); + } + } + }; + + pane.addPropertyChangeListener(changeListener); + + dlg.setVisible(true); + dlg.toFront(); + dlg.requestFocus(); + return dlg; + } + + /** + * Returns the String representation of a StackTrace. + * + * @param aThrowable the throwable object. + * @return the string. + */ + public static String getStackTrace(Throwable aThrowable) { + final Writer result = new StringWriter(); + final PrintWriter printWriter = new PrintWriter(result); + aThrowable.printStackTrace(printWriter); + return result.toString(); + } + + /** + * Defines a custom format for the stack trace as String. + * + * @param heading the title of the stack trace. + * @param aThrowable the throwable object. + * @return the string. + */ + public static String getCustomStackTrace(String heading, Throwable aThrowable) { + //add the class name and any message passed to constructor + final StringBuffer result = new StringBuffer(heading); + result.append(aThrowable.toString()); + final String lineSeperator = System.getProperty("line.separator"); + result.append(lineSeperator); + + //add each element of the stack trace + StackTraceElement[] stackTrace = aThrowable.getStackTrace(); + final List traceElements = Arrays.asList(stackTrace); + final Iterator traceElementsIter = traceElements.iterator(); + while (traceElementsIter.hasNext()) { + result.append(traceElementsIter.next()); + result.append(lineSeperator); + } + return result.toString(); + } + + +} \ No newline at end of file diff --git a/src/java/org/jivesoftware/spark/component/Notifications.java b/src/java/org/jivesoftware/spark/component/Notifications.java new file mode 100644 index 00000000..2284ec25 --- /dev/null +++ b/src/java/org/jivesoftware/spark/component/Notifications.java @@ -0,0 +1,390 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.component; + +import org.jdesktop.jdic.tray.SystemTray; +import org.jdesktop.jdic.tray.TrayIcon; +import org.jivesoftware.MainWindowListener; +import org.jivesoftware.Spark; +import org.jivesoftware.resource.SparkRes; +import org.jivesoftware.smack.packet.Presence; +import org.jivesoftware.spark.SparkManager; +import org.jivesoftware.spark.Workspace; +import org.jivesoftware.spark.ui.PresenceListener; +import org.jivesoftware.spark.ui.status.StatusBar; +import org.jivesoftware.spark.ui.status.StatusItem; + +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.BorderFactory; +import javax.swing.ImageIcon; +import javax.swing.JFrame; +import javax.swing.JMenu; +import javax.swing.JMenuItem; +import javax.swing.JPanel; +import javax.swing.JPopupMenu; +import javax.swing.JScrollPane; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.Frame; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.Point; +import java.awt.Toolkit; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.util.Iterator; + +/** + * Handles tray icon operations inside of Spark. Use to display incoming chat requests, incoming messages + * and general notifications. + */ +public final class Notifications implements ActionListener, MainWindowListener { + private ImageIcon availableIcon; + private ImageIcon unavaliableIcon; + private ImageIcon busyIcon; + private TrayIcon trayIcon; + private JPopupMenu notificationDialog; + private WrappedLabel messageLabel = new WrappedLabel(); + + + private final JMenuItem openMenu = new JMenuItem("Open"); + private final JMenuItem hideMenu = new JMenuItem("Hide"); + private final JMenuItem exitMenu = new JMenuItem("Exit"); + + // Define DND MenuItems + private final JMenu statusMenu = new JMenu("Status"); + + private boolean isDisposed; + private ImageTitlePanel headerLabel = new ImageTitlePanel(); + + private JFrame hideWindow = null; + + /** + * Creates a new instance of notifications. + */ + public Notifications() { + // Tray only works in windows. + if (Spark.isMac()) { + return; + } + + SystemTray tray = SystemTray.getDefaultSystemTray(); + setupNotificationDialog(); + + availableIcon = SparkRes.getImageIcon(SparkRes.MAIN_IMAGE); + unavaliableIcon = SparkRes.getImageIcon(SparkRes.MESSAGE_AWAY); + busyIcon = SparkRes.getImageIcon(SparkRes.MESSAGE_DND); + trayIcon = new TrayIcon(availableIcon); + trayIcon.setToolTip("Spark"); + + JPopupMenu popupMenu = new JPopupMenu("Tray Information"); + + // Add DND Menus + addStatusMenuItems(); + + // Add Open Menu + openMenu.setFont(new Font("Dialog", Font.BOLD, 11)); + popupMenu.add(openMenu); + + // Add Hide Menu + popupMenu.add(hideMenu); + popupMenu.addSeparator(); + // Add Spark Home Menu + + popupMenu.add(statusMenu); + + // Add Listeners + openMenu.addActionListener(this); + hideMenu.addActionListener(this); + + + Action logoutAction = new AbstractAction() { + public void actionPerformed(ActionEvent actionEvent) { + SparkManager.getMainWindow().logout(); + } + }; + + logoutAction.putValue(Action.NAME, "Logout"); + + if (Spark.isWindows()) { + popupMenu.add(logoutAction); + } + // Add Exit Menu + popupMenu.add(exitMenu); + exitMenu.addActionListener(this); + + SparkManager.getMainWindow().addMainWindowListener(this); + + trayIcon.setPopupMenu(popupMenu); + trayIcon.addActionListener(this); + tray.addTrayIcon(trayIcon); + + SparkManager.getSessionManager().addPresenceListener(new PresenceListener() { + public void presenceChanged(Presence presence) { + changePresence(presence); + } + }); + } + + /** + * Change the presence of the tray. + * + * @param presence the new presence. + */ + public void changePresence(Presence presence) { + if (Spark.isMac()) { + return; + } + + if (presence.getMode() == Presence.Mode.AVAILABLE || presence.getMode() == Presence.Mode.CHAT) { + trayIcon.setIcon(availableIcon); + } + else if (presence.getMode() == Presence.Mode.AWAY || presence.getMode() == Presence.Mode.EXTENDED_AWAY) { + trayIcon.setIcon(unavaliableIcon); + } + else { + trayIcon.setIcon(busyIcon); + } + } + + /** + * Displays an incoming message. + * + * @param from who the message was from. + * @param message the message body. + */ + public void showMessageReceived(String from, String message) { + try { + if (isDisposed) { + return; + } + if (Spark.isMac()) { + return; + } + trayIcon.getLocationOnScreen(); + showNotificationDialog("New message from " + from, message); + } + catch (Exception e) { + } + } + + /** + * Displays an incoming message. + * + * @param title the title to use. + * @param message the message to display. + */ + public void showMessage(String title, String message) { + try { + if (isDisposed) { + return; + } + if (Spark.isMac()) { + return; + } + showNotificationDialog(title, message); + } + catch (Exception e) { + } + } + + + /** + * Notify agent that there is a new incoming chat request. + */ + public final void showIncomingRequest() { + // Make sure we only show when minimized or not in focus + if (SparkManager.getMainWindow().isInFocus()) { + return; + } + + showNotificationDialog("Incoming Request", "You have a new incoming chat request."); + } + + /** + * Stops the icon from flashing. + */ + public void stopFlashing() { + Workspace workspace = SparkManager.getWorkspace(); + + StatusBar statusBox = workspace.getStatusBar(); + Presence presence = statusBox.getPresence(); + + changePresence(presence); + + } + + public void actionPerformed(ActionEvent e) { + Object o = e.getSource(); + if (!(o instanceof JMenuItem)) { + showMainWindow(); + return; + } + + final JMenuItem item = (JMenuItem)e.getSource(); + if (item == openMenu) { + showMainWindow(); + } + else if (item == hideMenu) { + SparkManager.getMainWindow().setVisible(false); + hideMenu.setEnabled(false); + } + else if (item == exitMenu) { + isDisposed = true; + SparkManager.getMainWindow().shutdown(); + } + else { + final String status = item.getText(); + + // Change Status + Workspace workspace = SparkManager.getWorkspace(); + StatusItem statusItem = workspace.getStatusBar().getStatusItem(status); + if (statusItem != null) { + SparkManager.getSessionManager().changePresence(statusItem.getPresence()); + } + } + } + + /** + * Brings the MainWindow to the front. + */ + private void showMainWindow() { + if (hideWindow != null) { + hideWindow.dispose(); + } + + Workspace workspace = SparkManager.getWorkspace(); + + SparkManager.getMainWindow().setState(Frame.NORMAL); + SparkManager.getMainWindow().setVisible(true); + + StatusBar statusBox = workspace.getStatusBar(); + Presence presence = statusBox.getPresence(); + if (presence.getMode() != Presence.Mode.AVAILABLE) { + SparkManager.getSessionManager().changePresence(presence); + } + + + notificationDialog.setVisible(false); + + hideMenu.setEnabled(true); + } + + + /** + * Add all Registered DND's to MenuItems. + */ + private void addStatusMenuItems() { + Workspace workspace = SparkManager.getWorkspace(); + StatusBar statusBar = workspace.getStatusBar(); + + Iterator iter = statusBar.getStatusList().iterator(); + while (iter.hasNext()) { + StatusItem item = (StatusItem)iter.next(); + final JMenuItem menuItem = new JMenuItem(item.getText(), item.getIcon()); + menuItem.addActionListener(this); + statusMenu.add(menuItem); + } + } + + public void shutdown() { + } + + public void mainWindowActivated() { + } + + public void mainWindowDeactivated() { + } + + /** + * Setup notification dialog. + */ + private void setupNotificationDialog() { + notificationDialog = new JPopupMenu(); + notificationDialog.setFocusable(false); + notificationDialog.setLayout(new BorderLayout()); + + final Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); + + Point newLoc = new Point((int)screenSize.getWidth() - 200, (int)screenSize.getHeight() - 150); + + JPanel mainPanel = new JPanel(); + mainPanel.setBackground(Color.white); + mainPanel.setLayout(new GridBagLayout()); + mainPanel.setBorder(BorderFactory.createLineBorder(Color.black, 1)); + + // Add Header Label + mainPanel.add(headerLabel, new GridBagConstraints(0, 0, 1, 1, 1.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0)); + + // Add Message Label + final JScrollPane messageScroller = new JScrollPane(messageLabel); + mainPanel.add(messageScroller, new GridBagConstraints(0, 2, 1, 1, 1.0, 1.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(5, 5, 5, 5), 0, 0)); + messageScroller.setBackground(Color.white); + messageScroller.setForeground(Color.white); + messageLabel.setBackground(Color.white); + messageScroller.getViewport().setBackground(Color.white); + + // JButton okButton = new JButton("Ok"); + mainPanel.setPreferredSize(new Dimension(200, 150)); + + headerLabel.setTitle("Jive Spark"); + headerLabel.setTitleFont(new Font("Dialog", Font.BOLD, 10)); + + messageLabel.setFont(new Font("Dialog", Font.PLAIN, 11)); + + notificationDialog.add(mainPanel, BorderLayout.CENTER); + notificationDialog.pack(); + notificationDialog.setPreferredSize(new Dimension(200, 150)); + notificationDialog.setLocation(newLoc); + messageLabel.addMouseListener(new MouseAdapter() { + public void mouseClicked(MouseEvent e) { + notificationDialog.setVisible(false); + showMainWindow(); + } + }); + + mainPanel.addMouseListener(new MouseAdapter() { + public void mouseClicked(MouseEvent e) { + notificationDialog.setVisible(false); + showMainWindow(); + } + }); + } + + /** + * Shows notification dialog for incoming messages. + * + * @param from who the body is from. + * @param body the body of the body. + */ + public void showNotificationDialog(String from, String body) { + // Tray only works in windows and mac. + if (!Spark.isWindows()) { + + return; + } + + //trayIcon.displayMessage(from, body, TrayIcon.INFO_MESSAGE_TYPE); + } + + public void showWindow(JPanel panel, int timeout) { + + } + + +} diff --git a/src/java/org/jivesoftware/spark/component/ProgressIcon.java b/src/java/org/jivesoftware/spark/component/ProgressIcon.java new file mode 100644 index 00000000..9ac4fd39 --- /dev/null +++ b/src/java/org/jivesoftware/spark/component/ProgressIcon.java @@ -0,0 +1,46 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.component; + +import javax.swing.Icon; + +import java.awt.Color; +import java.awt.Component; +import java.awt.Graphics; + +/** + * Displays a bar graph based on a percentage or relevance up to 100%. + */ +public class ProgressIcon implements Icon { + private int percent; + + /** + * Create new ProgressIcon. + * + * @param percent the percentage to display. + */ + public ProgressIcon(int percent) { + this.percent = percent; + } + + public int getIconHeight() { + return 10; + } + + public int getIconWidth() { + return percent; + } + + public void paintIcon(Component c, Graphics g, int x, int y) { + g.setColor(Color.blue); + g.fillRect(x, y, getIconWidth(), getIconHeight());//To change body of implemented methods use File | Settings | File Templates. + } +} diff --git a/src/java/org/jivesoftware/spark/component/RolloverButton.java b/src/java/org/jivesoftware/spark/component/RolloverButton.java new file mode 100644 index 00000000..fba993a0 --- /dev/null +++ b/src/java/org/jivesoftware/spark/component/RolloverButton.java @@ -0,0 +1,81 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.component; + +import javax.swing.Icon; +import javax.swing.JButton; + +import java.awt.Insets; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; + +/** + * Button UI for handling of rollover buttons. + */ +public class RolloverButton extends JButton { + + /** + * Create a new RolloverButton. + */ + public RolloverButton() { + decorate(); + } + + /** + * Create a new RolloverButton. + * + * @param icon the icon to use on the button. + */ + public RolloverButton(Icon icon) { + super(icon); + decorate(); + } + + /** + * Create a new RolloverButton. + * + * @param text the button text. + * @param icon the button icon. + */ + public RolloverButton(String text, Icon icon) { + super(text, icon); + decorate(); + } + + + /** + * Decorates the button with the approriate UI configurations. + */ + private void decorate() { + setBorderPainted(false); + setOpaque(true); + + setContentAreaFilled(false); + setMargin(new Insets(1, 1, 1, 1)); + + addMouseListener(new MouseAdapter() { + public void mouseEntered(MouseEvent e) { + if (isEnabled()) { + setBorderPainted(true); + setContentAreaFilled(true); + } + } + + public void mouseExited(MouseEvent e) { + setBorderPainted(false); + setContentAreaFilled(false); + } + }); + + } + + +} \ No newline at end of file diff --git a/src/java/org/jivesoftware/spark/component/RosterTree.java b/src/java/org/jivesoftware/spark/component/RosterTree.java new file mode 100644 index 00000000..6debc5dc --- /dev/null +++ b/src/java/org/jivesoftware/spark/component/RosterTree.java @@ -0,0 +1,168 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.component; + +import org.jivesoftware.resource.SparkRes; +import org.jivesoftware.smack.Roster; +import org.jivesoftware.smack.RosterEntry; +import org.jivesoftware.smack.RosterGroup; +import org.jivesoftware.smack.RosterListener; +import org.jivesoftware.smack.XMPPConnection; +import org.jivesoftware.smack.packet.Presence; +import org.jivesoftware.spark.SparkManager; + +import javax.swing.BorderFactory; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.tree.DefaultTreeModel; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + + +public final class RosterTree extends JPanel { + private final JiveTreeNode rootNode = new JiveTreeNode("Contact List"); + private final Tree rosterTree; + private final Map addressMap = new HashMap(); + private boolean showUnavailableAgents = true; + + /** + * Creates a new Roster Tree. + */ + public RosterTree() { + rootNode.setAllowsChildren(true); + rosterTree = new Tree(rootNode); + rosterTree.setCellRenderer(new JiveTreeCellRenderer()); + buildFromRoster(); + setLayout(new BorderLayout()); + + final JPanel panel = new JPanel(); + panel.setLayout(new GridBagLayout()); + panel.setBackground(Color.white); + + final JScrollPane treeScroller = new JScrollPane(rosterTree); + treeScroller.setBorder(BorderFactory.createEmptyBorder()); + panel.add(treeScroller, new GridBagConstraints(0, 1, 1, 1, 1.0, 1.0, GridBagConstraints.WEST, GridBagConstraints.BOTH, new Insets(5, 5, 5, 5), 0, 0)); + + add(panel, BorderLayout.CENTER); + for (int i = 0; i < rosterTree.getRowCount(); i++) { + rosterTree.expandRow(i); + } + } + + private void changePresence(String user, boolean available) { + final Iterator iter = addressMap.keySet().iterator(); + while (iter.hasNext()) { + final JiveTreeNode node = (JiveTreeNode)iter.next(); + final String nodeUser = (String)addressMap.get(node); + if (user.startsWith(nodeUser)) { + if (!available) { + node.setIcon(SparkRes.getImageIcon(SparkRes.CLEAR_BALL_ICON)); + } + else { + node.setIcon(SparkRes.getImageIcon(SparkRes.GREEN_BALL)); + } + } + } + } + + private void buildFromRoster() { + final XMPPConnection xmppCon = SparkManager.getConnection(); + final Roster roster = xmppCon.getRoster(); + + roster.addRosterListener(new RosterListener() { + public void entriesAdded(Collection addresses) { + + } + + public void entriesUpdated(Collection addresses) { + + } + + public void entriesDeleted(Collection addresses) { + + } + + public void presenceChanged(String user) { + Presence presence = roster.getPresence(user); + changePresence(user, presence != null && presence.getMode() == Presence.Mode.AVAILABLE); + + } + }); + + + final Iterator iter = roster.getGroups(); + while (iter.hasNext()) { + final RosterGroup group = (RosterGroup)iter.next(); + + + final JiveTreeNode groupNode = new JiveTreeNode(group.getName(), true); + groupNode.setAllowsChildren(true); + if (group.getEntryCount() > 0) { + rootNode.add(groupNode); + } + + Iterator entries = group.getEntries(); + while (entries.hasNext()) { + final RosterEntry entry = (RosterEntry)entries.next(); + String name = entry.getName(); + if (name == null) { + name = entry.getUser(); + } + + + final JiveTreeNode entryNode = new JiveTreeNode(name, false); + final Presence p = roster.getPresence(entry.getUser()); + addressMap.put(entryNode, entry.getUser()); + if (p != null && p.getType() == Presence.Type.AVAILABLE && p.getMode() == Presence.Mode.AVAILABLE) { + groupNode.add(entryNode); + } + else if ((p == null || p.getType() == Presence.Type.UNAVAILABLE) && showUnavailableAgents) { + groupNode.add(entryNode); + } + + changePresence(entry.getUser(), p != null); + final DefaultTreeModel model = (DefaultTreeModel)rosterTree.getModel(); + model.nodeStructureChanged(groupNode); + } + } + } + + /** + * Returns the Tree representation of the Roster Tree. + * + * @return the tree representation of the Roster Tree. + */ + public Tree getRosterTree() { + return rosterTree; + } + + /** + * Returns the selected agent node userobject. + * + * @param node the JiveTreeNode. + * @return the selected agent nodes userobject. + */ + public String getJID(JiveTreeNode node) { + return (String)addressMap.get(node); + } + + public String toString() { + return "Roster"; + } +} diff --git a/src/java/org/jivesoftware/spark/component/Table.java b/src/java/org/jivesoftware/spark/component/Table.java new file mode 100644 index 00000000..263c3e97 --- /dev/null +++ b/src/java/org/jivesoftware/spark/component/Table.java @@ -0,0 +1,472 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.component; + +import org.jivesoftware.spark.util.GraphicUtils; + +import javax.swing.BorderFactory; +import javax.swing.DefaultCellEditor; +import javax.swing.Icon; +import javax.swing.JButton; +import javax.swing.JComboBox; +import javax.swing.JLabel; +import javax.swing.JTable; +import javax.swing.JTextArea; +import javax.swing.ListSelectionModel; +import javax.swing.border.Border; +import javax.swing.table.DefaultTableModel; +import javax.swing.table.JTableHeader; +import javax.swing.table.TableCellRenderer; + +import java.awt.Color; +import java.awt.Component; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; +import java.awt.event.MouseEvent; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +/** + * Table class can be used to maintain quality look and feel + * throughout the product. This is mainly from the rendering capabilities. + * + * @version 1.0, 03/12/14 + */ +public abstract class Table extends JTable { + private Table.JiveTableModel tableModel; + + /** + * Define the color of row and column selections. + */ + public static final Color SELECTION_COLOR = new Color(166, 202, 240); + + /** + * Define the color used in the tooltips. + */ + public static final Color TOOLTIP_COLOR = new Color(166, 202, 240); + + private final Map objectMap = new HashMap(); + + /** + * Empty Constructor. + */ + protected Table() { + } + + public String getToolTipText(MouseEvent e) { + int r = rowAtPoint(e.getPoint()); + int c = columnAtPoint(e.getPoint()); + Object value = null; + try { + value = getValueAt(r, c); + } + catch (Exception e1) { + // If we encounter a row that should not actually exist and therefore + // has a null value. Just return an empty string for the tooltip. + return ""; + } + + String tooltipValue = null; + + if (value instanceof JLabel) { + tooltipValue = ((JLabel)value).getToolTipText(); + } + + if (value instanceof JLabel && tooltipValue == null) { + tooltipValue = ((JLabel)value).getText(); + } + else if (value != null && tooltipValue == null) { + tooltipValue = value.toString(); + } + else if (tooltipValue == null) { + tooltipValue = ""; + } + + String tooltip = GraphicUtils.createToolTip(tooltipValue); + + return tooltip; + } + + // Handle image rendering correctly + public TableCellRenderer getCellRenderer(int row, int column) { + Object o = getValueAt(row, column); + if (o != null) { + if (o instanceof JLabel) { + return new JLabelRenderer(false); + } + } + return super.getCellRenderer(row, column); + } + + /** + * Creates a table using the specified table headers. + * + * @param headers the table headers to use. + */ + protected Table(String[] headers) { + tableModel = new Table.JiveTableModel(headers, 0, false); + setModel(tableModel); + + // Handle JDK 1.5 bug with preferred size on table headers. + JTableHeader header = getTableHeader(); + Dimension dim = header.getPreferredSize(); + dim.height = 20; + header.setPreferredSize(dim); + + + getTableHeader().setReorderingAllowed(false); + setGridColor(Color.white); + setRowHeight(20); + getColumnModel().setColumnMargin(0); + setSelectionBackground(SELECTION_COLOR); + setSelectionForeground(Color.black); + setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + + this.addKeyListener(new KeyListener() { + public void keyPressed(KeyEvent e) { + if (e.getKeyChar() == KeyEvent.VK_ENTER) { + e.consume(); + enterPressed(); + } + } + + public void keyReleased(KeyEvent e) { + } + + public void keyTyped(KeyEvent e) { + + } + }); + } + + public Component prepareRenderer(TableCellRenderer renderer, + int rowIndex, int vColIndex) { + Component c = super.prepareRenderer(renderer, rowIndex, vColIndex); + if (rowIndex % 2 == 0 && !isCellSelected(rowIndex, vColIndex)) { + c.setBackground(getBackground()); + } + else if (isCellSelected(rowIndex, vColIndex)) { + c.setBackground(SELECTION_COLOR); + } + else { + // If not shaded, match the table's background + c.setBackground(getBackground()); + //c.setBackground(new Color(217, 232, 250)); + } + return c; + } + + + /** + * Adds a list to the table model. + * + * @param list the list to add to the model. + */ + public void add(List list) { + final Iterator iter = list.iterator(); + while (iter.hasNext()) { + Object[] newRow = (Object[])iter.next(); + tableModel.addRow(newRow); + } + } + + /** + * Get the object array of a row. + * + * @return the object array of a row. + */ + public Object[] getSelectedRowObject() { + return getRowObject(getSelectedRow()); + } + + /** + * Returns the object[] of a row. + * + * @param selectedRow the row to retrieve. + * @return the object[] of a row. + */ + public Object[] getRowObject(int selectedRow) { + if (selectedRow < 0) { + return null; + } + + int columnCount = getColumnCount(); + + Object[] obj = new Object[columnCount]; + for (int j = 0; j < columnCount; j++) { + Object objs = tableModel.getValueAt(selectedRow, j); + obj[j] = objs; + } + + return obj; + } + + /** + * Removes all columns and rows from table. + */ + public void clearTable() { + int rowCount = getRowCount(); + for (int i = 0; i < rowCount; i++) { + getTableModel().removeRow(0); + } + } + + /** + * The internal Table Model. + */ + public static class JiveTableModel extends DefaultTableModel { + private boolean isEditable; + + /** + * Use the JiveTableModel in order to better handle the table. This allows + * for consistency throughout the product. + * + * @param columnNames - String array of columnNames + * @param numRows - initial number of rows + * @param isEditable - true if the cells are editable, false otherwise. + */ + public JiveTableModel(Object[] columnNames, int numRows, boolean isEditable) { + super(columnNames, numRows); + this.isEditable = isEditable; + } + + /** + * Returns true if cell is editable. + * + * @param row the row to check. + * @param column the column to check. + * @return true if the cell is editable. + */ + public boolean isCellEditable(int row, int column) { + return isEditable; + } + } + + /** + * A swing renderer used to display labels within a table. + */ + public class JLabelRenderer extends JLabel implements TableCellRenderer { + Border unselectedBorder; + Border selectedBorder; + boolean isBordered = true; + + /** + * JLabelConstructor to build ui. + * + * @param isBordered true if the border should be shown. + */ + public JLabelRenderer(boolean isBordered) { + setOpaque(true); + this.isBordered = isBordered; + } + + public Component getTableCellRendererComponent(JTable table, Object color, boolean isSelected, boolean hasFocus, int row, int column) { + final String text = ((JLabel)color).getText(); + if (text != null) { + setText(" " + text); + } + final Icon icon = ((JLabel)color).getIcon(); + setIcon(icon); + + if (isSelected) { + setForeground(table.getSelectionForeground()); + setBackground(table.getSelectionBackground()); + } + else { + setForeground(Color.black); + setBackground(Color.white); + if (row % 2 == 0) { + //setBackground( new Color( 156, 207, 255 ) ); + } + } + + if (isBordered) { + if (isSelected) { + if (selectedBorder == null) { + selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5, + table.getSelectionBackground()); + } + setBorder(selectedBorder); + } + else { + if (unselectedBorder == null) { + unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5, + table.getBackground()); + } + setBorder(unselectedBorder); + } + } + return this; + } + } + + /** + * A swing renderer to dispaly Textareas within a table. + */ + public class TextAreaCellRenderer extends JTextArea implements TableCellRenderer { + + /** + * Create new renderer with font. + * + * @param font the font to use in the renderer. + */ + public TextAreaCellRenderer(Font font) { + setLineWrap(true); + setWrapStyleWord(true); + setFont(font); + } + + public Component getTableCellRendererComponent(JTable jTable, Object obj, boolean isSelected, boolean hasFocus, + int row, int column) { + // set color & border here + setText(obj == null ? "" : obj.toString()); + setSize(jTable.getColumnModel().getColumn(column).getWidth(), + getPreferredSize().height); + if (jTable.getRowHeight(row) != getPreferredSize().height) { + jTable.setRowHeight(row, getPreferredSize().height); + } + return this; + } + } + + /** + * A swing renderer used to display Buttons within a table. + */ + public class JButtonRenderer extends JButton implements TableCellRenderer { + Border unselectedBorder; + Border selectedBorder; + boolean isBordered = true; + + /** + * Empty Constructor. + */ + public JButtonRenderer() { + } + + + public Component getTableCellRendererComponent(JTable table, Object color, boolean isSelected, boolean hasFocus, int row, int column) { + final String text = ((JButton)color).getText(); + setText(text); + + final Icon icon = ((JButton)color).getIcon(); + setIcon(icon); + + if (isSelected) { + setForeground(table.getSelectionForeground()); + setBackground(table.getSelectionBackground()); + } + else { + setForeground(Color.black); + setBackground(Color.white); + if (row % 2 == 0) { + //setBackground( new Color( 156, 207, 255 ) ); + } + } + + if (isBordered) { + if (isSelected) { + if (selectedBorder == null) { + selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5, + table.getSelectionBackground()); + } + setBorder(selectedBorder); + } + else { + if (unselectedBorder == null) { + unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5, + table.getBackground()); + } + setBorder(unselectedBorder); + } + } + return this; + } + } + + public class ComboBoxRenderer extends JComboBox implements TableCellRenderer { + public ComboBoxRenderer() { + + } + + public ComboBoxRenderer(String[] items) { + super(items); + } + + public Component getTableCellRendererComponent(JTable table, Object value, + boolean isSelected, boolean hasFocus, int row, int column) { + if (isSelected) { + setForeground(table.getSelectionForeground()); + super.setBackground(table.getSelectionBackground()); + } + else { + setForeground(table.getForeground()); + setBackground(table.getBackground()); + } + + // Select the current value + setSelectedItem(value); + return this; + } + } + + public class MyComboBoxEditor extends DefaultCellEditor { + public MyComboBoxEditor(String[] items) { + super(new JComboBox(items)); + } + } + + /** + * Returns the table model. + * + * @return the table model. + */ + public Table.JiveTableModel getTableModel() { + return tableModel; + } + + /** + * Clears all objects from map. + */ + public void clearObjectMap() { + objectMap.clear(); + } + + /** + * Associate an object with a row. + * + * @param row - the current row + * @param object - the object to associate with the row. + */ + public void addObject(int row, Object object) { + objectMap.put(new Integer(row), object); + } + + /** + * Returns the associated row object. + * + * @param row - the row associated with the object. + * @return The object associated with the row. + */ + public Object getObject(int row) { + return objectMap.get(new Integer(row)); + } + + /** + * Override to handle when enter is pressed. + */ + public void enterPressed() { + } + +} \ No newline at end of file diff --git a/src/java/org/jivesoftware/spark/component/TimeTrackingLabel.java b/src/java/org/jivesoftware/spark/component/TimeTrackingLabel.java new file mode 100644 index 00000000..83a12b61 --- /dev/null +++ b/src/java/org/jivesoftware/spark/component/TimeTrackingLabel.java @@ -0,0 +1,109 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.component; + +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.Timer; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.Date; + +/** + * UI to show the time in a chat room. + */ +public class TimeTrackingLabel extends JLabel { + private Date startTime; + private JComponent parentComponent; + + private final String HOURS = "h"; + private final String MINUTES = "min"; + private final String SECONDS = "sec"; + + private final long MS_IN_A_DAY = 1000 * 60 * 60 * 24; + private final long MS_IN_AN_HOUR = 1000 * 60 * 60; + private final long MS_IN_A_MINUTE = 1000 * 60; + private final long MS_IN_A_SECOND = 1000; + final Timer timer; + + /** + * Construct a new Label using the start time. + * + * @param startingTime the start time. + * @param parent the parent component. + */ + public TimeTrackingLabel(Date startingTime, JComponent parent) { + startTime = startingTime; + parentComponent = parent; + + // Set default + setText("0 sec"); + + ActionListener updateTime = new ActionListener() { + public void actionPerformed(ActionEvent evt) { + Date currentTime = new Date(); + long diff = currentTime.getTime() - startTime.getTime(); + //long numDays = diff / MS_IN_A_DAY; + diff = diff % MS_IN_A_DAY; + long numHours = diff / MS_IN_AN_HOUR; + diff = diff % MS_IN_AN_HOUR; + long numMinutes = diff / MS_IN_A_MINUTE; + diff = diff % MS_IN_A_MINUTE; + long numSeconds = diff / MS_IN_A_SECOND; + diff = diff % MS_IN_A_SECOND; + //long numMilliseconds = diff; + + StringBuffer buf = new StringBuffer(); + if (numHours > 0) { + buf.append(numHours).append(" ").append(HOURS).append(", "); + } + + if (numMinutes > 0) { + buf.append(numMinutes).append(" ").append(MINUTES).append(", "); + } + + buf.append(numSeconds).append(" ").append(SECONDS); + + String result = buf.toString(); + setText(result); + parentComponent.invalidate(); + parentComponent.repaint(); + } + }; + + timer = new Timer(1000, updateTime); + + timer.start(); + } + + /** + * Returns the total length of the session in milliseconds. + * + * @return long - milliseconds. + */ + public long getTotalTime() { + return startTime.getTime() - new Date().getTime(); + } + + /** + * Stop the clock. + */ + public void stopTimer() { + timer.stop(); + } + + public String toString() { + return getText(); + } + + +} \ No newline at end of file diff --git a/src/java/org/jivesoftware/spark/component/TitlePanel.java b/src/java/org/jivesoftware/spark/component/TitlePanel.java new file mode 100644 index 00000000..34af46f3 --- /dev/null +++ b/src/java/org/jivesoftware/spark/component/TitlePanel.java @@ -0,0 +1,113 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.component; + +import javax.swing.BorderFactory; +import javax.swing.Icon; +import javax.swing.JLabel; +import javax.swing.JPanel; + +import java.awt.Color; +import java.awt.Font; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; + +/** + * TitlePanel class is the top panel displayed in this application. This + * should be used to identify the application to users using a title, brief description, + * and the company's logo. + * + * @version 1.0, 03/12/14 + */ +public final class TitlePanel extends JPanel { + private final JLabel titleLabel = new JLabel(); + private final WrappedLabel descriptionLabel = new WrappedLabel(); + private final JLabel iconLabel = new JLabel(); + private final GridBagLayout gridBagLayout = new GridBagLayout(); + + /** + * Create a new TitlePanel. + * + * @param title the title to use with the panel. + * @param description the panel description. + * @param icon the icon to use with the panel. + * @param showDescription true if the descrption should be shown. + */ + public TitlePanel(String title, String description, Icon icon, boolean showDescription) { + + // Set the icon + iconLabel.setIcon(icon); + + // Set the title + setTitle(title); + + // Set the description + setDescription(description); + + setLayout(gridBagLayout); + + if (showDescription) { + add(iconLabel, new GridBagConstraints(2, 0, 1, 2, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + add(descriptionLabel, new GridBagConstraints(0, 1, 1, 1, 0.0, 0.0, GridBagConstraints.NORTHWEST, GridBagConstraints.HORIZONTAL, new Insets(0, 9, 5, 5), 0, 0)); + add(titleLabel, new GridBagConstraints(0, 0, 1, 1, 1.0, 0.0, GridBagConstraints.NORTHWEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); + setBackground(Color.white); + + titleLabel.setFont(new Font("Verdana", Font.BOLD, 11)); + descriptionLabel.setFont(new Font("Verdana", 0, 10)); + } + else { + final JPanel panel = new JPanel(); + panel.setBorder(BorderFactory.createEtchedBorder()); + + panel.setLayout(new GridBagLayout()); + panel.add(titleLabel, new GridBagConstraints(0, 0, 1, 1, 1.0, 0.0, GridBagConstraints.NORTHWEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); + panel.add(iconLabel, new GridBagConstraints(2, 0, 1, 2, 0.0, 0.0, GridBagConstraints.NORTHEAST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + + panel.setBackground(new Color(49, 106, 197)); + titleLabel.setFont(new Font("Verdana", Font.BOLD, 13)); + titleLabel.setForeground(Color.white); + descriptionLabel.setFont(new Font("Verdana", 0, 10)); + add(panel, new GridBagConstraints(0, 0, 1, 0, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(0, 5, 5, 5), 0, 0)); + } + + } + + + /** + * Set the icon for the panel. + * + * @param icon - the relative icon based on classpath. ex. /com/jivesoftware/images/Foo.gif. + */ + public final void setIcon(Icon icon) { + titleLabel.setIcon(icon); + } + + /** + * Set the main title for this panel. + * + * @param title - main title. + */ + public final void setTitle(String title) { + titleLabel.setText(title); + } + + /** + * Set a brief description which will be displayed below the main title. + * + * @param desc - brief description + */ + public final void setDescription(String desc) { + descriptionLabel.setText(desc); + } + + +} diff --git a/src/java/org/jivesoftware/spark/component/Tree.java b/src/java/org/jivesoftware/spark/component/Tree.java new file mode 100644 index 00000000..a11d899b --- /dev/null +++ b/src/java/org/jivesoftware/spark/component/Tree.java @@ -0,0 +1,159 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.component; + +import javax.swing.JComponent; +import javax.swing.JTree; +import javax.swing.TransferHandler; +import javax.swing.event.TreeSelectionEvent; +import javax.swing.event.TreeSelectionListener; +import javax.swing.tree.DefaultMutableTreeNode; +import javax.swing.tree.TreeNode; +import javax.swing.tree.TreePath; + +import java.awt.event.MouseEvent; +import java.awt.event.MouseMotionListener; +import java.util.Enumeration; + +/** + * Creates a new Tree UI to allow for easier manipulation of tree nodes. + * + * @see TreeFolder + * @see TreeItem + * @see JiveTreeNode + */ +public class Tree extends JTree implements TreeSelectionListener, MouseMotionListener { + + /** + * Creates the Tree from a root node. + * + * @param node - the root node to create a tree from. + */ + public Tree(DefaultMutableTreeNode node) { + super(node); + addMouseMotionListener(this); + putClientProperty("JTree.lineStyle", "Angled"); + } + + /** + * Reacts to changing tree nodes. + * + * @param e - the TreeSelectionEvent notifying of a valueChange. + */ + public void valueChanged(TreeSelectionEvent e) { + JiveTreeNode node = (JiveTreeNode)getLastSelectedPathComponent(); + + if (node == null) return; + + + if (node.isLeaf()) { + setTransferHandler(new TransferHandler("text")); + } + } + + /** + * Returns the last selected node in the tree. + * + * @return the last selected node in the tree. + */ + public JiveTreeNode getTreeNode() { + return (JiveTreeNode)getLastSelectedPathComponent(); + } + + /** + * Handles drag and drop. + * + * @param e - the mousedragged event to handle drag and drop from. + */ + public void mouseDragged(MouseEvent e) { + final JComponent c = (JComponent)e.getSource(); + JiveTreeNode node = (JiveTreeNode)getLastSelectedPathComponent(); + if (node == null) { + return; + } + if (node.isLeaf()) { + TransferHandler handler = c.getTransferHandler(); + handler.exportAsDrag(c, e, TransferHandler.COPY); + } + } + + /** + * Finds the correct tree path. + * + * @param tree the tree to search. + * @param nodes the nodes to find in the tree. + * @return the treepath. + */ + public TreePath find(Tree tree, Object[] nodes) { + TreeNode root = (TreeNode)tree.getModel().getRoot(); + return find2(tree, new TreePath(root), nodes, 0, false); + } + + /** + * Finds the path in tree as specified by the array of names. The names array is a + * sequence of names where names[0] is the root and names[i] is a child of names[i-1]. + * Comparison is done using String.equals(). Returns null if not found. + * + * @param tree the tree to search. + * @param names a list of names to find. + * @return the treepath found. + */ + public TreePath findByName(Tree tree, String[] names) { + TreeNode root = (TreeNode)tree.getModel().getRoot(); + return find2(tree, new TreePath(root), names, 0, true); + } + + private TreePath find2(Tree tree, TreePath parent, Object[] nodes, int depth, boolean byName) { + TreeNode node = (TreeNode)parent.getLastPathComponent(); + Object o = node; + + // If by name, convert node to a string + if (byName) { + o = o.toString(); + } + + // If equal, go down the branch + if (o.equals(nodes[depth])) { + // If at end, return match + if (depth == nodes.length - 1) { + return parent; + } + + // Traverse children + if (node.getChildCount() >= 0) { + for (Enumeration e = node.children(); e.hasMoreElements();) { + TreeNode n = (TreeNode)e.nextElement(); + TreePath path = parent.pathByAddingChild(n); + TreePath result = find2(tree, path, nodes, depth + 1, byName); + // Found a match + if (result != null) { + return result; + } + } + } + } + + // No match at this branch + return null; + } + + public void mouseMoved(MouseEvent e) { + } + + /** + * Call to expand the entire tree. + */ + public void expandTree() { + for (int i = 0; i <= getRowCount(); i++) { + expandPath(getPathForRow(i)); + } + } +} \ No newline at end of file diff --git a/src/java/org/jivesoftware/spark/component/TreeFolder.java b/src/java/org/jivesoftware/spark/component/TreeFolder.java new file mode 100644 index 00000000..11e2f163 --- /dev/null +++ b/src/java/org/jivesoftware/spark/component/TreeFolder.java @@ -0,0 +1,101 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.component; + +import org.jivesoftware.spark.plugin.ContextMenuListener; + +import java.io.Serializable; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +public class TreeFolder implements Serializable { + private Set subFolders = new HashSet(); + private Set paletteItems = new HashSet(); + private String displayName; + private String description; + private String icon; + private ContextMenuListener listener; + + public TreeFolder() { + // Allow user the flexibilty to create + } + + public TreeFolder(String displayName, String description, String icon) { + this.displayName = displayName; + this.description = description; + this.icon = icon; + } + + public void addSubFolder(TreeFolder folder) { + subFolders.add(folder); + } + + public void removeSubFolder(TreeFolder folder) { + subFolders.remove(folder); + } + + public Iterator getSubFolders() { + return subFolders.iterator(); + } + + public void addPaletteItem(TreeItem item) { + paletteItems.add(item); + } + + public void removePaletteItem(TreeItem item) { + paletteItems.remove(item); + } + + public Iterator getPaletteItems() { + return paletteItems.iterator(); + } + + + public void setDisplayName(String displayName) { + this.displayName = displayName; + } + + + public String getDisplayName() { + return displayName; + } + + + public void setDescription(String description) { + this.description = description; + } + + + public String getDescription() { + return description; + } + + + public void setIcon(String icon) { + this.icon = icon; + } + + + public String getIcon() { + return icon; + } + + + public void setListener(ContextMenuListener listener) { + this.listener = listener; + } + + + public ContextMenuListener getListener() { + return listener; + } +} diff --git a/src/java/org/jivesoftware/spark/component/TreeItem.java b/src/java/org/jivesoftware/spark/component/TreeItem.java new file mode 100644 index 00000000..12f4b75b --- /dev/null +++ b/src/java/org/jivesoftware/spark/component/TreeItem.java @@ -0,0 +1,94 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.component; + +import java.io.Serializable; + +public class TreeItem implements Serializable { + private String displayName; + private String toolTip; + private String description; + private String editor; + private String extraData; + private int type; + + public TreeItem() { + } + + + public int getType() { + return type; + } + + public void setType(int type) { + this.type = type; + } + + + public TreeItem(String displayName, + String tooltip, + String description, + String editor) { + this.displayName = displayName; + this.toolTip = tooltip; + this.description = description; + this.editor = editor; + } + + public void setDisplayName(String displayName) { + this.displayName = displayName; + } + + + public String getDisplayName() { + return this.displayName; + } + + + public void setToolTip(String toolTip) { + this.toolTip = toolTip; + } + + + public String getToolTip() { + return this.toolTip; + } + + + public void setDescription(String description) { + this.description = description; + } + + + public String getDescription() { + return this.description; + } + + + public void setEditor(String editor) { + this.editor = editor; + } + + + public String getEditor() { + return this.editor; + } + + + public void setExtraData(String extraData) { + this.extraData = extraData; + } + + + public String getExtraData() { + return this.extraData; + } +} diff --git a/src/java/org/jivesoftware/spark/component/VerticalFlowLayout.java b/src/java/org/jivesoftware/spark/component/VerticalFlowLayout.java new file mode 100644 index 00000000..c8bbb9bd --- /dev/null +++ b/src/java/org/jivesoftware/spark/component/VerticalFlowLayout.java @@ -0,0 +1,276 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.component; + +import java.awt.Component; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.FlowLayout; +import java.awt.Insets; + +/** + * VerticalFlowLayout is similar to FlowLayout except it lays out components + * vertically. Extends FlowLayout because it mimics much of the + * behavior of the FlowLayout class, except vertically. An additional + * feature is that you can specify a fill to edge flag, which causes + * the VerticalFlowLayout manager to resize all components to expand to the + * column width Warning: This causes problems when the main panel + * has less space that it needs and it seems to prohibit multi-column + * output. Additionally there is a vertical fill flag, which fills the last + * component to the remaining height of the container. + */ +public class VerticalFlowLayout extends FlowLayout { + + /** + * Specify alignment top. + */ + public static final int TOP = 0; + + /** + * Specify a middle alignment. + */ + public static final int MIDDLE = 1; + + /** + * Specify the alignment to be bottom. + */ + public static final int BOTTOM = 2; + + int hgap; + int vgap; + boolean hfill; + boolean vfill; + + /** + * Construct a new VerticalFlowLayout with a middle alignment, and + * the fill to edge flag set. + */ + public VerticalFlowLayout() { + this(TOP, 5, 5, true, false); + } + + /** + * Construct a new VerticalFlowLayout with a middle alignment. + * + * @param hfill the fill to edge flag + * @param vfill the vertical fill in pixels. + */ + public VerticalFlowLayout(boolean hfill, boolean vfill) { + this(TOP, 5, 5, hfill, vfill); + } + + /** + * Construct a new VerticalFlowLayout with a middle alignment. + * + * @param align the alignment value + */ + public VerticalFlowLayout(int align) { + this(align, 5, 5, true, false); + } + + /** + * Construct a new VerticalFlowLayout. + * + * @param align the alignment value + * @param hfill the horizontalfill in pixels. + * @param vfill the vertical fill in pixels. + */ + public VerticalFlowLayout(int align, boolean hfill, boolean vfill) { + this(align, 5, 5, hfill, vfill); + } + + /** + * Construct a new VerticalFlowLayout. + * + * @param align the alignment value + * @param hgap the horizontal gap variable + * @param vgap the vertical gap variable + * @param hfill the fill to edge flag + * @param vfill true if the panel should vertically fill. + */ + public VerticalFlowLayout(int align, + int hgap, int vgap, + boolean hfill, boolean vfill) { + setAlignment(align); + this.hgap = hgap; + this.vgap = vgap; + this.hfill = hfill; + this.vfill = vfill; + } + + /** + * Returns the preferred dimensions given the components + * in the target container. + * + * @param target the component to lay out + */ + public Dimension preferredLayoutSize(Container target) { + Dimension tarsiz = new Dimension(0, 0); + + for (int i = 0; i < target.getComponentCount(); i++) { + Component m = target.getComponent(i); + if (m.isVisible()) { + Dimension d = m.getPreferredSize(); + tarsiz.width = Math.max(tarsiz.width, d.width); + if (i > 0) { + tarsiz.height += hgap; + } + tarsiz.height += d.height; + } + } + Insets insets = target.getInsets(); + tarsiz.width += insets.left + insets.right + hgap * 2; + tarsiz.height += insets.top + insets.bottom + vgap * 2; + return tarsiz; + } + + /** + * Returns the minimum size needed to layout the target container. + * + * @param target the component to lay out. + * @return the minimum layout dimension. + */ + public Dimension minimumLayoutSize(Container target) { + Dimension tarsiz = new Dimension(0, 0); + + for (int i = 0; i < target.getComponentCount(); i++) { + Component m = target.getComponent(i); + if (m.isVisible()) { + Dimension d = m.getMinimumSize(); + tarsiz.width = Math.max(tarsiz.width, d.width); + if (i > 0) { + tarsiz.height += vgap; + } + tarsiz.height += d.height; + } + } + Insets insets = target.getInsets(); + tarsiz.width += insets.left + insets.right + hgap * 2; + tarsiz.height += insets.top + insets.bottom + vgap * 2; + return tarsiz; + } + + /** + * Set true to fill vertically. + * + * @param vfill true to fill vertically. + */ + public void setVerticalFill(boolean vfill) { + this.vfill = vfill; + } + + /** + * Returns true if the layout vertically fills. + * + * @return true if vertically fills the layout using the specified. + */ + public boolean getVerticalFill() { + return vfill; + } + + /** + * Set to true to enable horizontally fill. + * + * @param hfill true to fill horizontally. + */ + public void setHorizontalFill(boolean hfill) { + this.hfill = hfill; + } + + /** + * Returns true if the layout horizontally fills. + * + * @return true if horizontally fills. + */ + public boolean getHorizontalFill() { + return hfill; + } + + /** + * places the components defined by first to last within the target + * container using the bounds box defined. + * + * @param target the container. + * @param x the x coordinate of the area. + * @param y the y coordinate of the area. + * @param width the width of the area. + * @param height the height of the area. + * @param first the first component of the container to place. + * @param last the last component of the container to place. + */ + private void placethem(Container target, int x, int y, int width, int height, + int first, int last) { + int align = getAlignment(); + if (align == this.MIDDLE) + y += height / 2; + if (align == this.BOTTOM) + y += height; + + for (int i = first; i < last; i++) { + Component m = target.getComponent(i); + Dimension md = m.getSize(); + if (m.isVisible()) { + int px = x + (width - md.width) / 2; + m.setLocation(px, y); + y += vgap + md.height; + } + } + } + + /** + * Lays out the container. + * + * @param target the container to lay out. + */ + public void layoutContainer(Container target) { + Insets insets = target.getInsets(); + int maxheight = target.getSize().height - (insets.top + insets.bottom + vgap * 2); + int maxwidth = target.getSize().width - (insets.left + insets.right + hgap * 2); + int numcomp = target.getComponentCount(); + int x = insets.left + hgap, y = 0; + int colw = 0, start = 0; + + for (int i = 0; i < numcomp; i++) { + Component m = target.getComponent(i); + if (m.isVisible()) { + Dimension d = m.getPreferredSize(); + // fit last component to remaining height + if ((this.vfill) && (i == (numcomp - 1))) { + d.height = Math.max((maxheight - y), m.getPreferredSize().height); + } + + // fit component size to container width + if (this.hfill) { + m.setSize(maxwidth, d.height); + d.width = maxwidth; + } + else { + m.setSize(d.width, d.height); + } + + if (y + d.height > maxheight) { + placethem(target, x, insets.top + vgap, colw, maxheight - y, start, i); + y = d.height; + x += hgap + colw; + colw = d.width; + start = i; + } + else { + if (y > 0) + y += vgap; + y += d.height; + colw = Math.max(colw, d.width); + } + } + } + placethem(target, x, insets.top + vgap, colw, maxheight - y, start, numcomp); + } +} diff --git a/src/java/org/jivesoftware/spark/component/WrappedLabel.java b/src/java/org/jivesoftware/spark/component/WrappedLabel.java new file mode 100644 index 00000000..825ebe3b --- /dev/null +++ b/src/java/org/jivesoftware/spark/component/WrappedLabel.java @@ -0,0 +1,39 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.component; + +import javax.swing.JTextArea; + +import java.awt.Dimension; + +/** + * Creates a simple Wrappable label to display Multi-Line Text. + * + * @author Derek DeMoro + */ +public class WrappedLabel extends JTextArea { + + /** + * Create a simple Wrappable label. + */ + public WrappedLabel() { + this.setEditable(false); + this.setWrapStyleWord(true); + this.setLineWrap(true); + this.setOpaque(false); + } + + public Dimension getPreferredSize() { + final Dimension size = super.getPreferredSize(); + size.width = 0; + return size; + } +} \ No newline at end of file diff --git a/src/java/org/jivesoftware/spark/component/borders/PartialLineBorder.java b/src/java/org/jivesoftware/spark/component/borders/PartialLineBorder.java new file mode 100644 index 00000000..a2942ca8 --- /dev/null +++ b/src/java/org/jivesoftware/spark/component/borders/PartialLineBorder.java @@ -0,0 +1,75 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.component.borders; + +import javax.swing.border.AbstractBorder; + +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Component; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Insets; + +/** + * Renders edge borders for a concave appearance. + * + * @author Derek DeMoro + */ +public class PartialLineBorder extends AbstractBorder { + private Color color; + private int thickness; + + boolean top, + left, + bottom, + right; + + public PartialLineBorder(Color color, int thickness) { + top = true; + left = true; + bottom = true; + right = true; + + this.color = color; + this.thickness = thickness; + + + } + + public boolean isBorderOpaque() { + return true; + } + + public Insets getBorderInsets(Component component) { + return new Insets(2, 2, 2, 2); + } + + public void paintBorder(Component component, Graphics g, int x, int y, int width, int height) { + Graphics2D g2 = (Graphics2D)g; + g2.setStroke(new BasicStroke(1.0f)); + g2.setColor(color); + + + if (top) { + g2.drawLine(x, y, x + width, y); + } + if (left) { + g2.drawLine(x, y, x, y + height); + } + if (bottom) { + g2.drawLine(x, y + height, x + width, y + height); + } + if (right) { + g2.drawLine(x + width, y, x + width, y + height); + } + } +} \ No newline at end of file diff --git a/src/java/org/jivesoftware/spark/component/browser/BrowserFactory.java b/src/java/org/jivesoftware/spark/component/browser/BrowserFactory.java new file mode 100644 index 00000000..2cfdfb60 --- /dev/null +++ b/src/java/org/jivesoftware/spark/component/browser/BrowserFactory.java @@ -0,0 +1,48 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.component.browser; + +import org.jivesoftware.Spark; +import org.jivesoftware.spark.component.HTMLViewer; + +/** + * Responsible for determining what type of Browser to return. On windows, + * either IE or Mozilla will be returned. Otherwise, we will return a simple + * HTMLViewer using JDK 1.4+ HTMLEditor. + */ +public class BrowserFactory { + + /** + * Empty Constructor. + */ + private BrowserFactory() { + + } + + /** + * Returns the Browser UI to use for system Spark is currently running on. + * + * @return the BrowserViewer. + * @see NativeBrowserViewer + * @see HTMLViewer + */ + public static BrowserViewer getBrowser() { + BrowserViewer browserViewer = new NativeBrowserViewer(); + if (Spark.isWindows()) { + + } + else { + + } + browserViewer.initializeBrowser(); + return browserViewer; + } +} diff --git a/src/java/org/jivesoftware/spark/component/browser/BrowserListener.java b/src/java/org/jivesoftware/spark/component/browser/BrowserListener.java new file mode 100644 index 00000000..75edc5d6 --- /dev/null +++ b/src/java/org/jivesoftware/spark/component/browser/BrowserListener.java @@ -0,0 +1,28 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.component.browser; + + +/** + * Implementation of BrowserListener allows for handling when documents + * have been downloaded via a BrowserViewer implementation. + * + * @authro Derek DeMoro + */ +public interface BrowserListener { + + /** + * Called when a document/page has been fully loaded. + * + * @param documentURL + */ + void documentLoaded(String documentURL); +} diff --git a/src/java/org/jivesoftware/spark/component/browser/BrowserViewer.java b/src/java/org/jivesoftware/spark/component/browser/BrowserViewer.java new file mode 100644 index 00000000..ea525150 --- /dev/null +++ b/src/java/org/jivesoftware/spark/component/browser/BrowserViewer.java @@ -0,0 +1,78 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.component.browser; + +import javax.swing.JPanel; + +import java.util.ArrayList; +import java.util.List; + +/** + * Abstract class describing a particular type of component capable of rendering html. + * + * @author Derek DeMoro + * @see NativeBrowserViewer + */ +public abstract class BrowserViewer extends JPanel { + private List listeners = new ArrayList(); + + /** + * Add a BrowserListener. + * + * @param listener the listener. + */ + public void addBrowserListener(BrowserListener listener) { + listeners.add(listener); + } + + /** + * Remove a BrowserListener. + * + * @param listener the listener. + */ + public void removeBrowserListener(BrowserListener listener) { + listeners.remove(listener); + } + + /** + * Fire all BrowserListeners. + * + * @param document the document that has been downloaded. + */ + public void fireBrowserListeners(String document) { + for (BrowserListener listener : listeners) { + listener.documentLoaded(document); + } + } + + + public void documentLoaded(String document) { + fireBrowserListeners(document); + } + + /** + * Should create the UI necessary to display html. + */ + public abstract void initializeBrowser(); + + /** + * Should load the given url. + * + * @param url the url to load. + */ + public abstract void loadURL(String url); + + /** + * Should go back in history one page. + */ + public abstract void goBack(); + +} diff --git a/src/java/org/jivesoftware/spark/component/browser/NativeBrowserViewer.java b/src/java/org/jivesoftware/spark/component/browser/NativeBrowserViewer.java new file mode 100644 index 00000000..7947e62d --- /dev/null +++ b/src/java/org/jivesoftware/spark/component/browser/NativeBrowserViewer.java @@ -0,0 +1,84 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.component.browser; + +import org.jdesktop.jdic.browser.WebBrowser; +import org.jdesktop.jdic.browser.WebBrowserEvent; +import org.jdesktop.jdic.browser.WebBrowserListener; +import org.jivesoftware.spark.util.log.Log; + +import java.awt.BorderLayout; +import java.net.MalformedURLException; +import java.net.URL; + +/** + * Implementation of BrowserViewer using Native Browsers (IE / Mozilla) + * + * @author Derek DeMoro + */ +class NativeBrowserViewer extends BrowserViewer implements WebBrowserListener { + private WebBrowser browser; + + public void initializeBrowser() { + browser = new WebBrowser(); + this.setLayout(new BorderLayout()); + + this.add(browser, BorderLayout.CENTER); + + browser.addWebBrowserListener(this); + + } + + public void loadURL(String url) { + try { + browser.setURL(new URL(url)); + } + catch (MalformedURLException e) { + Log.error(e); + } + } + + public void goBack() { + browser.back(); + } + + public void downloadStarted(WebBrowserEvent event) { + } + + public void downloadCompleted(WebBrowserEvent event) { + if (browser == null || browser.getURL() == null) { + return; + } + + String url = browser.getURL().toExternalForm(); + documentLoaded(url); + } + + public void downloadProgress(WebBrowserEvent event) { + + } + + public void downloadError(WebBrowserEvent event) { + + } + + public void documentCompleted(WebBrowserEvent event) { + + } + + public void titleChange(WebBrowserEvent event) { + + } + + public void statusTextChange(WebBrowserEvent event) { + + } +} diff --git a/src/java/org/jivesoftware/spark/component/browser/package.html b/src/java/org/jivesoftware/spark/component/browser/package.html new file mode 100644 index 00000000..96016882 --- /dev/null +++ b/src/java/org/jivesoftware/spark/component/browser/package.html @@ -0,0 +1,3 @@ +Provides support for showing browsers within Spark components. Users should use the BrowserFactory class to automatically have the browser most suited to the environment Spark is running in. +Support Windows, Mac, Linux and Solaris builds. + \ No newline at end of file diff --git a/src/java/org/jivesoftware/spark/component/package.html b/src/java/org/jivesoftware/spark/component/package.html new file mode 100644 index 00000000..be6b303e --- /dev/null +++ b/src/java/org/jivesoftware/spark/component/package.html @@ -0,0 +1 @@ +Provides useful Swing components that can be used within Spark. diff --git a/src/java/org/jivesoftware/spark/component/panes/CollapsiblePane.java b/src/java/org/jivesoftware/spark/component/panes/CollapsiblePane.java new file mode 100644 index 00000000..5e3d827b --- /dev/null +++ b/src/java/org/jivesoftware/spark/component/panes/CollapsiblePane.java @@ -0,0 +1,154 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.component.panes; + +import org.jivesoftware.spark.util.ModelUtil; + +import javax.swing.Icon; +import javax.swing.JButton; +import javax.swing.JFrame; +import javax.swing.JPanel; + +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * CollapsiblePane provides a component which can collapse or expand its content area + * with animation and fade in/fade out effects. It also acts as a standard container for + * other Swing components. + * + * @author Derek DeMoro + */ +public class CollapsiblePane extends JPanel { + + private CollapsibleTitlePane titlePane; + private JPanel mainPanel; + + private List listeners = new ArrayList(); + + private boolean subPane; + + public CollapsiblePane() { + setLayout(new BorderLayout()); + + titlePane = new CollapsibleTitlePane(); + mainPanel = new JPanel(); + + + add(titlePane, BorderLayout.NORTH); + add(mainPanel, BorderLayout.CENTER); + + mainPanel.setLayout(new BorderLayout()); + + titlePane.addMouseListener(new MouseAdapter() { + public void mouseClicked(MouseEvent e) { + if (e.isPopupTrigger()) { + return; + } + boolean isCollapsed = titlePane.isCollapsed(); + setCollapsed(!isCollapsed); + } + }); + } + + public CollapsiblePane(String title) { + this(); + setTitle(title); + } + + public void setTitle(String title) { + titlePane.setTitle(title); + } + + public void setIcon(Icon icon) { + titlePane.setIcon(icon); + } + + public void setCollapsed(boolean collapsed) { + titlePane.setCollapsed(collapsed); + mainPanel.setVisible(!collapsed); + + if (collapsed) { + firePaneCollapsed(); + } + else { + firePaneExpanded(); + } + } + + public void setContentPane(Component comp) { + mainPanel.add(comp); + } + + public void addCollapsiblePaneListener(CollapsiblePaneListener listener) { + listeners.add(listener); + } + + public void removeCollapsiblePaneListener(CollapsiblePaneListener listener) { + listeners.remove(listener); + } + + private void firePaneExpanded() { + final Iterator iter = ModelUtil.reverseListIterator(listeners.listIterator()); + while (iter.hasNext()) { + ((CollapsiblePaneListener)iter.next()).paneExpanded(); + } + } + + private void firePaneCollapsed() { + final Iterator iter = ModelUtil.reverseListIterator(listeners.listIterator()); + while (iter.hasNext()) { + ((CollapsiblePaneListener)iter.next()).paneCollapsed(); + } + } + + + public CollapsibleTitlePane getTitlePane() { + return titlePane; + } + + public boolean isCollapsed() { + return titlePane.isCollapsed(); + } + + public boolean isSubPane() { + return subPane; + } + + public void setSubPane(boolean subPane) { + this.subPane = subPane; + + titlePane.setSubPane(subPane); + } + + public static void main(String args[]) { + JFrame frame = new JFrame(); + + + CollapsiblePane pane = new CollapsiblePane(); + pane.setTitle("Jive Software"); + pane.setSubPane(true); + + pane.setContentPane(new JButton("HELLO")); + + frame.add(pane); + frame.pack(); + frame.setVisible(true); + + } + + +} diff --git a/src/java/org/jivesoftware/spark/component/panes/CollapsiblePaneListener.java b/src/java/org/jivesoftware/spark/component/panes/CollapsiblePaneListener.java new file mode 100644 index 00000000..abdfc1c0 --- /dev/null +++ b/src/java/org/jivesoftware/spark/component/panes/CollapsiblePaneListener.java @@ -0,0 +1,24 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.component.panes; + +/** + * Implementation of this interface is used for detecting when the CollapsiblePane + * expands and collapses. + * + * @author Derek DeMoro + */ +public interface CollapsiblePaneListener { + + void paneExpanded(); + + void paneCollapsed(); +} diff --git a/src/java/org/jivesoftware/spark/component/panes/CollapsibleTitlePane.java b/src/java/org/jivesoftware/spark/component/panes/CollapsibleTitlePane.java new file mode 100644 index 00000000..fbd8a667 --- /dev/null +++ b/src/java/org/jivesoftware/spark/component/panes/CollapsibleTitlePane.java @@ -0,0 +1,215 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.component.panes; + +import org.jivesoftware.Spark; +import org.jivesoftware.resource.Default; +import org.jivesoftware.resource.SparkRes; +import org.jivesoftware.spark.util.ColorUtil; +import org.jivesoftware.spark.util.GraphicUtils; +import org.jivesoftware.spark.util.log.Log; + +import javax.swing.Icon; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.UIManager; + +import java.awt.Color; +import java.awt.Font; +import java.awt.GradientPaint; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.util.StringTokenizer; + +/** + * Internal implementation of the Title pane in the northern region of a CollapsiblePane. + * + * @author Derek DeMoro + */ +public class CollapsibleTitlePane extends JPanel { + + private JLabel titleLabel; + private JLabel iconLabel; + + private JLabel preIconLabel; + + private boolean collapsed; + + private Color startColor; + private Color endColor; + + private Color titleColor; + private Font titleFont; + + private boolean subPane; + + public CollapsibleTitlePane() { + setLayout(new GridBagLayout()); + + titleColor = new Color(33, 93, 198); + titleFont = new Font("Dialog", Font.BOLD, 11); + + // Initialize color + startColor = Color.white; + endColor = new Color(198, 211, 247); + + titleLabel = new JLabel(); + iconLabel = new JLabel(); + + preIconLabel = new JLabel(); + + add(preIconLabel, new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, 10, 0, 0), 0, 0)); + + add(titleLabel, new GridBagConstraints(1, 0, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, 10, 0, 0), 0, 0)); + add(iconLabel, new GridBagConstraints(2, 0, 1, 1, 1.0, 0.0, GridBagConstraints.EAST, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0)); + + setCollapsed(false); + + Color customTitleColor = (Color)UIManager.get("CollapsiblePane.titleColor"); + if (customTitleColor != null) { + titleLabel.setForeground(customTitleColor); + } + else { + titleLabel.setForeground(titleColor); + } + + titleLabel.setFont(titleFont); + + addMouseListener(new MouseAdapter() { + public void mouseEntered(MouseEvent e) { + setCursor(GraphicUtils.HAND_CURSOR); + } + + public void mouseExited(MouseEvent e) { + setCursor(GraphicUtils.DEFAULT_CURSOR); + } + }); + + // Handle Custom Spark Job. + if (Spark.isCustomBuild()) { + titleColor = getColor(Default.getString(Default.TEXT_COLOR)); + String start = Default.getString(Default.CONTACT_GROUP_START_COLOR); + String end = Default.getString(Default.CONTACT_GROUP_END_COLOR); + startColor = getColor(start); + endColor = getColor(end); + } + + + } + + public void setIcon(Icon icon) { + titleLabel.setIcon(icon); + } + + public void setTitle(String title) { + titleLabel.setText(title); + } + + public boolean isCollapsed() { + return collapsed; + } + + public void setCollapsed(boolean collapsed) { + this.collapsed = collapsed; + + if (!isSubPane()) { + + if (collapsed) { + iconLabel.setIcon(SparkRes.getImageIcon(SparkRes.PANE_DOWN_ARROW_IMAGE)); + } + else { + iconLabel.setIcon(SparkRes.getImageIcon(SparkRes.PANE_UP_ARROW_IMAGE)); + } + } + else { + iconLabel.setIcon(null); + if (collapsed) { + preIconLabel.setIcon(SparkRes.getImageIcon(SparkRes.PLUS_SIGN)); + } + else { + preIconLabel.setIcon(SparkRes.getImageIcon(SparkRes.MINUS_SIGN)); + } + } + } + + public void setTitleColor(Color color) { + titleColor = color; + + titleLabel.setForeground(color); + } + + public Color getTitleColor() { + return titleColor; + } + + + public void paintComponent(Graphics g) { + Color stopColor = endColor; + Color starterColor = startColor; + + Color customStartColor = (Color)UIManager.get("CollapsiblePane.startColor"); + Color customEndColor = (Color)UIManager.get("CollapsiblePane.endColor"); + + if (customEndColor != null) { + stopColor = customEndColor; + } + + if (customStartColor != null) { + starterColor = customStartColor; + } + + if (isSubPane()) { + stopColor = ColorUtil.lighter(stopColor, 0.05); + } + + Graphics2D g2 = (Graphics2D)g; + + int w = getWidth(); + int h = getHeight(); + + GradientPaint gradient = new GradientPaint(0, 0, starterColor, w, h, stopColor, true); + g2.setPaint(gradient); + g2.fillRect(0, 0, w, h); + } + + protected boolean isSubPane() { + return subPane; + } + + protected void setSubPane(boolean subPane) { + this.subPane = subPane; + setCollapsed(isCollapsed()); + } + + private static Color getColor(String commaColorString) { + Color color = null; + try { + color = null; + + StringTokenizer tkn = new StringTokenizer(commaColorString, ","); + color = new Color(Integer.parseInt(tkn.nextToken()), Integer.parseInt(tkn.nextToken()), Integer.parseInt(tkn.nextToken())); + } + catch (NumberFormatException e1) { + Log.error(e1); + return Color.white; + } + return color; + } + + public void setTitleForeground(Color color) { + titleLabel.setForeground(color); + } +} diff --git a/src/java/org/jivesoftware/spark/component/renderer/JLabelIconRenderer.java b/src/java/org/jivesoftware/spark/component/renderer/JLabelIconRenderer.java new file mode 100644 index 00000000..8a245b23 --- /dev/null +++ b/src/java/org/jivesoftware/spark/component/renderer/JLabelIconRenderer.java @@ -0,0 +1,61 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.component.renderer; + + +import javax.swing.JLabel; +import javax.swing.JList; +import javax.swing.ListCellRenderer; + +import java.awt.Component; + +/** + * The JLabelIconRenderer is the an implementation of ListCellRenderer + * to add icons w/ associated text in JComboBox and JList. + * + * @author Derek DeMoro + */ +public class JLabelIconRenderer extends JLabel implements ListCellRenderer { + + /** + * Construct Default JLabelIconRenderer. + */ + public JLabelIconRenderer() { + setOpaque(true); + this.setVerticalTextPosition(JLabel.BOTTOM); + this.setHorizontalTextPosition(JLabel.CENTER); + this.setHorizontalAlignment(JLabel.CENTER); + } + + public Component getListCellRendererComponent(JList list, + Object value, + int index, + boolean isSelected, + boolean cellHasFocus) { + if (isSelected) { + setBackground(list.getSelectionBackground()); + setForeground(list.getSelectionForeground()); + } + else { + setBackground(list.getBackground()); + setForeground(list.getForeground()); + } + + this.setVerticalTextPosition(JLabel.BOTTOM); + this.setHorizontalTextPosition(JLabel.CENTER); + + JLabel label = (JLabel)value; + setText(label.getText()); + setIcon(label.getIcon()); + return this; + } +} + diff --git a/src/java/org/jivesoftware/spark/component/renderer/JPanelRenderer.java b/src/java/org/jivesoftware/spark/component/renderer/JPanelRenderer.java new file mode 100644 index 00000000..861183f6 --- /dev/null +++ b/src/java/org/jivesoftware/spark/component/renderer/JPanelRenderer.java @@ -0,0 +1,63 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.component.renderer; + + +import javax.swing.BorderFactory; +import javax.swing.JList; +import javax.swing.JPanel; +import javax.swing.ListCellRenderer; +import javax.swing.UIManager; + +import java.awt.Color; +import java.awt.Component; + +/** + * The JPanelRenderer is the an implementation of ListCellRenderer + * to add an entire panel ui to lists. + * + * @author Derek DeMoro + */ +public class JPanelRenderer extends JPanel implements ListCellRenderer { + + /** + * Construct Default JPanelRenderer. + */ + public JPanelRenderer() { + setOpaque(true); + } + + public Component getListCellRendererComponent(JList list, + Object value, + int index, + boolean isSelected, + boolean cellHasFocus) { + JPanel panel = (JPanel)value; + panel.setFocusable(false); + + if (isSelected) { + panel.setForeground((Color)UIManager.get("List.selectionForeground")); + panel.setBackground((Color)UIManager.get("List.selectionBackground")); + panel.setBorder(BorderFactory.createLineBorder((Color)UIManager.get("List.selectionBorder"))); + } + else { + panel.setBackground(list.getBackground()); + panel.setForeground(list.getForeground()); + panel.setBorder(BorderFactory.createLineBorder((Color)UIManager.get("List.background"))); + } + + list.setBackground((Color)UIManager.get("List.background")); + + + return panel; + } +} + diff --git a/src/java/org/jivesoftware/spark/component/renderer/ListIconRenderer.java b/src/java/org/jivesoftware/spark/component/renderer/ListIconRenderer.java new file mode 100644 index 00000000..2b07bd06 --- /dev/null +++ b/src/java/org/jivesoftware/spark/component/renderer/ListIconRenderer.java @@ -0,0 +1,60 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.component.renderer; + + +import javax.swing.ImageIcon; +import javax.swing.JLabel; +import javax.swing.JList; +import javax.swing.ListCellRenderer; +import javax.swing.SwingConstants; + +import java.awt.Component; + +/** + * The ListIconRenderer is the an implementation of ListCellRenderer + * to add icons w/ associated text in JComboBox and JList. + * + * @author Derek DeMoro + */ +public class ListIconRenderer extends JLabel implements ListCellRenderer { + + /** + * Create a Default ListIconRenderer. + */ + public ListIconRenderer() { + setOpaque(true); + setHorizontalAlignment(CENTER); + setVerticalAlignment(CENTER); + } + + public Component getListCellRendererComponent(JList list, + Object value, + int index, + boolean isSelected, + boolean cellHasFocus) { + if (isSelected) { + setBackground(list.getSelectionBackground()); + setForeground(list.getSelectionForeground()); + } + else { + setBackground(list.getBackground()); + setForeground(list.getForeground()); + } + + setHorizontalAlignment(SwingConstants.LEFT); + ImageIcon icon = (ImageIcon)value; + setText(icon.getDescription()); + setIcon(icon); + return this; + } +} + diff --git a/src/java/org/jivesoftware/spark/component/tabbedPane/SparkTab.java b/src/java/org/jivesoftware/spark/component/tabbedPane/SparkTab.java new file mode 100644 index 00000000..6f6e9ed6 --- /dev/null +++ b/src/java/org/jivesoftware/spark/component/tabbedPane/SparkTab.java @@ -0,0 +1,107 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.component.tabbedPane; + +import javax.swing.Icon; +import javax.swing.JComponent; +import javax.swing.JLabel; + +import java.awt.Color; +import java.awt.Component; +import java.awt.Font; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; + +/** + * + */ +public class SparkTab extends TabPanel { + private JLabel iconLabel; + private JLabel textLabel; + + private String actualText; + + private Color backgroundColor; + private Color selectedBorderColor; + private boolean selected; + private boolean boldWhenActive; + + private Font defaultFont; + + public SparkTab(Icon icon, String text) { + setLayout(new GridBagLayout()); + + + selectedBorderColor = new Color(173, 0, 0); + + // setBackground(backgroundColor); + + this.actualText = text; + + iconLabel = new JLabel(icon); + iconLabel.setOpaque(false); + textLabel = new JLabel(text); + + // add Label + add(iconLabel, new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(3, 2, 3, 2), 0, 0)); + + // add text label + add(textLabel, new GridBagConstraints(1, 0, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(3, 2, 3, 2), 0, 0)); + + // Set fonts + defaultFont = new Font("Dialog", Font.PLAIN, 11); + textLabel.setFont(defaultFont); + } + + public void addComponent(Component component) { + // add Component + add(component, new GridBagConstraints(3, 0, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(3, 2, 3, 2), 0, 0)); + } + + public void addPop(JComponent comp) { + add(comp, new GridBagConstraints(2, 0, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(3, 2, 3, 2), 0, 0)); + } + + public String getActualText() { + return actualText; + } + + + public void setSelected(boolean selected) { + super.setSelected(selected); + this.selected = selected; + + if (boldWhenActive) { + textLabel.setFont(textLabel.getFont().deriveFont(Font.BOLD)); + } + else { + textLabel.setFont(defaultFont); + } + + invalidate(); + repaint(); + } + + public JLabel getTitleLabel() { + return textLabel; + } + + public void setIcon(Icon icon) { + iconLabel.setIcon(icon); + } + + public void setBoldWhenActive(boolean boldWhenActive) { + this.boldWhenActive = boldWhenActive; + } + + +} diff --git a/src/java/org/jivesoftware/spark/component/tabbedPane/SparkTabbedPane.java b/src/java/org/jivesoftware/spark/component/tabbedPane/SparkTabbedPane.java new file mode 100644 index 00000000..f7347a03 --- /dev/null +++ b/src/java/org/jivesoftware/spark/component/tabbedPane/SparkTabbedPane.java @@ -0,0 +1,518 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.component.tabbedPane; + +import org.jivesoftware.resource.SparkRes; +import org.jivesoftware.spark.component.RolloverButton; +import org.jivesoftware.spark.util.ModelUtil; + +import javax.swing.Icon; +import javax.swing.JButton; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JPanel; + +import java.awt.BorderLayout; +import java.awt.CardLayout; +import java.awt.Color; +import java.awt.Component; +import java.awt.Dimension; +import java.awt.FlowLayout; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.Window; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +/** + * + */ +public class SparkTabbedPane extends JPanel implements MouseListener { + private JPanel tabs; + private JPanel mainPanel; + + private Window parentWindow; + + private boolean closeButtonEnabled; + private Icon closeInactiveButtonIcon; + private Icon closeActiveButtonIcon; + + private boolean popupAllowed; + + private boolean activeButtonBold; + + private Map framesMap = new HashMap(); + + + /** + * Listeners + */ + private List listeners = new ArrayList(); + + public SparkTabbedPane() { + setLayout(new BorderLayout()); + + tabs = new JPanel(new + FlowLayout(FlowLayout.LEFT, 0, 0)) { + public Dimension getPreferredSize() { + if (getParent() == null) + return getPreferredSize(); + // calculate the preferred size based on the flow of components + FlowLayout flow = (FlowLayout)getLayout(); + int w = getParent().getWidth(); + int h = flow.getVgap(); + int x = flow.getHgap(); + int rowH = 0; + Dimension d; + Component[] comps = getComponents(); + for (int i = 0; i < comps.length; i++) { + if (comps[i].isVisible()) { + d = comps[i].getPreferredSize(); + if (x + d.width > w && x > flow.getHgap()) { + x = flow.getHgap(); + h += rowH; + rowH = 0; + h += flow.getVgap(); + } + rowH = Math.max(d.height, rowH); + x += d.width + flow.getHgap(); + } + } + h += rowH; + return new Dimension(w, h); + } + }; + + + final JPanel topPanel = new JPanel(); + topPanel.setLayout(new GridBagLayout()); + topPanel.add(tabs, new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, new Insets(5, 0, 0, 0), 0, 0)); + topPanel.add(new JLabel(), new GridBagConstraints(1, 0, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 0, 0, 0), 0, 0)); + topPanel.setOpaque(false); + // Add Tabs panel to top of panel. + add(topPanel, BorderLayout.NORTH); + + // Create mainPanel + mainPanel = new JPanel(new CardLayout()); + add(mainPanel, BorderLayout.CENTER); + + // mainPanel.setBorder(BorderFactory.createLineBorder(Color.lightGray)); + + // Initialize close button + closeInactiveButtonIcon = SparkRes.getImageIcon(SparkRes.CLOSE_WHITE_X_IMAGE); + closeActiveButtonIcon = SparkRes.getImageIcon(SparkRes.CLOSE_DARK_X_IMAGE); + + setBackground(Color.white); + tabs.setOpaque(false); + } + + public SparkTab addTab(String text, Icon icon, final Component component, String tooltip) { + SparkTab tab = addTab(text, icon, component); + tab.setToolTipText(tooltip); + return tab; + } + + + public SparkTab addTab(String text, Icon icon, final Component component) { + final SparkTab tab = new SparkTab(icon, text); + //tabs.add(tab, new GridBagConstraints(tabs.getComponentCount(), 1, 1, 1, 1.0, 1.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(2, 0, 0, 0), 0, 0)); + + tabs.add(tab); + // Add Component to main panel + mainPanel.add(tab.getActualText(), component); + tab.addMouseListener(this); + + // Add Close Button + if (isCloseButtonEnabled()) { + final RolloverButton closeButton = new RolloverButton(closeInactiveButtonIcon); + tab.addComponent(closeButton); + closeButton.addMouseListener(new MouseAdapter() { + public void mouseEntered(MouseEvent mouseEvent) { + closeButton.setIcon(closeActiveButtonIcon); + } + + public void mouseExited(MouseEvent mouseEvent) { + closeButton.setIcon(closeInactiveButtonIcon); + } + }); + closeButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent actionEvent) { + close(tab, component); + } + + }); + } + + /* + if (isPopupAllowed()) { + RolloverButton popButton = new RolloverButton(LaRes.getImageIcon(LaRes.SMALL_PIN_BLUE)); + tab.addPop(popButton); + + popButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent actionEvent) { + int index = getIndex(tab); + + // Close Tab + + String title = tab.getActualText(); + + // Create Frame + final JFrame frame = new JFrame(); + frame.setTitle(title); + + frame.getContentPane().setLayout(new BorderLayout()); + frame.getContentPane().add(component, BorderLayout.CENTER); + frame.pack(); + + GraphicUtils.centerWindowOnScreen(frame); + frame.setVisible(true); + + mainPanel.remove(component); + tabs.remove(tab); + + + tabs.invalidate(); + tabs.validate(); + tabs.repaint(); + + mainPanel.invalidate(); + mainPanel.validate(); + mainPanel.repaint(); + + fireTabRemoved(tab, component, index); + Component[] comps = tabs.getComponents(); + if (comps.length == 0) { + allTabsClosed(); + } + else { + findSelectedTab(index); + } + + } + }); + } + */ + + setSelectedTab(tab); + + fireTabAdded(tab, component, getIndex(tab)); + return tab; + } + + public int getSelectedIndex() { + Component[] comps = tabs.getComponents(); + for (int i = 0; i < comps.length; i++) { + Component c = comps[i]; + SparkTab tab = (SparkTab)c; + if (tab.isSelected()) { + return i; + } + } + + return -1; + } + + public void setSelectedIndex(int index) { + Component[] comps = tabs.getComponents(); + if (index <= comps.length) { + SparkTab tab = (SparkTab)comps[index]; + setSelectedTab(tab); + } + } + + public int getTabCount() { + return tabs.getComponents().length; + } + + public int indexOfComponent(Component comp) { + Component[] comps = mainPanel.getComponents(); + for (int i = 0; i < comps.length; i++) { + Component c = comps[i]; + if (c == comp) { + return i; + } + } + + return -1; + } + + public Component getComponentAt(int index) { + Component[] comps = mainPanel.getComponents(); + for (int i = 0; i < comps.length; i++) { + Component c = comps[i]; + if (i == index) { + return c; + } + } + + return null; + } + + public void removeTabAt(int index) { + SparkTab tab = getTabAt(index); + Component comp = getComponentAt(index); + close(tab, comp); + } + + + public SparkTab getTabAt(int index) { + Component[] comps = tabs.getComponents(); + for (int i = 0; i < comps.length; i++) { + Component c = comps[i]; + if (i == index) { + return (SparkTab)c; + } + } + + return null; + } + + public void removeComponent(Component comp) { + Component[] comps = mainPanel.getComponents(); + for (int i = 0; i < comps.length; i++) { + Component c = comps[i]; + if (c == comp) { + + } + } + } + + public int getIndex(SparkTab tab) { + Component[] comps = tabs.getComponents(); + for (int i = 0; i < comps.length; i++) { + Component c = comps[i]; + if (c instanceof SparkTab && c == tab) { + return i; + } + } + + return -1; + } + + public void close(SparkTab tab, Component comp) { + int index = getIndex(tab); + + // Close Tab + mainPanel.remove(comp); + tabs.remove(tab); + + + tabs.invalidate(); + tabs.validate(); + tabs.repaint(); + + mainPanel.invalidate(); + mainPanel.validate(); + mainPanel.repaint(); + + fireTabRemoved(tab, comp, index); + Component[] comps = tabs.getComponents(); + if (comps.length == 0) { + allTabsClosed(); + } + else { + findSelectedTab(index); + } + } + + private void findSelectedTab(int previousIndex) { + Component[] comps = tabs.getComponents(); + for (int i = 0; i < comps.length; i++) { + Component c = comps[i]; + if (c instanceof SparkTab && i == previousIndex) { + setSelectedTab(((SparkTab)c)); + return; + } + } + + if (comps.length > 0 && comps.length == previousIndex) { + SparkTab tab = (SparkTab)comps[previousIndex - 1]; + setSelectedTab(tab); + } + } + + public void mouseClicked(MouseEvent e) { + if (e.getSource() instanceof SparkTab) { + SparkTab tab = (SparkTab)e.getSource(); + setSelectedTab(tab); + } + } + + public void setSelectedTab(SparkTab tab) { + CardLayout cl = (CardLayout)mainPanel.getLayout(); + cl.show(mainPanel, tab.getActualText()); + tab.setSelected(true); + deselectAllTabsExcept(tab); + + fireTabSelected(tab, getSelectedComponent(), getIndex(tab)); + } + + public void mousePressed(MouseEvent e) { + } + + public void mouseReleased(MouseEvent e) { + } + + public void mouseEntered(MouseEvent e) { + } + + public void mouseExited(MouseEvent e) { + } + + private void deselectAllTabsExcept(SparkTab tab) { + Component[] comps = tabs.getComponents(); + for (int i = 0; i < comps.length; i++) { + Component c = comps[i]; + if (c instanceof SparkTab) { + SparkTab sparkTab = (SparkTab)c; + if (sparkTab != tab) { + sparkTab.setSelected(false); + sparkTab.showBorder(true); + } + else if (sparkTab == tab) { + int j = i - 1; + if (j >= 0) { + SparkTab previousTab = (SparkTab)comps[j]; + previousTab.showBorder(false); + } + } + } + + } + + } + + public Component getSelectedComponent() { + Component[] comps = mainPanel.getComponents(); + for (int i = 0; i < comps.length; i++) { + Component c = comps[i]; + if (c.isShowing()) { + return c; + } + } + + return null; + } + + + public void setParentWindow(Window window) { + this.parentWindow = window; + } + + public Dimension getPreferredSize() { + final Dimension size = super.getPreferredSize(); + size.width = 0; + return size; + } + + + public boolean isCloseButtonEnabled() { + return closeButtonEnabled; + } + + public void setCloseButtonEnabled(boolean closeButtonEnabled) { + this.closeButtonEnabled = closeButtonEnabled; + } + + public Icon getCloseInactiveButtonIcon() { + return closeInactiveButtonIcon; + } + + public void setCloseInactiveButtonIcon(Icon closeInactiveButtonIcon) { + this.closeInactiveButtonIcon = closeInactiveButtonIcon; + } + + public Icon getCloseActiveButtonIcon() { + return closeActiveButtonIcon; + } + + public void setCloseActiveButtonIcon(Icon closeActiveButtonIcon) { + this.closeActiveButtonIcon = closeActiveButtonIcon; + } + + + public boolean isPopupAllowed() { + return popupAllowed; + } + + public void setPopupAllowed(boolean popupAllowed) { + this.popupAllowed = popupAllowed; + } + + + public void addSparkTabbedPaneListener(SparkTabbedPaneListener listener) { + listeners.add(listener); + } + + public void removeSparkTabbedPaneListener(SparkTabbedPaneListener listener) { + listeners.remove(listener); + } + + public void fireTabAdded(SparkTab tab, Component component, int index) { + final Iterator list = ModelUtil.reverseListIterator(listeners.listIterator()); + while (list.hasNext()) { + ((SparkTabbedPaneListener)list.next()).tabAdded(tab, component, index); + } + } + + public void fireTabRemoved(SparkTab tab, Component component, int index) { + final Iterator list = ModelUtil.reverseListIterator(listeners.listIterator()); + while (list.hasNext()) { + ((SparkTabbedPaneListener)list.next()).tabRemoved(tab, component, index); + } + } + + public void fireTabSelected(SparkTab tab, Component component, int index) { + final Iterator list = ModelUtil.reverseListIterator(listeners.listIterator()); + while (list.hasNext()) { + ((SparkTabbedPaneListener)list.next()).tabSelected(tab, component, index); + } + } + + public void allTabsClosed() { + final Iterator list = ModelUtil.reverseListIterator(listeners.listIterator()); + while (list.hasNext()) { + ((SparkTabbedPaneListener)list.next()).allTabsRemoved(); + } + } + + public boolean isActiveButtonBold() { + return activeButtonBold; + } + + public void setActiveButtonBold(boolean activeButtonBold) { + this.activeButtonBold = activeButtonBold; + } + + public static void main(String args[]) { + JFrame f = new JFrame(); + SparkTabbedPane pane = new SparkTabbedPane(); + pane.setCloseButtonEnabled(true); + pane.setPopupAllowed(true); + for (int i = 0; i < 3; i++) { + pane.addTab("Hello" + i, SparkRes.getImageIcon(SparkRes.SMALL_AGENT_IMAGE), new JButton("BUTTON" + i)); + } + + + f.add(pane); + f.pack(); + f.setVisible(true); + } +} diff --git a/src/java/org/jivesoftware/spark/component/tabbedPane/SparkTabbedPaneListener.java b/src/java/org/jivesoftware/spark/component/tabbedPane/SparkTabbedPaneListener.java new file mode 100644 index 00000000..00f82760 --- /dev/null +++ b/src/java/org/jivesoftware/spark/component/tabbedPane/SparkTabbedPaneListener.java @@ -0,0 +1,29 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.component.tabbedPane; + +import java.awt.Component; + +/** + * + */ +public interface SparkTabbedPaneListener { + + void tabRemoved(SparkTab tab, Component component, int index); + + void tabAdded(SparkTab tab, Component component, int index); + + void tabSelected(SparkTab tab, Component component, int index); + + void allTabsRemoved(); + + +} diff --git a/src/java/org/jivesoftware/spark/component/tabbedPane/TabPanel.java b/src/java/org/jivesoftware/spark/component/tabbedPane/TabPanel.java new file mode 100644 index 00000000..399a2ab9 --- /dev/null +++ b/src/java/org/jivesoftware/spark/component/tabbedPane/TabPanel.java @@ -0,0 +1,44 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.component.tabbedPane; + +import javax.swing.JPanel; + +/** + * + */ +public class TabPanel extends JPanel { + + private boolean selected; + private TabPanelUI ui; + + /** + * Creates a background panel using the default Spark background image. + */ + public TabPanel() { + ui = new TabPanelUI(); + setUI(ui); + } + + public boolean isSelected() { + return selected; + } + + public void setSelected(boolean selected) { + this.selected = selected; + ui.setSelected(selected); + } + + public void showBorder(boolean show) { + ui.setHideBorder(!show); + } +} + diff --git a/src/java/org/jivesoftware/spark/component/tabbedPane/TabPanelUI.java b/src/java/org/jivesoftware/spark/component/tabbedPane/TabPanelUI.java new file mode 100644 index 00000000..9043409b --- /dev/null +++ b/src/java/org/jivesoftware/spark/component/tabbedPane/TabPanelUI.java @@ -0,0 +1,176 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.component.tabbedPane; + + +import org.jivesoftware.Spark; +import org.jivesoftware.resource.Default; +import org.jivesoftware.spark.util.log.Log; + +import javax.swing.JComponent; +import javax.swing.JPanel; +import javax.swing.UIManager; +import javax.swing.plaf.basic.BasicPanelUI; + +import java.awt.Color; +import java.awt.GradientPaint; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Insets; +import java.awt.RenderingHints; +import java.awt.Shape; +import java.awt.geom.RoundRectangle2D; +import java.util.StringTokenizer; + +public class TabPanelUI extends BasicPanelUI { + private Color backgroundColor1 = Color.white;//new Color(235, 247, 223); + private Color backgroundColor2 = Color.white;//new Color(214, 219, 191); + + private Color borderColor = new Color(86, 88, 72); + private Color borderColorAlpha1 = new Color(86, 88, 72, 100); + private Color borderColorAlpha2 = new Color(86, 88, 72, 50); + private Color borderHighlight = new Color(225, 224, 224); + + private boolean selected; + private boolean hideBorder; + + // ------------------------------------------------------------------------------------------------------------------ + // Custom installation methods + // ------------------------------------------------------------------------------------------------------------------ + + protected void installDefaults(JPanel p) { + p.setOpaque(false); + } + + public void setSelected(boolean selected) { + if (selected) { + backgroundColor1 = getSelectedStartColor(); + backgroundColor2 = getSelectedEndColor(); + } + else { + backgroundColor1 = Color.white; + backgroundColor2 = Color.white; + } + + this.selected = selected; + } + + // ------------------------------------------------------------------------------------------------------------------ + // Custom painting methods + // ------------------------------------------------------------------------------------------------------------------ + + public void paint(Graphics g, JComponent c) { + Graphics2D g2d = (Graphics2D)g; + g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + + Insets vInsets = c.getInsets(); + + int w = c.getWidth() - (vInsets.left + vInsets.right); + int h = c.getHeight() - (vInsets.top + vInsets.bottom); + + int x = vInsets.left; + int y = vInsets.top; + int arc = 8; + + Shape vButtonShape = new RoundRectangle2D.Double((double)x, (double)y, (double)w, (double)h, (double)arc, (double)arc); + Shape vOldClip = g.getClip(); + + g2d.setClip(vButtonShape); + g2d.setColor(backgroundColor2); + g2d.fillRect(x, y, w, h / 2); + g2d.setColor(backgroundColor2); + g2d.fillRect(x, y + h / 2, w, h / 2); + + g2d.setClip(vOldClip); + GradientPaint vPaint = new GradientPaint(x, y, borderColor, x, y + h, borderHighlight); + g2d.setPaint(vPaint); + + if (selected) { + g2d.setColor(Color.lightGray); + g2d.drawRoundRect(x, y, w, h, arc, arc); + } + + g2d.clipRect(x, y, w + 1, h - arc / 4); + g2d.setColor(borderColorAlpha1); + // g2d.drawLine(x, y + h, w, h - 1); + + g2d.setClip(vOldClip); + g2d.setColor(borderColorAlpha2); + + // g2d.drawRoundRect(x + 1, y + 2, w - 2, h - 3, arc, arc - 2); + + + g2d.setColor(backgroundColor2); + g2d.fillRect(x, h - 5, w, h); + if (selected) { + + } + else if (!hideBorder) { + // Draw border on right side. + g2d.setColor(Color.lightGray); + g2d.drawLine(w - 1, 4, w - 1, h - 4); + } + + } + + + public void setHideBorder(boolean hide) { + hideBorder = hide; + } + + + private Color getSelectedStartColor() { + Color uiStartColor = (Color)UIManager.get("SparkTabbedPane.startColor"); + if (uiStartColor != null) { + return uiStartColor; + } + + if (Spark.isCustomBuild()) { + String end = Default.getString(Default.CONTACT_GROUP_END_COLOR); + return getColor(end); + } + else { + return new Color(193, 216, 248); + } + } + + + private Color getSelectedEndColor() { + Color uiEndColor = (Color)UIManager.get("SparkTabbedPane.endColor"); + if (uiEndColor != null) { + return uiEndColor; + } + + if (Spark.isCustomBuild()) { + String end = Default.getString(Default.CONTACT_GROUP_END_COLOR); + return getColor(end); + } + else { + return new Color(180, 207, 247); + } + } + + private static Color getColor(String commaColorString) { + Color color = null; + try { + color = null; + + StringTokenizer tkn = new StringTokenizer(commaColorString, ","); + color = new Color(Integer.parseInt(tkn.nextToken()), Integer.parseInt(tkn.nextToken()), Integer.parseInt(tkn.nextToken())); + } + catch (NumberFormatException e1) { + Log.error(e1); + return Color.white; + } + return color; + } +} + diff --git a/src/java/org/jivesoftware/spark/filetransfer/FileTransferListener.java b/src/java/org/jivesoftware/spark/filetransfer/FileTransferListener.java new file mode 100644 index 00000000..ff925243 --- /dev/null +++ b/src/java/org/jivesoftware/spark/filetransfer/FileTransferListener.java @@ -0,0 +1,30 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.filetransfer; + +import org.jivesoftware.smackx.filetransfer.FileTransferRequest; + +/** + * Implementation of the TransferListener interface allows for + * handling of file transfers externally of the default behaviour. + */ +public interface FileTransferListener { + + /** + * Returns true if the object wishes to handle the file transfer itself. Otherwise, + * it will default. + * + * @param request the FileTransferRequest + * @return true if object handles transfer request. + */ + boolean handleTransfer(FileTransferRequest request); + +} diff --git a/src/java/org/jivesoftware/spark/filetransfer/SparkTransferManager.java b/src/java/org/jivesoftware/spark/filetransfer/SparkTransferManager.java new file mode 100644 index 00000000..65c810be --- /dev/null +++ b/src/java/org/jivesoftware/spark/filetransfer/SparkTransferManager.java @@ -0,0 +1,656 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.filetransfer; + +import org.jivesoftware.Spark; +import org.jivesoftware.resource.SparkRes; +import org.jivesoftware.smack.PacketListener; +import org.jivesoftware.smack.Roster; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.filter.AndFilter; +import org.jivesoftware.smack.filter.FromContainsFilter; +import org.jivesoftware.smack.filter.PacketTypeFilter; +import org.jivesoftware.smack.packet.Message; +import org.jivesoftware.smack.packet.Packet; +import org.jivesoftware.smack.packet.Presence; +import org.jivesoftware.smack.util.StringUtils; +import org.jivesoftware.smackx.filetransfer.FileTransferManager; +import org.jivesoftware.smackx.filetransfer.FileTransferRequest; +import org.jivesoftware.smackx.filetransfer.OutgoingFileTransfer; +import org.jivesoftware.spark.ChatManager; +import org.jivesoftware.spark.SparkManager; +import org.jivesoftware.spark.component.RolloverButton; +import org.jivesoftware.spark.ui.ChatInputEditor; +import org.jivesoftware.spark.ui.ChatRoom; +import org.jivesoftware.spark.ui.ChatRoomButton; +import org.jivesoftware.spark.ui.ChatRoomClosingListener; +import org.jivesoftware.spark.ui.ChatRoomListenerAdapter; +import org.jivesoftware.spark.ui.ContactItem; +import org.jivesoftware.spark.ui.ContactList; +import org.jivesoftware.spark.ui.FileDropListener; +import org.jivesoftware.spark.ui.TranscriptWindow; +import org.jivesoftware.spark.ui.rooms.ChatRoomImpl; +import org.jivesoftware.spark.ui.status.StatusBar; +import org.jivesoftware.spark.util.GraphicUtils; +import org.jivesoftware.spark.util.ResourceUtils; +import org.jivesoftware.spark.util.SwingWorker; +import org.jivesoftware.spark.util.WindowsFileSystemView; +import org.jivesoftware.spark.util.log.Log; +import org.jivesoftware.sparkimpl.plugin.filetransfer.transfer.Downloads; +import org.jivesoftware.sparkimpl.plugin.filetransfer.transfer.ui.ReceiveMessage; +import org.jivesoftware.sparkimpl.plugin.filetransfer.transfer.ui.SendMessage; +import org.jivesoftware.sparkimpl.plugin.manager.Enterprise; + +import javax.imageio.ImageIO; +import javax.swing.AbstractAction; +import javax.swing.JComponent; +import javax.swing.JFileChooser; +import javax.swing.JMenu; +import javax.swing.JMenuItem; +import javax.swing.JPanel; +import javax.swing.KeyStroke; +import javax.swing.text.BadLocationException; +import javax.swing.text.Document; +import javax.swing.text.Style; +import javax.swing.text.StyleConstants; +import javax.swing.text.StyledDocument; + +import java.awt.AWTException; +import java.awt.Component; +import java.awt.Cursor; +import java.awt.Image; +import java.awt.Rectangle; +import java.awt.Robot; +import java.awt.Toolkit; +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.Transferable; +import java.awt.datatransfer.UnsupportedFlavorException; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +/** + * Responsible for the handling of File Transfer within Spark. You would use the SparkManager + * for sending of images, files, multiple files and adding your own transfer listeners for plugin work. + */ +public class SparkTransferManager { + + private List listeners = new ArrayList(); + private File defaultDirectory; + + private static SparkTransferManager singleton; + private static final Object LOCK = new Object(); + private JFileChooser fc; + private FileTransferManager transferManager; + private Map waitMap = new HashMap(); + + /** + * Returns the singleton instance of SparkTransferManager, + * creating it if necessary. + *

        + * + * @return the singleton instance of SparkTransferManager + */ + public static SparkTransferManager getInstance() { + // Synchronize on LOCK to ensure that we don't end up creating + // two singletons. + synchronized (LOCK) { + if (null == singleton) { + SparkTransferManager controller = new SparkTransferManager(); + singleton = controller; + return controller; + } + } + return singleton; + } + + private SparkTransferManager() { + boolean enabled = Enterprise.containsFeature(Enterprise.FILE_TRANSFER_FEATURE); + if (!enabled) { + return; + } + + final JMenu actionsMenu = SparkManager.getMainWindow().getMenuByName("Actions"); + JMenuItem downloadsMenu = new JMenuItem("", SparkRes.getImageIcon(SparkRes.DOWNLOAD_16x16)); + ResourceUtils.resButton(downloadsMenu, "&View Downloads"); + actionsMenu.add(downloadsMenu); + downloadsMenu.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + Downloads downloads = Downloads.getInstance(); + downloads.showDownloadsDirectory(); + } + }); + + // Create the file transfer manager + transferManager = new FileTransferManager(SparkManager.getConnection()); + final ContactList contactList = SparkManager.getWorkspace().getContactList(); + + // Create the listener + transferManager.addFileTransferListener(new org.jivesoftware.smackx.filetransfer.FileTransferListener() { + public void fileTransferRequest(FileTransferRequest request) { + + // Check if a listener handled this request + if (fireTransferListeners(request)) { + return; + } + String requestor = request.getRequestor(); + String bareJID = StringUtils.parseBareAddress(requestor); + + + ContactItem contactItem = contactList.getContactItemByJID(bareJID); + + ChatRoom chatRoom; + if (contactItem != null) { + chatRoom = SparkManager.getChatManager().createChatRoom(bareJID, contactItem.getNickname(), contactItem.getNickname()); + } + else { + chatRoom = SparkManager.getChatManager().createChatRoom(bareJID, bareJID, bareJID); + } + + TranscriptWindow transcriptWindow = chatRoom.getTranscriptWindow(); + StyledDocument doc = (StyledDocument)transcriptWindow.getDocument(); + + // The image must first be wrapped in a style + Style style = doc.addStyle("StyleName", null); + + final ReceiveMessage receivingMessageUI = new ReceiveMessage(); + receivingMessageUI.acceptFileTransfer(request); + + chatRoom.addClosingListener(new ChatRoomClosingListener() { + public void closing() { + receivingMessageUI.cancelTransfer(); + } + }); + + StyleConstants.setComponent(style, receivingMessageUI); + + // Insert the image at the end of the text + try { + doc.insertString(doc.getLength(), "ignored text", style); + doc.insertString(doc.getLength(), "\n", null); + } + catch (BadLocationException e) { + Log.error(e); + } + + chatRoom.scrollToBottom(); + + SparkManager.getChatManager().getChatContainer().fireNotifyOnMessage(chatRoom); + } + }); + + // Add Send File to Chat Room + addSendFileButton(); + + + contactList.addFileDropListener(new FileDropListener() { + public void filesDropped(Collection files, Component component) { + if (component instanceof ContactItem) { + ContactItem item = (ContactItem)component; + + ChatRoom chatRoom = null; + Iterator iter = files.iterator(); + while (iter.hasNext()) { + chatRoom = sendFile((File)iter.next(), item.getFullJID()); + } + + if (chatRoom != null) { + SparkManager.getChatManager().getChatContainer().activateChatRoom(chatRoom); + } + } + } + }); + + if (defaultDirectory == null) { + defaultDirectory = new File(System.getProperty("user.home")); + } + + addPresenceListener(); + + // Add View Downloads to Command Panel + StatusBar statusBar = SparkManager.getWorkspace().getStatusBar(); + JPanel commandPanel = statusBar.getCommandPanel(); + + RolloverButton viewDownloads = new RolloverButton(SparkRes.getImageIcon(SparkRes.DOWNLOAD_16x16)); + viewDownloads.setToolTipText("View Downloads"); + commandPanel.add(viewDownloads); + viewDownloads.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + Downloads downloads = Downloads.getInstance(); + downloads.showDownloadsDirectory(); + } + }); + } + + + public void sendFileTo(ContactItem item) { + final ContactList contactList = SparkManager.getWorkspace().getContactList(); + getFileChooser().setDialogTitle("Select the file to send"); + int ok = getFileChooser().showOpenDialog(contactList); + if (ok != JFileChooser.APPROVE_OPTION) { + return; + } + + File[] files = getFileChooser().getSelectedFiles(); + for (int i = 0; i < files.length; i++) { + File file = files[i]; + if (file.exists()) { + defaultDirectory = file.getParentFile(); + sendFile(file, item.getFullJID()); + } + } + } + + private void addSendFileButton() { + final ChatManager chatManager = SparkManager.getChatManager(); + chatManager.addChatRoomListener(new ChatRoomListenerAdapter() { + + FileDropListener fileDropListener; + + public void chatRoomOpened(final ChatRoom room) { + if (!(room instanceof ChatRoomImpl)) { + return; + } + + + final ChatInputEditor chatSendField = room.getChatInputEditor(); + chatSendField.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("control v"), "paste"); + + chatSendField.getActionMap().put("paste", new AbstractAction("paste") { + public void actionPerformed(ActionEvent evt) { + String clipboardText = SparkManager.getClipboard(); + + if (clipboardText == null && getClipboard() != null) { + sendImage(getClipboard(), room); + } + else { + try { + Document document = chatSendField.getDocument(); + document.insertString(chatSendField.getCaretPosition(), clipboardText, null); + } + catch (BadLocationException e) { + Log.error(e); + } + } + } + }); + + + fileDropListener = new FileDropListener() { + public void filesDropped(Collection files, Component component) { + if (component instanceof ChatRoomImpl) { + ChatRoomImpl roomImpl = (ChatRoomImpl)component; + + + Iterator iter = files.iterator(); + while (iter.hasNext()) { + sendFile((File)iter.next(), roomImpl.getParticipantJID()); + } + + SparkManager.getChatManager().getChatContainer().activateChatRoom(roomImpl); + } + } + }; + + room.addFileDropListener(fileDropListener); + + + ChatRoomButton sendFileButton = new ChatRoomButton("", SparkRes.getImageIcon(SparkRes.SEND_FILE_24x24)); + sendFileButton.setToolTipText("Send file(s) to this user"); + + room.getToolBar().addChatRoomButton(sendFileButton); + + final ChatRoomButton sendScreenShotButton = new ChatRoomButton("", SparkRes.getImageIcon(SparkRes.PHOTO_IMAGE)); + sendScreenShotButton.setToolTipText("Send a picture of your desktop"); + room.getToolBar().addChatRoomButton(sendScreenShotButton); + + sendScreenShotButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent actionEvent) { + sendScreenshot(sendScreenShotButton, room); + } + }); + + sendFileButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + SwingWorker worker = new SwingWorker() { + public Object construct() { + try { + Thread.sleep(10); + } + catch (InterruptedException e1) { + Log.error(e1); + } + return "delay"; + } + + public void finished() { + ChatRoomImpl roomImpl = (ChatRoomImpl)room; + + getFileChooser().setDialogTitle("Select File(s) to send"); + getFileChooser().setMultiSelectionEnabled(true); + int ok = getFileChooser().showOpenDialog(roomImpl); + if (ok != JFileChooser.APPROVE_OPTION) { + return; + } + + File[] file = getFileChooser().getSelectedFiles(); + if (file == null || file.length == 0) { + return; + } + + + final int no = file.length; + for (int i = 0; i < no; i++) { + File sendFile = file[i]; + + if (sendFile.exists()) { + defaultDirectory = sendFile.getParentFile(); + sendFile(sendFile, roomImpl.getParticipantJID()); + } + } + + + } + }; + worker.start(); + } + }); + } + + public void chatRoomClosed(ChatRoom room) { + room.removeFileDropListener(fileDropListener); + } + }); + + + } + + private void sendScreenshot(final ChatRoomButton button, final ChatRoom room) { + button.setEnabled(false); + room.setCursor(new Cursor(Cursor.WAIT_CURSOR)); + final SwingWorker worker = new SwingWorker() { + + public Object construct() { + try { + Robot robot = new Robot(); + Rectangle area = new Rectangle(Toolkit.getDefaultToolkit().getScreenSize()); + return robot.createScreenCapture(area); + } + catch (AWTException e) { + Log.error(e); + } + return null; + } + + public void finished() { + BufferedImage bufferedImage = (BufferedImage)get(); + if (bufferedImage != null) { + sendImage(bufferedImage, room); + } + + button.setEnabled(true); + room.setCursor(new Cursor(Cursor.DEFAULT_CURSOR)); + } + }; + worker.start(); + } + + private void addPresenceListener() { + SparkManager.getConnection().addPacketListener(new PacketListener() { + public void processPacket(Packet packet) { + Presence presence = (Presence)packet; + if (presence != null) { + String bareJID = StringUtils.parseBareAddress(presence.getFrom()); + + // Iterate through map. + List list = (List)waitMap.get(bareJID); + if (list != null) { + // Iterate through list and send. + Iterator iter = list.iterator(); + ChatRoom room = null; + while (iter.hasNext()) { + File file = (File)iter.next(); + room = sendFile(file, bareJID); + } + + if (room != null) { + Message message = new Message(); + message.setBody("You have just been sent offline file(s)."); + room.sendMessage(message); + } + } + + + waitMap.remove(bareJID); + } + } + }, new PacketTypeFilter(Presence.class)); + } + + /** + * Send a file to a user. + * + * @param file the file to send. + * @param bJID the bare jid of the user to send the file to. + * @return the ChatRoom of the user. + */ + public ChatRoom sendFile(File file, String bJID) { + Roster roster = SparkManager.getConnection().getRoster(); + Presence presence = roster.getPresence(bJID); + + final ContactList contactList = SparkManager.getWorkspace().getContactList(); + + if (presence == null) { + List list = (List)waitMap.get(bJID); + if (list == null) { + list = new ArrayList(); + } + + list.add(file); + waitMap.put(bJID, list); + + ChatRoom chatRoom; + ContactItem contactItem = contactList.getContactItemByJID(bJID); + if (contactItem != null) { + chatRoom = SparkManager.getChatManager().createChatRoom(bJID, contactItem.getNickname(), contactItem.getNickname()); + } + else { + chatRoom = SparkManager.getChatManager().createChatRoom(bJID, bJID, bJID); + } + + chatRoom.getTranscriptWindow().insertErrorMessage("The user is offline. Will auto-send \"" + file.getName() + "\" when user comes back online."); + return null; + } + + + String bareJID = StringUtils.parseBareAddress(presence.getFrom()); + + // Create the outgoing file transfer + final OutgoingFileTransfer transfer = transferManager.createOutgoingFileTransfer(presence.getFrom()); + + ContactItem contactItem = contactList.getContactItemByJID(bareJID); + + ChatRoom chatRoom; + if (contactItem != null) { + chatRoom = SparkManager.getChatManager().createChatRoom(bareJID, contactItem.getNickname(), contactItem.getNickname()); + } + else { + chatRoom = SparkManager.getChatManager().createChatRoom(bareJID, bareJID, bareJID); + } + + + TranscriptWindow transcriptWindow = chatRoom.getTranscriptWindow(); + StyledDocument doc = (StyledDocument)transcriptWindow.getDocument(); + + // The image must first be wrapped in a style + Style style = doc.addStyle("StyleName", null); + + SendMessage sendMessageGUI = new SendMessage(); + try { + transfer.sendFile(file, "Sending file"); + } + catch (XMPPException e) { + Log.error(e); + } + + // Add listener to cancel transfer is sending file to user who just went offline. + AndFilter presenceFilter = new AndFilter(new PacketTypeFilter(Presence.class), new FromContainsFilter(bareJID)); + final PacketListener packetListener = new PacketListener() { + public void processPacket(Packet packet) { + Presence presence = (Presence)packet; + if (presence == null || presence.getType() == Presence.Type.UNAVAILABLE) { + if (transfer != null) { + transfer.cancel(); + } + } + } + }; + + // Add presence listener to check if user is offline and cancel sending. + SparkManager.getConnection().addPacketListener(packetListener, presenceFilter); + + chatRoom.addClosingListener(new ChatRoomClosingListener() { + public void closing() { + SparkManager.getConnection().removePacketListener(packetListener); + + if (!transfer.isDone()) { + transfer.cancel(); + } + } + }); + + sendMessageGUI.sendFile(transfer, bareJID, contactItem.getNickname()); + StyleConstants.setComponent(style, sendMessageGUI); + + // Insert the image at the end of the text + try { + doc.insertString(doc.getLength(), "ignored text", style); + doc.insertString(doc.getLength(), "\n", null); + } + catch (BadLocationException e) { + Log.error(e); + } + + chatRoom.scrollToBottom(); + return chatRoom; + } + + /** + * Send an image to a user. + * + * @param image the image to send. + * @param room the ChatRoom of the user you wish to send the image to. + */ + public void sendImage(final Image image, final ChatRoom room) { + File tmpDirectory = new File(Spark.getUserHome(), "Spark/tmp"); + tmpDirectory.mkdirs(); + + String imageName = "image_" + StringUtils.randomString(2) + ".png"; + final File imageFile = new File(tmpDirectory, imageName); + // Write image to system. + + SwingWorker worker = new SwingWorker() { + public Object construct() { + try { + // Write out file in separate thread. + BufferedImage bi = GraphicUtils.convert(image); + ImageIO.write(bi, "png", imageFile); + } + catch (InterruptedException e) { + Log.error(e); + } + catch (IOException e) { + Log.error(e); + } + return "ok"; + } + + public void finished() { + ChatRoomImpl roomImpl = (ChatRoomImpl)room; + sendFile(imageFile, roomImpl.getParticipantJID()); + SparkManager.getChatManager().getChatContainer().activateChatRoom(room); + } + }; + worker.start(); + } + + /** + * Returns an image if one is found in the clipboard, otherwise null is returned. + * + * @return the image in the clipboard if found, otherwise null. + */ + public static Image getClipboard() { + Transferable t = Toolkit.getDefaultToolkit().getSystemClipboard().getContents(null); + + try { + if (t != null && t.isDataFlavorSupported(DataFlavor.imageFlavor)) { + Image text = (Image)t.getTransferData(DataFlavor.imageFlavor); + return text; + } + } + catch (UnsupportedFlavorException e) { + } + catch (IOException e) { + } + return null; + } + + /** + * Adds a new TransferListener to the SparkManager. FileTransferListeners can be used + * to intercept incoming file transfers for own customizations. You may wish to not + * allow certain file transfers, or have your own UI to handle incoming files. + * + * @param listener the listener + */ + public void addTransferListener(FileTransferListener listener) { + listeners.add(listener); + } + + /** + * Removes the FileTransferListener. + * + * @param listener the listener + */ + public void removeTransferListener(FileTransferListener listener) { + listeners.remove(listener); + } + + private boolean fireTransferListeners(FileTransferRequest request) { + final Iterator iter = new ArrayList(listeners).iterator(); + while (iter.hasNext()) { + FileTransferListener listener = (FileTransferListener)iter.next(); + boolean accepted = listener.handleTransfer(request); + if (accepted) { + return true; + } + } + return false; + } + + private JFileChooser getFileChooser() { + if (fc == null) { + fc = new JFileChooser(defaultDirectory); + fc.setMultiSelectionEnabled(true); + if (Spark.isWindows()) { + fc.setFileSystemView(new WindowsFileSystemView()); + } + } + return fc; + } +} diff --git a/src/java/org/jivesoftware/spark/filetransfer/package.html b/src/java/org/jivesoftware/spark/filetransfer/package.html new file mode 100644 index 00000000..f1eef4c9 --- /dev/null +++ b/src/java/org/jivesoftware/spark/filetransfer/package.html @@ -0,0 +1 @@ +Provides support for intercepting file transfers within Spark. \ No newline at end of file diff --git a/src/java/org/jivesoftware/spark/package.html b/src/java/org/jivesoftware/spark/package.html new file mode 100644 index 00000000..aebedeee --- /dev/null +++ b/src/java/org/jivesoftware/spark/package.html @@ -0,0 +1 @@ +Provides the Managers used as the main entry points into the Spark client. Use the SparkManager to gain access to most managers within Spark. diff --git a/src/java/org/jivesoftware/spark/plugin/ContextMenuListener.java b/src/java/org/jivesoftware/spark/plugin/ContextMenuListener.java new file mode 100644 index 00000000..3ed93e9d --- /dev/null +++ b/src/java/org/jivesoftware/spark/plugin/ContextMenuListener.java @@ -0,0 +1,49 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.plugin; + +import javax.swing.JPopupMenu; + +import java.awt.event.MouseEvent; + +/** + * The ContextMenuListener allows implementors to add their own menu + * items to the context menu associated with this listener. + */ +public interface ContextMenuListener { + + /** + * Called just before the context menu is popping up. + * + * @param object the object the event was fired for. + * @param popup the PopupMenu to be displayed. + */ + void poppingUp(Object object, JPopupMenu popup); + + /** + * Called just before the context menu closed. + * + * @param popup the popup menu in the process of closing. + */ + void poppingDown(JPopupMenu popup); + + /** + * Called when the user double clicks on an item that has a popup menu. + * Only one listener should return true from this menu. + * + * @param e the current mouse event + * @return true if user handles the default action. + */ + boolean handleDefaultAction(MouseEvent e); + +} + + diff --git a/src/java/org/jivesoftware/spark/plugin/Invokable.java b/src/java/org/jivesoftware/spark/plugin/Invokable.java new file mode 100644 index 00000000..b9df303a --- /dev/null +++ b/src/java/org/jivesoftware/spark/plugin/Invokable.java @@ -0,0 +1,29 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.plugin; + +/** + * The Invokable interface can be used to identify a class as being capable + * of being "invoked". + *

        + * + * @author Derek DeMoro + */ +public interface Invokable { + + /** + * Invokes the object. + * + * @param params optional arguments from the invoker. + * @return true if the invocation was successful, false if it failed, or was aborted. + */ + boolean invoke(Object[] params); +} \ No newline at end of file diff --git a/src/java/org/jivesoftware/spark/plugin/Plugin.java b/src/java/org/jivesoftware/spark/plugin/Plugin.java new file mode 100644 index 00000000..925231d0 --- /dev/null +++ b/src/java/org/jivesoftware/spark/plugin/Plugin.java @@ -0,0 +1,69 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.plugin; + +/** + * The Plugin interface is the required implementation to have your Sparkplugs work within the Spark client. + * Users will implement this interface, register the class in their own plugin.xml and do any initialization + * within the initialize method. It's also a good idea to unregister any components, listeners and other resources + * in the #uninstall method to allow for better usability. + */ +public interface Plugin { + + + /** + * Invoked by the PluginManager after the instance of the + * Plugin is instantiated. When invoked, The Plugin + * should register with whatever listeners they may need to use or are required + * for use during this classes lifecycle. Plugin authors should take + * care to ensure that any extraneous initialization is not preformed on this method, and + * any startup code that can be delayed until a later time is delayed, as + * the Plugin's are synchronously initialized during the + * startup of Spark, and each Plugin has the potential to + * negatively impact the startup time of the product. + * + * @see org.jivesoftware.spark.PluginManager + */ + public void initialize(); + + /** + * This method is invoked by the PluginManager when Spark + * wishes you to remove any temporary resources (in memory) such as installed + * components, or settings. Any non java resources (file handles, database connections, + * etc) which are still being held by this Plugin should be + * released by this method immediately. This method is not guaranteed to + * be called, but on normal terminations of Spark, this method will be + * invoked. + */ + public void shutdown(); + + /** + * This method is invoked by the PluginManager before Spark + * terminates. Plugin's should NOT use this method to release resources. + * They should only use this method to give users the opportunity to + * cancel the exit process if there is some process started by this + * plugin still running. + *

        + * Implementations should return false to cancel the shutdown + * process. + */ + public boolean canShutDown(); + + + /** + * This method is invoked by the PluginManager when a Spark user + * asks that this plugin be uninstalled. Before this method is called, you + * will need to release all your in-memory resources in the #shutdown method. This + * method should be used to remove on disk resources such as files, images, etc. + */ + public void uninstall(); + +} \ No newline at end of file diff --git a/src/java/org/jivesoftware/spark/plugin/PluginClassLoader.java b/src/java/org/jivesoftware/spark/plugin/PluginClassLoader.java new file mode 100644 index 00000000..ff1da891 --- /dev/null +++ b/src/java/org/jivesoftware/spark/plugin/PluginClassLoader.java @@ -0,0 +1,74 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.plugin; + +import java.io.File; +import java.io.FilenameFilter; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; + +/** + * A simple classloader to extend the classpath to + * include all jars in a lib directory.

        + *

        + * The new classpath includes all *.jar files. + * + * @author Derek DeMoro + */ +public class PluginClassLoader extends URLClassLoader { + + /** + * Constructs the classloader. + * + * @param parent the parent class loader (or null for none). + * @param libDir the directory to load jar files from. + * @throws java.net.MalformedURLException if the libDir path is not valid. + */ + public PluginClassLoader(ClassLoader parent, File libDir) throws MalformedURLException { + super(new URL[]{libDir.toURL()}, parent); + } + + /** + * Adds all archives in a plugin to the classpath. + * + * @param pluginDir the directory of the plugin. + * @throws MalformedURLException the exception thrown if URL is not valid. + */ + public void addPlugin(File pluginDir) throws MalformedURLException { + File libDir = new File(pluginDir, "lib"); + + File[] jars = libDir.listFiles(new FilenameFilter() { + public boolean accept(File dir, String name) { + boolean accept = false; + String smallName = name.toLowerCase(); + if (smallName.endsWith(".jar")) { + accept = true; + } + else if (smallName.endsWith(".zip")) { + accept = true; + } + return accept; + } + }); + + // Do nothing if no jar or zip files were found + if (jars == null) { + return; + } + + for (int i = 0; i < jars.length; i++) { + if (jars[i].isFile()) { + addURL(jars[i].toURL()); + } + } + } +} diff --git a/src/java/org/jivesoftware/spark/plugin/PublicPlugin.java b/src/java/org/jivesoftware/spark/plugin/PublicPlugin.java new file mode 100644 index 00000000..39553903 --- /dev/null +++ b/src/java/org/jivesoftware/spark/plugin/PublicPlugin.java @@ -0,0 +1,134 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.plugin; + +import java.io.File; + +public class PublicPlugin { + private String name; + private String pluginClass; + private String version; + private String author; + private String email; + private String description; + private String homePage; + private String downloadURL; + private boolean changeLogAvailable; + private boolean readMeAvailable; + private boolean smallIconAvailable; + private boolean largeIconAvailable; + + private File pluginDir; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getPluginClass() { + return pluginClass; + } + + public void setPluginClass(String pluginClass) { + this.pluginClass = pluginClass; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + public String getAuthor() { + return author; + } + + public void setAuthor(String author) { + this.author = author; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getHomePage() { + return homePage; + } + + public void setHomePage(String homePage) { + this.homePage = homePage; + } + + public File getPluginDir() { + return pluginDir; + } + + public void setPluginDir(File pluginDir) { + this.pluginDir = pluginDir; + } + + public String getDownloadURL() { + return downloadURL; + } + + public void setDownloadURL(String downloadURL) { + this.downloadURL = downloadURL; + } + + public boolean isChangeLogAvailable() { + return changeLogAvailable; + } + + public void setChangeLogAvailable(boolean changeLogAvailable) { + this.changeLogAvailable = changeLogAvailable; + } + + public boolean isReadMeAvailable() { + return readMeAvailable; + } + + public void setReadMeAvailable(boolean readMeAvailable) { + this.readMeAvailable = readMeAvailable; + } + + public boolean isSmallIconAvailable() { + return smallIconAvailable; + } + + public void setSmallIconAvailable(boolean smallIconAvailable) { + this.smallIconAvailable = smallIconAvailable; + } + + public boolean isLargeIconAvailable() { + return largeIconAvailable; + } + + public void setLargeIconAvailable(boolean largeIconAvailable) { + this.largeIconAvailable = largeIconAvailable; + } +} diff --git a/src/java/org/jivesoftware/spark/plugin/package.html b/src/java/org/jivesoftware/spark/plugin/package.html new file mode 100644 index 00000000..f005bfd9 --- /dev/null +++ b/src/java/org/jivesoftware/spark/plugin/package.html @@ -0,0 +1 @@ +Provides the API for creating and registring Plugins. diff --git a/src/java/org/jivesoftware/spark/preference/Preference.java b/src/java/org/jivesoftware/spark/preference/Preference.java new file mode 100644 index 00000000..5f68f8ba --- /dev/null +++ b/src/java/org/jivesoftware/spark/preference/Preference.java @@ -0,0 +1,102 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.preference; + +import javax.swing.Icon; +import javax.swing.JComponent; + +/** + * The Preference class allows plugin developers to add their own preferences + * to the Spark client. + */ +public interface Preference { + + /** + * Return the title to use in the preference window. + * + * @return the title to use inside the preferences list. + */ + String getTitle(); + + /** + * Return the icon to use inside the Preferences list. The standard icon size + * for preferences is 24x24. + * + * @return the icon to use inside the Preferences list. + */ + Icon getIcon(); + + /** + * Return the tooltip to use for this preference. The tooltip is displayed + * whenever a user places their mouse cursor over the icon. + * + * @return the tooltip to display. + */ + String getTooltip(); + + /** + * Return the title to use inside the Preferences list. The title is displayed below + * and centered of the icon. + * + * @return the title to use inside the preferences list. + */ + String getListName(); + + /** + * Returns the associated namespace of this preference. + * + * @return the associated namespace of this preference. + */ + String getNamespace(); + + /** + * Return the UI to display whenever this preference is selected in the preference dialog. + * + * @return the UI to display when this preference is selected. + */ + JComponent getGUI(); + + /** + * Called whenever the preference is invoked from the Preference list. + */ + void load(); + + /** + * Called whenever the preference should be saved. + */ + void commit(); + + /** + * Return true if the data supplied is valid, otherwise return false. + * + * @return true if the data supplied is valid. + */ + boolean isDataValid(); + + /** + * The error message to display if #isDataDisplayed returns false. + * + * @return the error message to display. + */ + String getErrorMessage(); + + /** + * Returns the data model representing this preference. + * + * @return the data model representing this preference. + */ + Object getData(); + + /** + * Called when Spark is closing. This should be used to persist any information at that time. + */ + void shutdown(); +} \ No newline at end of file diff --git a/src/java/org/jivesoftware/spark/preference/PreferenceManager.java b/src/java/org/jivesoftware/spark/preference/PreferenceManager.java new file mode 100644 index 00000000..2547e62e --- /dev/null +++ b/src/java/org/jivesoftware/spark/preference/PreferenceManager.java @@ -0,0 +1,107 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.preference; + +import org.jivesoftware.MainWindowListener; +import org.jivesoftware.spark.SparkManager; +import org.jivesoftware.sparkimpl.preference.PreferenceDialog; +import org.jivesoftware.sparkimpl.preference.PreferencesPanel; +import org.jivesoftware.sparkimpl.preference.chat.ChatPreference; +import org.jivesoftware.sparkimpl.settings.local.LocalPreference; + +import javax.swing.JDialog; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +public class PreferenceManager { + private Map map = new LinkedHashMap(); + private PreferenceDialog preferenceDialog; + + public PreferenceManager() { + // Initialize base preferences + ChatPreference chatPreferences = new ChatPreference(); + addPreference(chatPreferences); + chatPreferences.load(); + + + LocalPreference localPreferences = new LocalPreference(); + addPreference(localPreferences); + localPreferences.load(); + + getPreferences(); + + SparkManager.getMainWindow().addMainWindowListener(new MainWindowListener() { + public void shutdown() { + fireShutdown(); + } + + public void mainWindowActivated() { + + } + + public void mainWindowDeactivated() { + + } + }); + } + + + public void showPreferences() { + preferenceDialog = new PreferenceDialog(); + + preferenceDialog.invoke(SparkManager.getMainWindow(), new PreferencesPanel(getPreferences())); + } + + + public void addPreference(Preference preference) { + map.put(preference.getNamespace(), preference); + } + + public void removePreference(Preference preference) { + map.remove(preference.getNamespace()); + } + + public Preference getPreference(String namespace) { + return (Preference)map.get(namespace); + } + + public Object getPreferenceData(String namespace) { + return getPreference(namespace).getData(); + } + + public Iterator getPreferences() { + final List returnList = new ArrayList(); + final Iterator iter = map.keySet().iterator(); + while (iter.hasNext()) { + final String namespace = (String)iter.next(); + returnList.add(map.get(namespace)); + } + return returnList.iterator(); + + } + + private void fireShutdown() { + final Iterator iter = map.keySet().iterator(); + while (iter.hasNext()) { + final String namespace = (String)iter.next(); + final Preference preference = (Preference)map.get(namespace); + preference.shutdown(); + } + } + + public JDialog getPreferenceDialog() { + return preferenceDialog.getDialog(); + } +} diff --git a/src/java/org/jivesoftware/spark/preference/package.html b/src/java/org/jivesoftware/spark/preference/package.html new file mode 100644 index 00000000..a27590c0 --- /dev/null +++ b/src/java/org/jivesoftware/spark/preference/package.html @@ -0,0 +1 @@ +Provides support for adding preferences to the Preference Manager in Spark. \ No newline at end of file diff --git a/src/java/org/jivesoftware/spark/search/SearchManager.java b/src/java/org/jivesoftware/spark/search/SearchManager.java new file mode 100644 index 00000000..706c9e8b --- /dev/null +++ b/src/java/org/jivesoftware/spark/search/SearchManager.java @@ -0,0 +1,126 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.search; + +import org.jivesoftware.spark.SparkManager; +import org.jivesoftware.spark.util.SwingWorker; +import org.jivesoftware.sparkimpl.search.users.UserSearchService; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * Users of the SearchManager can add their own Searchable objects to the Spark + * search service. This allows for a pluggable search architecture by simply plugging into the + * find area of the bottom of Spark. + */ +public class SearchManager { + private List searchServices = new ArrayList(); + private SearchService ui; + + private static SearchManager singleton; + private static final Object LOCK = new Object(); + + /** + * Returns the singleton instance of SearchManager, + * creating it if necessary. + *

        + * + * @return the singleton instance of SearchManager + */ + public static SearchManager getInstance() { + // Synchronize on LOCK to ensure that we don't end up creating + // two singletons. + synchronized (LOCK) { + if (null == singleton) { + SearchManager controller = new SearchManager(); + singleton = controller; + return controller; + } + } + return singleton; + } + + private SearchManager() { + ui = new SearchService(); + + // By default, the user search is first. + SwingWorker worker = new SwingWorker() { + UserSearchService searchWizard = null; + + public Object construct() { + searchWizard = new UserSearchService(); + return searchWizard; + } + + public void finished() { + if (searchWizard.getSearchServices() != null) { + ui.setActiveSearchService(searchWizard); + addSearchService(searchWizard); + } + } + }; + + worker.start(); + + + } + + /** + * Add your own Searchable service.The UI will take + * immediate effect to indicate that this search service is now available as + * an option. + * + * @param searchable the search service. + */ + public void addSearchService(Searchable searchable) { + searchServices.add(searchable); + checkSearchService(); + } + + /** + * Remove the Searchable service. The UI will take + * immediate effect to indicate that this search service is no longer + * an option. + * + * @param searchable the searchable object to remove. + */ + public void removeSearchService(Searchable searchable) { + searchServices.remove(searchable); + checkSearchService(); + } + + /** + * Returns all registered search services. + * + * @return the collection of search services. + */ + public Collection getSearchServices() { + return searchServices; + } + + private void checkSearchService() { + Collection searchables = SparkManager.getSearchManager().getSearchServices(); + if (searchables.size() <= 1) { + ui.getFindField().enableDropdown(false); + } + else { + ui.getFindField().enableDropdown(true); + } + } + + public SearchService getSearchServiceUI() { + return ui; + } + + +} diff --git a/src/java/org/jivesoftware/spark/search/SearchService.java b/src/java/org/jivesoftware/spark/search/SearchService.java new file mode 100644 index 00000000..1b235f4a --- /dev/null +++ b/src/java/org/jivesoftware/spark/search/SearchService.java @@ -0,0 +1,205 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.search; + +import org.jivesoftware.Spark; +import org.jivesoftware.resource.Default; +import org.jivesoftware.resource.SparkRes; +import org.jivesoftware.spark.SparkManager; +import org.jivesoftware.spark.Workspace; +import org.jivesoftware.spark.component.IconTextField; +import org.jivesoftware.spark.util.ResourceUtils; +import org.jivesoftware.spark.util.SwingWorker; + +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.Icon; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JPopupMenu; +import javax.swing.UIManager; + +import java.awt.Color; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Image; +import java.awt.Insets; +import java.awt.event.ActionEvent; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.geom.AffineTransform; +import java.util.Collection; +import java.util.Iterator; + +public class SearchService extends JPanel { + private IconTextField findField; + private Image backgroundImage; + private boolean newSearch; + + private Searchable activeSearchable; + + public SearchService() { + setLayout(new GridBagLayout()); + findField = new IconTextField(SparkRes.getImageIcon(SparkRes.SEARCH_USER_16x16)); + + backgroundImage = Default.getImageIcon(Default.TOP_BOTTOM_BACKGROUND_IMAGE).getImage(); + + final JLabel findLabel = new JLabel(); + + ResourceUtils.resLabel(findLabel, findField, "&Find"); + + // add(findLabel, new GridBagConstraints(0, 1, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + + if (Spark.isMac()) { + add(findField, new GridBagConstraints(1, 1, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 30), 0, 0)); + } + else { + add(findField, new GridBagConstraints(1, 1, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); + } + + // Check for secure connection + if (SparkManager.getConnection().isSecureConnection()) { + final JLabel lockLabel = new JLabel(); + lockLabel.setHorizontalTextPosition(JLabel.LEFT); + lockLabel.setIcon(SparkRes.getImageIcon(SparkRes.LOCK_16x16)); + add(lockLabel, new GridBagConstraints(2, 1, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 0, 5, 5), 0, 0)); + lockLabel.setToolTipText("Spark is running in secure mode."); + } + + findField.setToolTipText("Search for Contacts"); + + findField.getTextComponent().addKeyListener(new KeyListener() { + public void keyTyped(KeyEvent e) { + + } + + public void keyPressed(KeyEvent e) { + if (e.getKeyChar() == KeyEvent.VK_ENTER) { + final Icon previousIcon = findField.getIcon(); + + findField.setIcon(SparkRes.getImageIcon(SparkRes.BUSY_IMAGE)); + findField.validate(); + findField.repaint(); + + SwingWorker worker = new SwingWorker() { + public Object construct() { + activeSearchable.search(findField.getText()); + return "ok"; + } + + public void finished() { + findField.setIcon(previousIcon); + findField.setText(""); + } + }; + + + worker.start(); + } + } + + public void keyReleased(KeyEvent e) { + + } + }); + + findField.getTextComponent().addMouseListener(new MouseAdapter() { + public void mouseClicked(MouseEvent e) { + if (newSearch) { + findField.setText(""); + findField.getTextComponent().setForeground((Color)UIManager.get("TextField.foreground")); + newSearch = false; + } + } + }); + + + Workspace workspace = SparkManager.getWorkspace(); + workspace.add(this, new GridBagConstraints(0, 10, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0)); + workspace.invalidate(); + workspace.validate(); + workspace.repaint(); + + + findField.getImageComponent().addMouseListener(new MouseAdapter() { + public void mouseClicked(MouseEvent e) { + Collection searchables = SparkManager.getSearchManager().getSearchServices(); + if (searchables.size() <= 1) { + return; + } + + // Show popup + final JPopupMenu popup = new JPopupMenu(); + Iterator iter = searchables.iterator(); + while (iter.hasNext()) { + final Searchable searchable = (Searchable)iter.next(); + Action action = new AbstractAction() { + public void actionPerformed(ActionEvent e) { + setActiveSearchService(searchable); + } + }; + + action.putValue(Action.SMALL_ICON, searchable.getIcon()); + action.putValue(Action.NAME, searchable.getName()); + popup.add(action); + } + + popup.show(findField, 0, findField.getHeight()); + + } + }); + } + + public void setActiveSearchService(final Searchable searchable) { + this.activeSearchable = searchable; + + newSearch = true; + findField.requestFocus(); + findField.getTextComponent().setForeground((Color)UIManager.get("TextField.lightforeground")); + findField.setIcon(searchable.getIcon()); + findField.setText(searchable.getDefaultText()); + findField.setToolTipText(searchable.getToolTip()); + + findField.getTextComponent().addFocusListener(new FocusListener() { + public void focusGained(FocusEvent e) { + findField.setText(""); + } + + public void focusLost(FocusEvent e) { + findField.getTextComponent().setForeground((Color)UIManager.get("TextField.lightforeground")); + findField.setText(searchable.getDefaultText()); + } + }); + } + + public void paintComponent(Graphics g) { + double scaleX = getWidth() / (double)backgroundImage.getWidth(null); + double scaleY = getHeight() / (double)backgroundImage.getHeight(null); + AffineTransform xform = AffineTransform.getScaleInstance(scaleX, scaleY); + ((Graphics2D)g).drawImage(backgroundImage, xform, this); + } + + protected IconTextField getFindField() { + return findField; + } + + public void setBackgroundImage(Image image) { + this.backgroundImage = image; + } + + +} diff --git a/src/java/org/jivesoftware/spark/search/Searchable.java b/src/java/org/jivesoftware/spark/search/Searchable.java new file mode 100644 index 00000000..0320819e --- /dev/null +++ b/src/java/org/jivesoftware/spark/search/Searchable.java @@ -0,0 +1,58 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.search; + +import javax.swing.Icon; + +/** + * Plugin writers will implement Searchable in order to tie into the + * find feature in Spark. + */ +public interface Searchable { + + /** + * Return the icon you wish to use in the IconTextField. + * + * @return the icon you wish to use in the icon text field. + */ + Icon getIcon(); + + /** + * Return the name of your plugin. + * + * @return the name of your searchable object. + */ + String getName(); + + /** + * Return the default text that appears in the textfield when a user selects + * it in the dropdown list. + * + * @return the default text. + */ + String getDefaultText(); + + /** + * Return the text you wish to show in the tooltip when a user hovers over the + * searchable find field. + * + * @return the tooltip text. + */ + String getToolTip(); + + /** + * Is called whenver a user does an explict search within Spark. You are responsible + * for the searching and displaying of the search results. + * + * @param query the explict query. + */ + void search(String query); +} diff --git a/src/java/org/jivesoftware/spark/search/package.html b/src/java/org/jivesoftware/spark/search/package.html new file mode 100644 index 00000000..b5847fdb --- /dev/null +++ b/src/java/org/jivesoftware/spark/search/package.html @@ -0,0 +1 @@ +Provides support for creating your own searchable objects within Spark. This is similar to the Google search within FireFox. diff --git a/src/java/org/jivesoftware/spark/ui/ChatArea.java b/src/java/org/jivesoftware/spark/ui/ChatArea.java new file mode 100644 index 00000000..ae688677 --- /dev/null +++ b/src/java/org/jivesoftware/spark/ui/ChatArea.java @@ -0,0 +1,691 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.ui; + +import org.jdesktop.jdic.desktop.Desktop; +import org.jivesoftware.Spark; +import org.jivesoftware.resource.EmotionRes; +import org.jivesoftware.spark.SparkManager; +import org.jivesoftware.spark.plugin.ContextMenuListener; +import org.jivesoftware.spark.util.BrowserLauncher; +import org.jivesoftware.spark.util.ModelUtil; +import org.jivesoftware.spark.util.log.Log; + +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.Icon; +import javax.swing.JComponent; +import javax.swing.JMenuItem; +import javax.swing.JPopupMenu; +import javax.swing.JTextPane; +import javax.swing.KeyStroke; +import javax.swing.UIManager; +import javax.swing.text.AttributeSet; +import javax.swing.text.BadLocationException; +import javax.swing.text.Document; +import javax.swing.text.Element; +import javax.swing.text.SimpleAttributeSet; +import javax.swing.text.StyleConstants; +import javax.swing.text.StyledDocument; + +import java.awt.Color; +import java.awt.Cursor; +import java.awt.Font; +import java.awt.event.ActionEvent; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.awt.event.MouseMotionListener; +import java.net.URL; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.StringTokenizer; + +/** + * The ChatArea class handles proper chat text formatting such as url handling. Use ChatArea for proper + * formatting of bold, italics, underlined and urls. + */ +public class ChatArea extends JTextPane implements MouseListener, MouseMotionListener { + /** + * The SimpleAttributeSet used within this instance of JTextPane. + */ + public final SimpleAttributeSet styles = new SimpleAttributeSet(); + + /** + * The default Hand cursor. + */ + public static final Cursor HAND_CURSOR = new Cursor(Cursor.HAND_CURSOR); + + /** + * The default Text Cursor. + */ + public static final Cursor DEFAULT_CURSOR = new Cursor(Cursor.DEFAULT_CURSOR); + + /** + * The currently selected Font Family to use. + */ + private String fontFamily; + + /** + * The currently selected Font Size to use. + */ + private int fontSize; + + private List contextMenuListener = new ArrayList(); + + private JPopupMenu popup; + + + private JMenuItem cutMenu; + private JMenuItem copyMenu; + private JMenuItem pasteMenu; + private JMenuItem selectAll; + + private List interceptors = new ArrayList(); + + /** + * ChatArea Constructor. + */ + public ChatArea() { + final Action cutAction = new AbstractAction() { + public void actionPerformed(ActionEvent e) { + String selectedText = getSelectedText(); + try { + getDocument().remove(getSelectionStart(), getSelectionEnd()); + SparkManager.setClipboard(selectedText); + } + catch (BadLocationException e1) { + Log.error("Error removing selected text", e1); + } + + } + }; + cutAction.putValue(Action.NAME, "Cut"); + + Action copyAction = new AbstractAction() { + public void actionPerformed(ActionEvent e) { + SparkManager.setClipboard(getSelectedText()); + } + }; + copyAction.putValue(Action.NAME, "Copy"); + + Action pasteAction = new AbstractAction() { + public void actionPerformed(ActionEvent e) { + String text = SparkManager.getClipboard(); + try { + Document document = getDocument(); + document.insertString(getCaretPosition(), text, null); + } + catch (BadLocationException e1) { + Log.error("Unable to insert clipboard text.", e1); + } + } + }; + pasteAction.putValue(Action.NAME, "Paste"); + + Action selectAllAction = new AbstractAction() { + public void actionPerformed(ActionEvent e) { + requestFocus(); + selectAll(); + } + }; + selectAllAction.putValue(Action.NAME, "Select All"); + + + cutMenu = new JMenuItem(cutAction); + copyMenu = new JMenuItem(copyAction); + pasteMenu = new JMenuItem(pasteAction); + selectAll = new JMenuItem(selectAllAction); + + // Set Default Font + setFont(new Font("Dialog", Font.PLAIN, 12)); + + + getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("control x"), "cut"); + + getActionMap().put("cut", new AbstractAction("cut") { + public void actionPerformed(ActionEvent evt) { + cutAction.actionPerformed(evt); + } + }); + + getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("control c"), "copy"); + + getActionMap().put("copy", new AbstractAction("copy") { + public void actionPerformed(ActionEvent evt) { + SparkManager.setClipboard(getSelectedText()); + } + }); + + } + + /** + * Set the current text of the ChatArea. + * + * @param message inserts the text directly into the ChatArea + */ + public void setText(String message) { + // By default, use the hand cursor for link selection + // and scrolling. + setCursor(HAND_CURSOR); + + // Make sure the message is not null. + message = message.trim(); + message = message.replaceAll("/\"", ""); + if (ModelUtil.hasLength(message)) { + try { + insert(message); + } + catch (BadLocationException e) { + Log.error(e); + } + } + } + + /** + * Clear the current document. This will remove all text and element + * attributes such as bold, italics, and underlining. Note that the font family and + * font size will be persisted. + */ + public void clear() { + super.setText(""); + if (fontFamily != null) { + setFont(fontFamily); + } + + if (fontSize != 0) { + setFontSize(fontSize); + } + + StyleConstants.setUnderline(styles, false); + StyleConstants.setBold(styles, false); + StyleConstants.setItalic(styles, false); + setCharacterAttributes(styles, false); + } + + + /** + * Does the actual insertion of text, adhering to the styles + * specified during message creation in either the thin or thick client. + * + * @param text - the text to insert. + * @throws BadLocationException + */ + public void insert(String text) throws BadLocationException { + boolean bold = false; + boolean italic = false; + boolean underlined = false; + + + StringTokenizer t = new StringTokenizer(text, " \n", true); + while (t.hasMoreTokens()) { + String textFound = t.nextToken(); + if (textFound.startsWith("http://") || textFound.startsWith("ftp://") + || textFound.startsWith("https://") || textFound.startsWith("www") || textFound.startsWith("\\") || textFound.indexOf("://") != -1) { + insertLink(textFound); + } + else if (!insertImage(textFound)) { + insertText(textFound); + } + } + + // By default, always have decorations off. + StyleConstants.setBold(styles, bold); + StyleConstants.setItalic(styles, italic); + StyleConstants.setUnderline(styles, underlined); + } + + /** + * Inserts text into the current document. + * + * @param text the text to insert + * @throws BadLocationException + */ + public void insertText(String text) throws BadLocationException { + final Document doc = getDocument(); + styles.removeAttribute("link"); + StyleConstants.setForeground(styles, getForeground()); + + doc.insertString(doc.getLength(), text, styles); + } + + /** + * Inserts text into the current document. + * + * @param text the text to insert + * @param color the color of the text + * @throws BadLocationException + */ + public void insertText(String text, Color color) throws BadLocationException { + final Document doc = getDocument(); + StyleConstants.setForeground(styles, color); + doc.insertString(doc.getLength(), text, styles); + } + + /** + * Inserts a link into the current document. + * + * @param link - the link to insert( ex. http://www.javasoft.com ) + * @throws BadLocationException + */ + public void insertLink(String link) throws BadLocationException { + final Document doc = getDocument(); + styles.addAttribute("link", link); + + StyleConstants.setForeground(styles, (Color)UIManager.get("Link.foreground")); + StyleConstants.setUnderline(styles, true); + doc.insertString(doc.getLength(), link, styles); + StyleConstants.setUnderline(styles, false); + StyleConstants.setForeground(styles, (Color)UIManager.get("TextPane.foreground")); + styles.removeAttribute("link"); + setCharacterAttributes(styles, false); + + } + + /** + * Inserts an emotion icon into the current document. + * + * @param image - the smiley representation of the image.( ex. :) ) + * @return true if the image was found, otherwise false. + */ + public boolean insertImage(String image) { + final Document doc = getDocument(); + Icon emotion = EmotionRes.getImageIcon(image.toLowerCase()); + if (emotion == null) { + emotion = EmotionRes.getImageIcon(image); + } + + if (emotion == null) { + return false; + } + setEditable(true); + select(doc.getLength(), doc.getLength()); + insertIcon(emotion); + setEditable(false); + + return true; + } + + /** + * Sets the current element to be either bold or not depending + * on the current state. If the element is currently set as bold, + * it will be set to false, and vice-versa. + */ + public void setBold() { + final Element element = getStyledDocument().getCharacterElement(getCaretPosition() - 1); + if (element != null) { + AttributeSet as = element.getAttributes(); + boolean isBold = StyleConstants.isBold(as); + StyleConstants.setBold(styles, !isBold); + try { + setCharacterAttributes(styles, true); + } + catch (Exception ex) { + Log.error("Error settings bold:", ex); + } + } + } + + /** + * Sets the current element to be either italicized or not depending + * on the current state. If the element is currently set as italic, + * it will be set to false, and vice-versa. + */ + public void setItalics() { + final Element element = getStyledDocument().getCharacterElement(getCaretPosition() - 1); + if (element != null) { + AttributeSet as = element.getAttributes(); + boolean isItalic = StyleConstants.isItalic(as); + StyleConstants.setItalic(styles, !isItalic); + try { + setCharacterAttributes(styles, true); + } + catch (Exception fontException) { + Log.error("Error settings italics:", fontException); + } + } + } + + /** + * Sets the current document to be either underlined or not depending + * on the current state. If the element is currently set as underlined, + * it will be set to false, and vice-versa. + */ + public void setUnderlined() { + final Element element = getStyledDocument().getCharacterElement(getCaretPosition() - 1); + if (element != null) { + AttributeSet as = element.getAttributes(); + boolean isUnderlined = StyleConstants.isUnderline(as); + StyleConstants.setUnderline(styles, !isUnderlined); + try { + setCharacterAttributes(styles, true); + } + catch (Exception underlineException) { + Log.error("Error settings underline:", underlineException); + } + } + } + + /** + * Set the font on the current element. + * + * @param font the font to use with the current element + */ + public void setFont(String font) { + StyleConstants.setFontFamily(styles, font); + try { + setCharacterAttributes(styles, false); + } + catch (Exception fontException) { + Log.error("Error settings font:", fontException); + } + + fontFamily = font; + } + + /** + * Set the current font size. + * + * @param size the current font size. + */ + public void setFontSize(int size) { + StyleConstants.setFontSize(styles, size); + try { + setCharacterAttributes(styles, false); + } + catch (Exception fontException) { + Log.error("Error settings font:", fontException); + } + + fontSize = size; + } + + /** + * Inserts the current font. + * + * @param text - the current font( string representation ) + */ + private void insertFont(String text) { + int index = text.indexOf("="); + int lastIndexOf = text.lastIndexOf(" "); + String font = text.substring(index + 1, lastIndexOf).replaceAll("_", " "); + + + index = text.lastIndexOf("="); + int slash = text.indexOf("/"); + + int fontSize = Integer.parseInt(text.substring(index + 1, slash)); + setFontSize(fontSize); + setFont(font); + } + + + public void mouseClicked(MouseEvent e) { + try { + final int pos = viewToModel(e.getPoint()); + final Element element = getStyledDocument().getCharacterElement(pos); + + if (element != null) { + final AttributeSet as = element.getAttributes(); + final Object o = as.getAttribute("link"); + + if (o != null) { + try { + final String url = (String)o; + boolean handled = fireLinkInterceptors(url); + if (!handled) { + if (Spark.isWindows() || Spark.isMac()) { + BrowserLauncher.openURL(url); + } + else { + Desktop.browse(new URL(url)); + } + } + } + catch (Exception ioe) { + Log.error("Error launching browser:", ioe); + } + } + } + } + catch (Exception ex) { + Log.error("Visible Error", ex); + } + } + + public void mousePressed(MouseEvent e) { + if (e.isPopupTrigger()) { + handlePopup(e); + return; + } + } + + /** + * This launches the BrowserLauncher with the URL + * located in ChatArea. Note that the url will + * automatically be clickable when added to ChatArea + * + * @param e - the MouseReleased event + */ + public void mouseReleased(MouseEvent e) { + if (e.isPopupTrigger()) { + handlePopup(e); + return; + } + + + } + + public void mouseEntered(MouseEvent e) { + } + + public void mouseExited(MouseEvent e) { + } + + public void mouseDragged(MouseEvent e) { + } + + /** + * Checks to see if the mouse is located over a browseable + * link. + * + * @param e - the current MouseEvent. + */ + public void mouseMoved(MouseEvent e) { + checkForLink(e); + } + + /** + * Checks to see if the mouse is located over a browseable + * link. + * + * @param e - the current MouseEvent. + */ + private void checkForLink(MouseEvent e) { + try { + final int pos = viewToModel(e.getPoint()); + final Element element = getStyledDocument().getCharacterElement(pos); + + if (element != null) { + final AttributeSet as = element.getAttributes(); + final Object o = as.getAttribute("link"); + + if (o != null) { + setCursor(HAND_CURSOR); + } + else { + setCursor(DEFAULT_CURSOR); + } + } + } + catch (Exception ex) { + Log.error("Error in CheckLink:", ex); + } + } + + /** + * Examines the chatInput text pane, and returns a string containing the text with any markup + * (jive markup in our case). This will strip any terminating new line from the input. + * + * @return a string of marked up text. + */ + public String getMarkup() { + final StringBuffer buf = new StringBuffer(); + final String text = getText(); + final StyledDocument doc = getStyledDocument(); + final Element rootElem = doc.getDefaultRootElement(); + + // MAY RETURN THIS BLOCK + if (text.trim().length() <= 0) { + return null; + } + + boolean endsInNewline = text.charAt(text.length() - 1) == '\n'; + for (int j = 0; j < rootElem.getElementCount(); j++) { + final Element pElem = rootElem.getElement(j); + + for (int i = 0; i < pElem.getElementCount(); i++) { + final Element e = pElem.getElement(i); + final AttributeSet as = e.getAttributes(); + final boolean bold = StyleConstants.isBold(as); + final boolean italic = StyleConstants.isItalic(as); + final boolean underline = StyleConstants.isUnderline(as); + int end = e.getEndOffset(); + + if (end > text.length()) { + end = text.length(); + } + + if (endsInNewline && end >= text.length() - 1) { + end--; + } + + // swing text.. :-/ + if (j == rootElem.getElementCount() - 1 + && i == pElem.getElementCount() - 1) { + end = text.length(); + } + + final String current = text.substring(e.getStartOffset(), end); + if (bold) { + buf.append("[b]"); + } + if (italic) { + buf.append("[i]"); + } + if (underline) { + buf.append("[u]"); + } + //buf.append( "[font face=/\"" + fontFamily + "/\" size=/\"" + fontSize + "/\"/]" ); + + // Iterator over current string to find url tokens + final StringTokenizer tkn = new StringTokenizer(current, " ", true); + while (tkn.hasMoreTokens()) { + final String token = tkn.nextToken(); + if (token.startsWith("http://") || token.startsWith("ftp://") + || token.startsWith("https://")) { + buf.append("[url]").append(token).append("[/url]"); + } + else if (token.startsWith("www")) { + buf.append("[url "); + buf.append("http://").append(token); + buf.append("]"); + buf.append(token); + buf.append("[/url]"); + } + else { + buf.append(token); + } + } + + // Always add end tags for markup + if (underline) { + buf.append("[/u]"); + } + if (italic) { + buf.append("[/i]"); + } + if (bold) { + buf.append("[/b]"); + } + // buf.append( "[/font]" ); + } + } + + return buf.toString(); + } + + private void handlePopup(MouseEvent e) { + popup = new JPopupMenu(); + popup.add(cutMenu); + popup.add(copyMenu); + popup.add(pasteMenu); + fireContextMenuListeners(); + popup.addSeparator(); + popup.add(selectAll); + + // Handle enable + boolean textSelected = ModelUtil.hasLength(getSelectedText()); + String clipboard = SparkManager.getClipboard(); + cutMenu.setEnabled(textSelected && isEditable()); + copyMenu.setEnabled(textSelected); + pasteMenu.setEnabled(ModelUtil.hasLength(clipboard) && isEditable()); + + popup.show(this, e.getX(), e.getY()); + } + + /** + * Adds a ContextMenuListener to ChatArea. + * + * @param listener the ContextMenuListener. + */ + public void addContextMenuListener(ContextMenuListener listener) { + contextMenuListener.add(listener); + } + + /** + * Remove a ContextMenuListener to ChatArea. + * + * @param listener the ContextMenuListener. + */ + public void removeContextMenuListener(ContextMenuListener listener) { + contextMenuListener.remove(listener); + } + + private void fireContextMenuListeners() { + Iterator listeners = new ArrayList(contextMenuListener).iterator(); + while (listeners.hasNext()) { + ContextMenuListener listener = (ContextMenuListener)listeners.next(); + listener.poppingUp(this, popup); + } + } + + public void addLinkInterceptor(LinkInterceptor interceptor) { + interceptors.add(interceptor); + } + + public void removeLinkInterceptor(LinkInterceptor interceptor) { + interceptors.remove(interceptor); + } + + public boolean fireLinkInterceptors(String link) { + final Iterator iter = new ArrayList(interceptors).iterator(); + while (iter.hasNext()) { + boolean handled = ((LinkInterceptor)iter.next()).handleLink(link); + if (handled) { + return true; + } + } + + return false; + } + +} \ No newline at end of file diff --git a/src/java/org/jivesoftware/spark/ui/ChatContainer.java b/src/java/org/jivesoftware/spark/ui/ChatContainer.java new file mode 100644 index 00000000..43a8281c --- /dev/null +++ b/src/java/org/jivesoftware/spark/ui/ChatContainer.java @@ -0,0 +1,1028 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.ui; + +import org.jivesoftware.MainWindow; +import org.jivesoftware.Spark; +import org.jivesoftware.resource.SparkRes; +import org.jivesoftware.smack.PacketListener; +import org.jivesoftware.smack.Roster; +import org.jivesoftware.smack.filter.AndFilter; +import org.jivesoftware.smack.filter.FromContainsFilter; +import org.jivesoftware.smack.filter.PacketTypeFilter; +import org.jivesoftware.smack.packet.Message; +import org.jivesoftware.smack.packet.Packet; +import org.jivesoftware.smack.packet.Presence; +import org.jivesoftware.smack.util.StringUtils; +import org.jivesoftware.spark.SparkManager; +import org.jivesoftware.spark.component.tabbedPane.SparkTab; +import org.jivesoftware.spark.component.tabbedPane.SparkTabbedPane; +import org.jivesoftware.spark.component.tabbedPane.SparkTabbedPaneListener; +import org.jivesoftware.spark.ui.rooms.ChatRoomImpl; +import org.jivesoftware.spark.ui.rooms.GroupChatRoom; +import org.jivesoftware.spark.ui.status.StatusItem; +import org.jivesoftware.spark.util.ModelUtil; +import org.jivesoftware.spark.util.SwingWorker; +import org.jivesoftware.spark.util.log.Log; +import org.jivesoftware.sparkimpl.settings.local.SettingsManager; + +import javax.swing.AbstractAction; +import javax.swing.BorderFactory; +import javax.swing.Icon; +import javax.swing.JComponent; +import javax.swing.JFrame; +import javax.swing.JOptionPane; +import javax.swing.KeyStroke; +import javax.swing.SwingUtilities; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +import java.awt.Color; +import java.awt.Component; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.Frame; +import java.awt.event.ActionEvent; +import java.awt.event.KeyEvent; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +/** + * Should be subclassed to track all available ChatRooms. + */ +public class ChatContainer extends SparkTabbedPane implements MessageListener, ChangeListener { + /** + * List of all ChatRoom Listeners. + */ + private final List chatRoomListeners = new ArrayList(); + private final List chatRoomList = new ArrayList(); + private final Map presenceMap = new HashMap(); + + private static final String WELCOME_TITLE = SparkRes.getString(SparkRes.WELCOME); + + + private ChatFrame chatFrame; + + /** + * Creates the ChatRooms to hold all ChatRooms. + */ + public ChatContainer() { + // Set minimum size + setMinimumSize(new Dimension(400, 200)); + // Don't allow tabs to shrink and allow scrolling. + + addSparkTabbedPaneListener(new SparkTabbedPaneListener() { + public void tabRemoved(SparkTab tab, Component component, int index) { + stateChanged(null); + cleanupChatRoom((ChatRoom)component); + } + + public void tabAdded(SparkTab tab, Component component, int index) { + stateChanged(null); + } + + public void tabSelected(SparkTab tab, Component component, int index) { + stateChanged(null); + } + + public void allTabsRemoved() { + chatFrame.setTitle(""); + chatFrame.setVisible(false); + } + }); + + setCloseButtonEnabled(true); + + // Add Key Navigation + addKeyNavigation(); + + this.setFocusable(false); + } + + /** + * Adds navigation capability to chat rooms. Users can navigate using the alt-left or right arrow keys. + */ + private void addKeyNavigation() { + KeyStroke leftStroke = KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0); + String leftStrokeString = org.jivesoftware.spark.util.StringUtils.keyStroke2String(leftStroke); + + // Handle Left Arrow + this.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("alt " + leftStrokeString + ""), "navigateLeft"); + this.getActionMap().put("navigateLeft", new AbstractAction("navigateLeft") { + public void actionPerformed(ActionEvent evt) { + int selectedIndex = getSelectedIndex(); + if (selectedIndex > 0) { + setSelectedIndex(selectedIndex - 1); + } + else { + setSelectedIndex(getTabCount() - 1); + } + } + }); + + KeyStroke rightStroke = KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0); + String rightStrokeString = org.jivesoftware.spark.util.StringUtils.keyStroke2String(rightStroke); + + // Handle Right Arrow + this.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("alt " + rightStrokeString + ""), "navigateRight"); + this.getActionMap().put("navigateRight", new AbstractAction("navigateRight") { + public void actionPerformed(ActionEvent evt) { + int selectedIndex = getSelectedIndex(); + if (selectedIndex > -1) { + int count = getTabCount(); + if (selectedIndex == (count - 1)) { + setSelectedIndex(0); + } + else { + setSelectedIndex(selectedIndex + 1); + } + } + } + }); + + this.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("ESCAPE"), "escape"); + this.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("control W"), "escape"); + + this.getActionMap().put("escape", new AbstractAction("escape") { + public void actionPerformed(ActionEvent evt) { + closeActiveRoom(); + } + }); + + } + + + /** + * Adds a new ChatRoom to Spark. + * + * @param room the ChatRoom to add. + */ + public void addChatRoom(final ChatRoom room) { + createFrameIfNeeded(); + + room.setBorder(BorderFactory.createMatteBorder(1, 0, 0, 0, Color.LIGHT_GRAY)); + AndFilter presenceFilter = new AndFilter(new PacketTypeFilter(Presence.class), new FromContainsFilter(room.getRoomname())); + + // Next, create a packet listener. We use an anonymous inner class for brevity. + PacketListener myListener = new PacketListener() { + public void processPacket(final Packet packet) { + SwingUtilities.invokeLater(new Runnable() { + public void run() { + handleRoomPresence((Presence)packet); + } + }); + } + }; + + + SparkManager.getConnection().addPacketListener(myListener, presenceFilter); + + // Add to PresenceMap + presenceMap.put(room.getRoomname(), myListener); + + String tooltip = ""; + if (room instanceof ChatRoomImpl) { + tooltip = ((ChatRoomImpl)room).getParticipantJID(); + String nickname = SparkManager.getUserManager().getUserNicknameFromJID(((ChatRoomImpl)room).getParticipantJID()); + + tooltip = "Contact: " + nickname + "
        JID: " + tooltip; + } + else { + tooltip = ((GroupChatRoom)room).getRoomname(); + } + + // Create ChatRoom UI and dock + addTab(room.getTabTitle(), room.getTabIcon(), room, tooltip); + + + room.addMessageListener(this); + + // Remove brand panel + final String title = getTabAt(0).getActualText(); + if (title.equals(WELCOME_TITLE)) { + chatFrame.setTitle(room.getRoomTitle()); + } + + SwingWorker worker = new SwingWorker() { + public Object construct() { + try { + Thread.sleep(100); + } + catch (InterruptedException e1) { + Log.error(e1); + } + return ""; + } + + public void finished() { + checkVisibility(room); + } + }; + + worker.start(); + + // Add to ChatRoomList + chatRoomList.add(room); + + fireChatRoomOpened(room); + + focusChat(); + } + + /** + * Handles the presence of a one to one chat room. + * + * @param p the presence to handle. + */ + private void handleRoomPresence(final Presence p) { + final String roomname = StringUtils.parseBareAddress(p.getFrom()); + ChatRoom chatRoom; + try { + chatRoom = getChatRoom(roomname); + } + catch (ChatRoomNotFoundException e1) { + Log.error("Could not locate chat room.", e1); + return; + } + + final String userid = StringUtils.parseResource(p.getFrom()); + if (p.getType() == Presence.Type.UNAVAILABLE) { + fireUserHasLeft(chatRoom, userid); + } + else if (p.getType() == Presence.Type.AVAILABLE) { + fireUserHasJoined(chatRoom, userid); + } + + // Change tab icon + if (chatRoom instanceof ChatRoomImpl) { + StatusItem statusItem = SparkManager.getWorkspace().getStatusBar().getItemFromPresence(p); + Roster roster = SparkManager.getConnection().getRoster(); + Icon tabIcon = null; + if (statusItem == null && p == null) { + tabIcon = SparkRes.getImageIcon(SparkRes.CLEAR_BALL_ICON); + } + else if (statusItem == null && p != null && p.getType() == Presence.Type.AVAILABLE) { + tabIcon = SparkRes.getImageIcon(SparkRes.GREEN_BALL); + } + else { + String status = p.getStatus(); + if (status != null && status.indexOf("phone") != -1) { + tabIcon = SparkRes.getImageIcon(SparkRes.ON_PHONE_IMAGE); + } + else if (statusItem == null) { + tabIcon = SparkRes.getImageIcon(SparkRes.CLEAR_BALL_ICON); + } + else { + tabIcon = statusItem.getIcon(); + } + } + + int tabLoc = indexOfComponent(chatRoom); + if (tabLoc != -1) { + getTabAt(tabLoc).setIcon(tabIcon); + } + } + } + + private void checkVisibility(ChatRoom chatRoom) { + if (!chatFrame.isVisible() && SparkManager.getMainWindow().isFocused()) { + chatFrame.setState(Frame.NORMAL); + chatFrame.setVisible(true); + } + else if (chatFrame.isVisible() && !chatFrame.isInFocus()) { + flashWindow(chatRoom); + } + else if (chatFrame.isVisible() && chatFrame.getState() == Frame.ICONIFIED) { + // Set to new tab. + int tabLocation = indexOfComponent(chatRoom); + setSelectedIndex(tabLocation); + + // If the ContactList is in the tray, we need better notification by flashing + // the chatframe. + flashWindow(chatRoom); + } + + // Handle when chat frame is visible but the Contact List is not. + else if (chatFrame.isVisible() && !SparkManager.getMainWindow().isVisible()) { + flashWindow(chatRoom); + } + else if (!chatFrame.isVisible()) { + if (Spark.isWindows()) { + chatFrame.setFocusableWindowState(false); + chatFrame.setState(Frame.ICONIFIED); + } + chatFrame.setVisible(true); + + // Set to new tab. + int tabLocation = indexOfComponent(chatRoom); + setSelectedIndex(tabLocation); + + // If the ContactList is in the tray, we need better notification by flashing + // the chatframe. + if (!SparkManager.getMainWindow().isVisible()) { + flashWindow(chatRoom); + } + else if (chatFrame.getState() == Frame.ICONIFIED) { + flashWindow(chatRoom); + } + + } + } + + + /** + * Removes the ChatRoom resources. + * + * @param room the room to remove. + */ + private void cleanupChatRoom(ChatRoom room) { + fireChatRoomClosed(room); + room.removeMessageListener(this); + + + final PacketListener listener = (PacketListener)presenceMap.get(room.getRoomname()); + if (listener != null) { + SparkManager.getConnection().removePacketListener(listener); + } + + // Remove mappings + presenceMap.remove(room.getRoomname()); + + chatRoomList.remove(room); + } + + /** + * Close all chat rooms. + */ + public void closeAllChatRooms() { + Iterator iter = new ArrayList(chatRoomList).iterator(); + while (iter.hasNext()) { + ChatRoom chatRoom = (ChatRoom)iter.next(); + closeTab(chatRoom); + chatRoom.closeChatRoom(); + } + } + + /** + * Leaves a ChatRoom. Leaving a chat room does everything but close the room itself. + * + * @param room the room to leave. + */ + public void leaveChatRoom(ChatRoom room) { + // Notify that the chatroom has been left. + fireChatRoomLeft(room); + room.leaveChatRoom(); + + // Setting the tab to be "disabled". Will not actually disable the tab because + // that doesn't allow for selection. + final int location = indexOfComponent(room); + if (location != -1) { +// setBackgroundAt(location, Color.GRAY); + // setForegroundAt(location, Color.GRAY); + // setIconAt(location, null); + } + + final PacketListener listener = (PacketListener)presenceMap.get(room.getRoomname()); + if (listener != null) { + SparkManager.getConnection().removePacketListener(listener); + } + } + + /** + * Returns a ChatRoom by name. + * + * @param roomName the name of the ChatRoom. + * @return the ChatRoom + * @throws ChatRoomNotFoundException + */ + public ChatRoom getChatRoom(String roomName) throws ChatRoomNotFoundException { + for (int i = 0; i < getTabCount(); i++) { + ChatRoom room = null; + try { + room = getChatRoom(i); + } + catch (ChatRoomNotFoundException e1) { + // Ignore + } + + if (room != null && room.getRoomname().equalsIgnoreCase(roomName) && room.isActive()) { + return room; + } + } + throw new ChatRoomNotFoundException(roomName + " not found."); + } + + /** + * Returns a ChatRoom in the specified tab location. + * + * @param location the tab location. + * @return the ChatRoom found. + * @throws ChatRoomNotFoundException + */ + public ChatRoom getChatRoom(int location) throws ChatRoomNotFoundException { + if (getTabCount() < location) { + return null; + } + try { + Component comp = getComponentAt(location); + if (comp != null && comp instanceof ChatRoom) { + return (ChatRoom)comp; + } + } + catch (ArrayIndexOutOfBoundsException outOfBoundsEx) { + Log.error("Error getting Chat Room", outOfBoundsEx); + } + + throw new ChatRoomNotFoundException(); + } + + /** + * Returns the Active ChatRoom. + * + * @return the ChatRoom active in the tabbed pane. + * @throws ChatRoomNotFoundException is thrown if no chat room is found. + */ + public ChatRoom getActiveChatRoom() throws ChatRoomNotFoundException { + int location = getSelectedIndex(); + if (location != -1) { + return getChatRoom(location); + } + throw new ChatRoomNotFoundException(); + } + + /** + * Returns all the ChatRooms found in the UI. + * + * @return all ChatRooms found in the UI. + */ + public Iterator getAllChatRooms() { + return chatRoomList.iterator(); + } + + /** + * Activates the specified ChatRoom. + * + * @param room the ChatRoom to activate. + */ + public void activateChatRoom(ChatRoom room) { + int tabLocation = indexOfComponent(room); + setSelectedIndex(tabLocation); + if (!chatFrame.isVisible()) { + chatFrame.setVisible(true); + } + + if (chatFrame.getState() == Frame.ICONIFIED) { + chatFrame.setState(Frame.NORMAL); + } + chatFrame.requestFocus(); + focusChat(); + } + + /** + * Activates the component in tabbed pane. + * + * @param component the component contained within the tab to activate. + */ + public void activateComponent(Component component) { + int tabLocation = indexOfComponent(component); + if (tabLocation != -1) { + setSelectedIndex(tabLocation); + } + } + + /** + * Used for Tray Notifications. + * + * @param room the ChatRoom where the message was received. + * @param message the message received. + */ + public void messageReceived(ChatRoom room, Message message) { + // Check to see if it's a room update. + String from = message.getFrom(); + String insertMessage = message.getBody(); + if (room.getChatType() == Message.Type.CHAT) { + from = StringUtils.parseName(from); + } + else { + from = StringUtils.parseResource(from); + } + + if (ModelUtil.hasLength(from)) { + insertMessage = from + ": " + insertMessage; + } + + fireNotifyOnMessage(room); + } + + public void fireNotifyOnMessage(final ChatRoom chatRoom) { + ChatRoom activeChatRoom = null; + try { + activeChatRoom = getActiveChatRoom(); + } + catch (ChatRoomNotFoundException e1) { + Log.error(e1); + } + + if (chatFrame.isVisible() && (chatFrame.getState() == Frame.ICONIFIED || chatFrame.getInactiveTime() > 60000)) { + int tabLocation = indexOfComponent(chatRoom); + setSelectedIndex(tabLocation); + startFlashing(chatRoom); + return; + } + + if (!chatFrame.isVisible() && SparkManager.getMainWindow().isFocused()) { + chatFrame.setState(Frame.NORMAL); + chatFrame.setVisible(true); + } + else if (chatFrame.isVisible() && !chatFrame.isInFocus()) { + startFlashing(chatRoom); + } + else if (chatFrame.isVisible() && chatFrame.getState() == Frame.ICONIFIED) { + // Set to new tab. + int tabLocation = indexOfComponent(chatRoom); + setSelectedIndex(tabLocation); + + // If the ContactList is in the tray, we need better notification by flashing + // the chatframe. + startFlashing(chatRoom); + } + + // Handle when chat frame is visible but the Contact List is not. + else if (chatFrame.isVisible() && !SparkManager.getMainWindow().isVisible() && !chatFrame.isInFocus()) { + startFlashing(chatRoom); + } + else if (!chatFrame.isVisible()) { + if (Spark.isWindows()) { + chatFrame.setFocusableWindowState(false); + chatFrame.setState(Frame.ICONIFIED); + } + chatFrame.setVisible(true); + + // Set to new tab. + int tabLocation = indexOfComponent(chatRoom); + setSelectedIndex(tabLocation); + + // If the ContactList is in the tray, we need better notification by flashing + // the chatframe. + if (!SparkManager.getMainWindow().isVisible()) { + startFlashing(chatRoom); + } + else if (chatFrame.getState() == Frame.ICONIFIED) { + startFlashing(chatRoom); + } + + } + else if (chatRoom != activeChatRoom) { + startFlashing(chatRoom); + } + + } + + public void messageSent(ChatRoom room, Message message) { + } + + /** + * Notification that the tab pane has been modified. Generally by changing of the tabs. + * + * @param e the ChangeEvent. + */ + public void stateChanged(ChangeEvent e) { + stopFlashing(); + + // Request focus in Chat Area if selected + final ChatRoom room; + try { + room = getActiveChatRoom(); + + focusChat(); + + // Set the title of the room. + chatFrame.setTitle(room.getRoomTitle()); + } + catch (ChatRoomNotFoundException e1) { + // Ignore + } + + } + + + private void stopFlashing() { + // Get current tab + int sel = getSelectedIndex(); + if (sel != -1) { + final ChatRoom room; + try { + room = getChatRoom(sel); + stopFlashing(room); + } + catch (ChatRoomNotFoundException e1) { + //AgentLog.logError("Could not find chat room.", e1); + } + } + } + + + /** + * Closes a tab of a room. + * + * @param component the component inside of the tab to close. + */ + public void closeTab(Component component) { + int location = indexOfComponent(component); + if (location == -1) { + return; + } + + if (getTabCount() == 0) { + chatFrame.setTitle(""); + chatFrame.setVisible(false); + } + + this.removeTabAt(location); + } + + public void closeActiveRoom() { + ChatRoom room = null; + try { + room = getActiveChatRoom(); + } + catch (ChatRoomNotFoundException e1) { + // AgentLog.logError("Chat room not found", e1); + } + + // Confirm end session + boolean isGroupChat = room.getChatType() == Message.Type.GROUP_CHAT; + String message = "Would you like to end this session?"; + if (true) { + room.closeChatRoom(); + return; + } + else { + if (!room.isActive()) { + room.closeChatRoom(); + return; + } + } + + final int ok = JOptionPane.showConfirmDialog(SparkManager.getMainWindow(), message, + "Confirmation", JOptionPane.YES_NO_OPTION); + if (ok == JOptionPane.OK_OPTION) { + room.closeChatRoom(); + return; + } + } + + public String toString() { + StringBuffer buf = new StringBuffer(); + Iterator iter = chatRoomList.iterator(); + while (iter.hasNext()) { + ChatRoom room = (ChatRoom)iter.next(); + buf.append("Roomname=").append(room.getRoomname()).append("\n"); + } + return buf.toString(); + } + + + /** + * Returns true if there are any Rooms present. + * + * @return true if Rooms are present, otherwise false. + */ + public boolean hasRooms() { + int count = getSelectedIndex(); + return count != -1; + } + + /** + * Adds a ChatRoom listener to ChatRooms. The + * listener will be called when either a ChatRoom has been + * added, removed, or activated. + * + * @param listener the ChatRoomListener to register + */ + public void addChatRoomListener(ChatRoomListener listener) { + if (!chatRoomListeners.contains(listener)) { + chatRoomListeners.add(listener); + } + } + + /** + * Removes the specified ChatRoomListener. + * + * @param listener the ChatRoomListener to remove + */ + public void removeChatRoomListener(ChatRoomListener listener) { + chatRoomListeners.remove(listener); + } + + /** + * Notifies users that a ChatRoom has been opened. + * + * @param room - the ChatRoom that has been opened. + */ + protected void fireChatRoomOpened(ChatRoom room) { + final Iterator iter = new ArrayList(chatRoomListeners).iterator(); + while (iter.hasNext()) { + ((ChatRoomListener)iter.next()).chatRoomOpened(room); + } + } + + /** + * Notifies users that a ChatRoom has been left. + * + * @param room - the ChatRoom that has been left + */ + protected void fireChatRoomLeft(ChatRoom room) { + final Iterator iter = new HashSet(chatRoomListeners).iterator(); + while (iter.hasNext()) { + final Object chatRoomListener = iter.next(); + ((ChatRoomListener)chatRoomListener).chatRoomLeft(room); + } + } + + /** + * Notifies users that a ChatRoom has been closed. + * + * @param room - the ChatRoom that has been closed. + */ + protected void fireChatRoomClosed(ChatRoom room) { + final Iterator iter = new HashSet(chatRoomListeners).iterator(); + while (iter.hasNext()) { + final Object chatRoomListener = iter.next(); + ((ChatRoomListener)chatRoomListener).chatRoomClosed(room); + } + } + + /** + * Notifies users that a ChatRoom has been activated. + * + * @param room - the ChatRoom that has been activated. + */ + protected void fireChatRoomActivated(ChatRoom room) { + final Iterator iter = new HashSet(chatRoomListeners).iterator(); + while (iter.hasNext()) { + ((ChatRoomListener)iter.next()).chatRoomActivated(room); + } + } + + /** + * Notifies users that a user has joined a ChatRoom. + * + * @param room - the ChatRoom that a user has joined. + * @param userid - the userid of the person. + */ + protected void fireUserHasJoined(final ChatRoom room, final String userid) { + SwingUtilities.invokeLater(new Runnable() { + public void run() { + final Iterator iter = new HashSet(chatRoomListeners).iterator(); + while (iter.hasNext()) { + ((ChatRoomListener)iter.next()).userHasJoined(room, userid); + } + } + }); + + } + + /** + * Notifies users that a user has left a ChatRoom. + * + * @param room - the ChatRoom that a user has left. + * @param userid - the userid of the person. + */ + protected void fireUserHasLeft(final ChatRoom room, final String userid) { + SwingUtilities.invokeLater(new Runnable() { + public void run() { + final Iterator iter = new HashSet(chatRoomListeners).iterator(); + while (iter.hasNext()) { + ((ChatRoomListener)iter.next()).userHasLeft(room, userid); + } + } + }); + + } + + /** + * Starts flashing of MainWindow. + * + * @param room the ChatRoom to check if a message has been inserted + * but the room is not the selected room. + */ + public void startFlashing(final ChatRoom room) { + SwingUtilities.invokeLater(new Runnable() { + public void run() { + try { + final int index = indexOfComponent(room); + if (index != -1) { + room.increaseUnreadMessageCount(); + int unreadMessageCount = room.getUnreadMessageCount(); + String appendedMessage = ""; + if (unreadMessageCount > 1) { + appendedMessage = " (" + unreadMessageCount + ")"; + } + + SparkTab tab = getTabAt(index); + tab.getTitleLabel().setText(room.getTabTitle() + appendedMessage); + + makeTabRed(room); + } + + boolean invokeFlash = !SettingsManager.getLocalPreferences().isChatRoomNotificationsOff() || !(room instanceof GroupChatRoom); + + if (!chatFrame.isFocused() && invokeFlash) { + SparkManager.getAlertManager().flashWindow(chatFrame); + } + } + catch (Exception ex) { + Log.error("Issue in ChatRooms with tab location.", ex); + } + } + }); + } + + public void flashWindow(final ChatRoom room) { + SwingUtilities.invokeLater(new Runnable() { + public void run() { + try { + boolean invokeFlash = !SettingsManager.getLocalPreferences().isChatRoomNotificationsOff() || !(room instanceof GroupChatRoom); + + if (!chatFrame.isFocused() && invokeFlash) { + SparkManager.getAlertManager().flashWindow(chatFrame); + } + } + catch (Exception ex) { + Log.error("Issue in ChatRooms with tab location.", ex); + } + } + }); + } + + public void makeTabRed(final ChatRoom room) { + final int index = indexOfComponent(room); + if (index != -1) { + SparkTab tab = getTabAt(index); + Font font = tab.getTitleLabel().getFont(); + tab.getTitleLabel().setForeground(Color.red); + Font newFont = font.deriveFont(Font.BOLD); + tab.getTitleLabel().setFont(newFont); + } + } + + public void useTabDefault(final ChatRoom room) { + final int index = indexOfComponent(room); + if (index != -1) { + SparkTab tab = getTabAt(index); + Font font = tab.getTitleLabel().getFont(); + tab.getTitleLabel().setForeground(Color.black); + + Font newFont = font.deriveFont(Font.PLAIN); + tab.getTitleLabel().setFont(newFont); + } + } + + /** + * Checks to see if the MainWindow should stop flashing. + * + * @param room the ChatRoom to check. + */ + public void stopFlashing(final ChatRoom room) { + SwingUtilities.invokeLater(new Runnable() { + public void run() { + try { + int index = indexOfComponent(room); + if (index != -1) { + SparkTab tab = getTabAt(index); + useTabDefault(room); + tab.getTitleLabel().setText(room.getTabTitle()); + room.clearUnreadMessageCount(); + } + } + catch (Exception ex) { + Log.error("Could not stop flashing for " + room + " because " + ex.getMessage(), ex); + } + + SparkManager.getAlertManager().stopFlashing(chatFrame); + } + }); + } + + public void setChatRoomTitle(ChatRoom room, String title) { + int index = indexOfComponent(room); + if (index != -1) { + SparkTab tab = getTabAt(index); + useTabDefault(room); + tab.getTitleLabel().setText(room.getTabTitle()); + } + } + + private void createFrameIfNeeded() { + if (chatFrame != null) { + return; + } + chatFrame = new ChatFrame(); + + + chatFrame.addWindowListener(new WindowAdapter() { + public void windowActivated(WindowEvent windowEvent) { + stopFlashing(); + int sel = getSelectedIndex(); + if (sel == -1) { + return; + } + final ChatRoom room; + try { + room = getChatRoom(sel); + focusChat(); + + // Set the title of the room. + chatFrame.setTitle(room.getRoomTitle()); + } + catch (ChatRoomNotFoundException e1) { + } + + } + + public void windowDeactivated(WindowEvent windowEvent) { + } + + }); + } + + + public void focusChat() { + SwingWorker worker = new SwingWorker() { + public Object construct() { + try { + Thread.sleep(50); + } + catch (InterruptedException e1) { + Log.error(e1); + } + return "ok"; + } + + public void finished() { + try { + //chatFrame.requestFocus(); + ChatRoom chatRoom = getActiveChatRoom(); + chatRoom.requestFocusInWindow(); + chatRoom.getChatInputEditor().requestFocusInWindow(); + } + catch (ChatRoomNotFoundException e1) { + // Ignore. There may legitamtly not be a chat room. + } + } + }; + worker.start(); + + } + + public Collection getChatRooms() { + return chatRoomList; + } + + public ChatFrame getChatFrame() { + return chatFrame; + } + + public void blinkFrameIfNecessary(final JFrame frame) { + + final MainWindow mainWindow = SparkManager.getMainWindow(); + + if (mainWindow.isFocused()) { + frame.setVisible(true); + return; + } + else { + // Set to new tab. + if (Spark.isWindows()) { + frame.setState(Frame.ICONIFIED); + + SparkManager.getAlertManager().flashWindow(frame); + + frame.setVisible(true); + frame.addWindowListener(new WindowAdapter() { + public void windowActivated(WindowEvent e) { + SparkManager.getAlertManager().stopFlashing(frame); + } + }); + } + } + } + + +} diff --git a/src/java/org/jivesoftware/spark/ui/ChatFrame.java b/src/java/org/jivesoftware/spark/ui/ChatFrame.java new file mode 100644 index 00000000..bc8a724b --- /dev/null +++ b/src/java/org/jivesoftware/spark/ui/ChatFrame.java @@ -0,0 +1,158 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.ui; + +import org.jivesoftware.MainWindow; +import org.jivesoftware.Spark; +import org.jivesoftware.resource.SparkRes; +import org.jivesoftware.spark.SparkManager; +import org.jivesoftware.spark.util.GraphicUtils; +import org.jivesoftware.sparkimpl.plugin.layout.LayoutSettings; +import org.jivesoftware.sparkimpl.plugin.layout.LayoutSettingsManager; + +import javax.swing.JFrame; + +import java.awt.BorderLayout; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.awt.event.WindowFocusListener; + +/** + * The Window used to display the ChatRoom container. + */ +public class ChatFrame extends JFrame implements WindowFocusListener { + + private long inactiveTime; + + private boolean focused; + + /** + * Creates default ChatFrame. + */ + public ChatFrame() { + setIconImage(SparkRes.getImageIcon(SparkRes.MAIN_IMAGE).getImage()); + + getContentPane().setLayout(new BorderLayout()); + getContentPane().add(SparkManager.getChatManager().getChatContainer(), BorderLayout.CENTER); + pack(); + + LayoutSettings settings = LayoutSettingsManager.getLayoutSettings(); + if (settings.getChatFrameX() == 0 && settings.getChatFrameY() == 0) { + // Use default settings. + setSize(500, 400); + GraphicUtils.centerWindowOnScreen(this); + } + else { + setBounds(settings.getChatFrameX(), settings.getChatFrameY(), settings.getChatFrameWidth(), settings.getChatFrameHeight()); + } + + addWindowFocusListener(this); + + // Setup WindowListener to be the proxy to the actual window listener + // which cannot normally be used outside of the Window component because + // of protected access. + addWindowListener(new WindowAdapter() { + + /** + * This event fires when the window has become active. + * + * @param e WindowEvent is not used. + */ + public void windowActivated(WindowEvent e) { + inactiveTime = 0; + if (Spark.isMac()) { + setJMenuBar(MainWindow.getInstance().getMenu()); + } + } + + /** + * Invoked when a window is de-activated. + */ + public void windowDeactivated(WindowEvent e) { + inactiveTime = System.currentTimeMillis(); + } + + /** + * This event fires whenever a user minimizes the window + * from the toolbar. + * + * @param e WindowEvent is not used. + */ + public void windowIconified(WindowEvent e) { + } + + public void windowDeiconified(WindowEvent e) { + setFocusableWindowState(true); + } + + + /** + * This event fires when the application is closing. + * This allows Plugins to do any persistence or other + * work before exiting. + * + * @param e WindowEvent is never used. + */ + public void windowClosing(WindowEvent e) { + // Save layout + saveLayout(); + + SparkManager.getChatManager().getChatContainer().closeAllChatRooms(); + } + }); + } + + public void windowGainedFocus(WindowEvent e) { + focused = true; + + SparkManager.getChatManager().getChatContainer().focusChat(); + } + + public void windowLostFocus(WindowEvent e) { + focused = false; + } + + /** + * Returns true if the frame is in focus, otherwise returns false. + * + * @return true if the frame is in focus, otherwise returns false. + */ + public boolean isInFocus() { + return focused; + } + + /** + * Returns time the ChatFrame has not been in focus. + * + * @return the time in milliseconds. + */ + public long getInactiveTime() { + if (inactiveTime == 0) { + return 0; + } + + return System.currentTimeMillis() - inactiveTime; + } + + /** + * Saves the layout on closing of the chat frame. + */ + private void saveLayout() { + LayoutSettings settings = LayoutSettingsManager.getLayoutSettings(); + settings.setChatFrameHeight(getHeight()); + settings.setChatFrameWidth(getWidth()); + settings.setChatFrameX(getX()); + settings.setChatFrameY(getY()); + LayoutSettingsManager.saveLayoutSettings(); + } + + +} diff --git a/src/java/org/jivesoftware/spark/ui/ChatInputEditor.java b/src/java/org/jivesoftware/spark/ui/ChatInputEditor.java new file mode 100644 index 00000000..aabc0b71 --- /dev/null +++ b/src/java/org/jivesoftware/spark/ui/ChatInputEditor.java @@ -0,0 +1,94 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.ui; + +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.JComponent; +import javax.swing.KeyStroke; +import javax.swing.UIManager; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import javax.swing.undo.UndoManager; + +import java.awt.Color; +import java.awt.event.ActionEvent; +import java.awt.event.KeyEvent; + +/** + * This is implementation of ChatArea that should be used as the sendField + * in any chat room implementation. + */ +public class ChatInputEditor extends ChatArea implements DocumentListener { + + private final UndoManager undoManager = new UndoManager(); + + /** + * Creates a new Default ChatSendField. + */ + public ChatInputEditor() { + this.setDragEnabled(true); + this.getDocument().addUndoableEditListener(undoManager); + Action undo = new AbstractAction() { + public void actionPerformed(ActionEvent e) { + undoManager.undo(); + } + }; + + this.getInputMap().put(KeyStroke.getKeyStroke('z', ActionEvent.CTRL_MASK), "undo"); + this.registerKeyboardAction(undo, KeyStroke.getKeyStroke(KeyEvent.VK_Z, KeyEvent.CTRL_MASK), JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); + + this.getDocument().addDocumentListener(this); + + addMouseListener(this); + } + + public void insertUpdate(DocumentEvent e) { + // this.setCaretPosition(e.getOffset()); + this.requestFocusInWindow(); + } + + public void setText(String str) { + // Do nothing. + } + + public void removeUpdate(DocumentEvent e) { + } + + public void changedUpdate(DocumentEvent e) { + } + + /** + * Disables the Chat Editor, rendering it to the system default + * color. + */ + public void showAsDisabled() { + this.setEditable(false); + this.setEnabled(false); + + clear(); + + final Color disabledColor = (Color)UIManager.get("Button.disabled"); + + this.setBackground(disabledColor); + } + + /** + * Enable the Chat Editor. + */ + public void showEnabled() { + this.setEditable(true); + this.setEnabled(true); + + this.setBackground(Color.white); + } + +} \ No newline at end of file diff --git a/src/java/org/jivesoftware/spark/ui/ChatPrinter.java b/src/java/org/jivesoftware/spark/ui/ChatPrinter.java new file mode 100644 index 00000000..1a53b636 --- /dev/null +++ b/src/java/org/jivesoftware/spark/ui/ChatPrinter.java @@ -0,0 +1,369 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.ui; + +import javax.swing.JEditorPane; +import javax.swing.text.Document; +import javax.swing.text.PlainDocument; +import javax.swing.text.View; +import javax.swing.text.html.HTMLDocument; + +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Rectangle; +import java.awt.Shape; +import java.awt.print.PageFormat; +import java.awt.print.Printable; +import java.awt.print.PrinterException; +import java.awt.print.PrinterJob; + +/** + * Used to print any item contained with a TextArea, such as a Chat. + */ +public class ChatPrinter implements Printable { +/* DocumentRenderer prints objects of type Document. Text attributes, including + fonts, color, and small icons, will be rendered to a printed page. + DocumentRenderer computes line breaks, paginates, and performs other + formatting. + + An HTMLDocument is printed by sending it as an argument to the + print(HTMLDocument) method. A PlainDocument is printed the same way. Other + types of documents must be sent in a JEditorPane as an argument to the + print(JEditorPane) method. Printing Documents in this way will automatically + display a print dialog. + + As objects which implement the Printable Interface, instances of the + DocumentRenderer class can also be used as the argument in the setPrintable + method of the PrinterJob class. Instead of using the print() methods + detailed above, a programmer may gain access to the formatting capabilities + of this class without using its print dialog by creating an instance of + DocumentRenderer and setting the document to be printed with the + setDocument() or setJEditorPane(). The Document may then be printed by + setting the instance of DocumentRenderer in any PrinterJob. +*/ + private int currentPage = -1; //Used to keep track of when + //the page to print changes. + + private JEditorPane JEditorPane; //Container to hold the + //Document. This object will + //be used to lay out the + //Document for printing. + + private double pageEndY; //Location of the current page + //end. + + private double pageStartY; //Location of the current page + //start. + + private boolean scaleWidthToFit = true; //boolean to allow control over + //whether pages too wide to fit + //on a page will be scaled. + +/* The DocumentRenderer class uses pFormat and pJob in its methods. Note + that pFormat is not the variable name used by the print method of the + DocumentRenderer. Although it would always be expected to reference the + pFormat object, the print method gets its PageFormat as an argument. +*/ + private PageFormat pFormat; + private PrinterJob pJob; + + /** + * The constructor initializes the pFormat and PJob variables. + */ + public ChatPrinter() { + pFormat = new PageFormat(); + pJob = PrinterJob.getPrinterJob(); + } + + /** + * Method to get the current Document. + * + * @return the Chat document object. + */ + public Document getDocument() { + if (JEditorPane != null) + return JEditorPane.getDocument(); + else + return null; + } + + /** + * Method to get the current choice the width scaling option. + * + * @return true if it should scale the width. + */ + public boolean getScaleWidthToFit() { + return scaleWidthToFit; + } + + /** + * pageDialog() displays a page setup dialog. + */ + public void pageDialog() { + pFormat = pJob.pageDialog(pFormat); + } + + /** + * may be called to render a page more than once, each page is painted in + * order. We may, therefore, keep track of changes in the page being rendered + * by setting the currentPage variable to equal the pageIndex, and then + * comparing these variables on subsequent calls to this method. When the two + * variables match, it means that the page is being rendered for the second or + * third time. When the currentPage differs from the pageIndex, a new page is + * being requested. + *

        + * The highlights of the process used print a page are as follows: + *

        + * I. The Graphics object is cast to a Graphics2D object to allow for + * scaling. + * II. The JEditorPane is laid out using the width of a printable page. + * This will handle line breaks. If the JEditorPane cannot be sized at + * the width of the graphics clip, scaling will be allowed. + * III. The root view of the JEditorPane is obtained. By examining this root + * view and all of its children, printView will be able to determine + * the location of each printable element of the document. + * IV. If the scaleWidthToFit option is chosen, a scaling ratio is + * determined, and the graphics2D object is scaled. + * V. The Graphics2D object is clipped to the size of the printable page. + * VI. currentPage is checked to see if this is a new page to render. If so, + * pageStartY and pageEndY are reset. + * VII. To match the coordinates of the printable clip of graphics2D and the + * allocation rectangle which will be used to lay out the views, + * graphics2D is translated to begin at the printable X and Y + * coordinates of the graphics clip. + * VIII. An allocation Rectangle is created to represent the layout of the + * Views. + *

        + * The Printable Interface always prints the area indexed by reference + * to the Graphics object. For instance, with a standard 8.5 x 11 inch + * page with 1 inch margins the rectangle X = 72, Y = 72, Width = 468, + * and Height = 648, the area 72, 72, 468, 648 will be painted regardless + * of which page is actually being printed. + *

        + * To align the allocation Rectangle with the graphics2D object two + * things are done. The first step is to translate the X and Y + * coordinates of the graphics2D object to begin at the X and Y + * coordinates of the printable clip, see step VII. Next, when printing + * other than the first page, the allocation rectangle must start laying + * out in coordinates represented by negative numbers. After page one, + * the beginning of the allocation is started at minus the page end of + * the prior page. This moves the part which has already been rendered to + * before the printable clip of the graphics2D object. + *

        + * X. The printView method is called to paint the page. Its return value + * will indicate if a page has been rendered. + *

        + * Although public, print should not ordinarily be called by programs other + * than PrinterJob. + * + * @param graphics the Graphic Object used to print. + * @param pageFormat the page formatter. + * @param pageIndex the page to print. + * @return the page number printed. + */ + public int print(Graphics graphics, PageFormat pageFormat, int pageIndex) { + double scale = 1.0; + Graphics2D graphics2D; + View rootView; +// I + graphics2D = (Graphics2D)graphics; +// II + JEditorPane.setSize((int)pageFormat.getImageableWidth(), Integer.MAX_VALUE); + JEditorPane.validate(); +// III + rootView = JEditorPane.getUI().getRootView(JEditorPane); +// IV + if ((scaleWidthToFit) && (JEditorPane.getMinimumSize().getWidth() > + pageFormat.getImageableWidth())) { + scale = pageFormat.getImageableWidth() / + JEditorPane.getMinimumSize().getWidth(); + graphics2D.scale(scale, scale); + } +// V + graphics2D.setClip((int)(pageFormat.getImageableX() / scale), + (int)(pageFormat.getImageableY() / scale), + (int)(pageFormat.getImageableWidth() / scale), + (int)(pageFormat.getImageableHeight() / scale)); +// VI + if (pageIndex > currentPage) { + currentPage = pageIndex; + pageStartY += pageEndY; + pageEndY = graphics2D.getClipBounds().getHeight(); + } +// VII + graphics2D.translate(graphics2D.getClipBounds().getX(), + graphics2D.getClipBounds().getY()); +// VIII + Rectangle allocation = new Rectangle(0, + (int)-pageStartY, + (int)(JEditorPane.getMinimumSize().getWidth()), + (int)(JEditorPane.getPreferredSize().getHeight())); +// X + if (printView(graphics2D, allocation, rootView)) { + return Printable.PAGE_EXISTS; + } + else { + pageStartY = 0; + pageEndY = 0; + currentPage = -1; + return Printable.NO_SUCH_PAGE; + } + } + + /** + * print(HTMLDocument) is called to set an HTMLDocument for printing. + * + * @param htmlDocument the HtmlDocument to print. + */ + public void print(HTMLDocument htmlDocument) { + setDocument(htmlDocument); + printDialog(); + } + + /** + * print(JEditorPane) prints a Document contained within a JEditorPane. + * + * @param jedPane the JEditorPane to print. + */ + public void print(JEditorPane jedPane) { + setDocument(jedPane); + printDialog(); + } + + /** + * print(PlainDocument) is called to set a PlainDocument for printing. + * + * @param plainDocument the PlainDocument to print. + */ + public void print(PlainDocument plainDocument) { + setDocument(plainDocument); + printDialog(); + } + + /** + * A private method, printDialog(), displays the print dialog and initiates + * printing in response to user input. + */ + private void printDialog() { + if (pJob.printDialog()) { + pJob.setPrintable(this, pFormat); + try { + pJob.print(); + } + catch (PrinterException printerException) { + pageStartY = 0; + pageEndY = 0; + currentPage = -1; + System.out.println("Error Printing Document"); + } + } + } + + + private boolean printView(Graphics2D graphics2D, Shape allocation, + View view) { + boolean pageExists = false; + Rectangle clipRectangle = graphics2D.getClipBounds(); + Shape childAllocation; + View childView; + + if (view.getViewCount() > 0) { + for (int i = 0; i < view.getViewCount(); i++) { + childAllocation = view.getChildAllocation(i, allocation); + if (childAllocation != null) { + childView = view.getView(i); + if (printView(graphics2D, childAllocation, childView)) { + pageExists = true; + } + } + } + } + else { +// I + if (allocation.getBounds().getMaxY() >= clipRectangle.getY()) { + pageExists = true; +// II + if ((allocation.getBounds().getHeight() > clipRectangle.getHeight()) && + (allocation.intersects(clipRectangle))) { + view.paint(graphics2D, allocation); + } + else { +// III + if (allocation.getBounds().getY() >= clipRectangle.getY()) { + if (allocation.getBounds().getMaxY() <= clipRectangle.getMaxY()) { + view.paint(graphics2D, allocation); + } + else { +// IV + if (allocation.getBounds().getY() < pageEndY) { + pageEndY = allocation.getBounds().getY(); + } + } + } + } + } + } + return pageExists; + } + + + private void setContentType(String type) { + JEditorPane.setContentType(type); + } + + /** + * Method to set an HTMLDocument as the Document to print. + * + * @param htmlDocument sets the html document. + */ + public void setDocument(HTMLDocument htmlDocument) { + JEditorPane = new JEditorPane(); + setDocument("text/html", htmlDocument); + } + + /** + * Method to set the Document to print as the one contained in a JEditorPane. + * This method is useful when Java does not provide direct access to a + * particular Document type, such as a Rich Text Format document. With this + * method such a document can be sent to the DocumentRenderer class enclosed + * in a JEditorPane. + * + * @param jedPane the JEditorPane document container. + */ + public void setDocument(JEditorPane jedPane) { + JEditorPane = new JEditorPane(); + setDocument(jedPane.getContentType(), jedPane.getDocument()); + } + + /** + * Method to set a PlainDocument as the Document to print. + * + * @param plainDocument the PlainDocument to use. + */ + public void setDocument(PlainDocument plainDocument) { + JEditorPane = new JEditorPane(); + setDocument("text/plain", plainDocument); + } + + private void setDocument(String type, Document document) { + setContentType(type); + JEditorPane.setDocument(document); + } + + /** + * Method to set the current choice of the width scaling option. + * + * @param scaleWidth the width to scale to. + */ + public void setScaleWidthToFit(boolean scaleWidth) { + scaleWidthToFit = scaleWidth; + } +} diff --git a/src/java/org/jivesoftware/spark/ui/ChatRoom.java b/src/java/org/jivesoftware/spark/ui/ChatRoom.java new file mode 100644 index 00000000..3864af41 --- /dev/null +++ b/src/java/org/jivesoftware/spark/ui/ChatRoom.java @@ -0,0 +1,870 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.ui; + +import org.jivesoftware.resource.SparkRes; +import org.jivesoftware.smack.PacketListener; +import org.jivesoftware.smack.packet.Message; +import org.jivesoftware.smack.packet.Packet; +import org.jivesoftware.smackx.debugger.EnhancedDebuggerWindow; +import org.jivesoftware.spark.ChatAreaSendField; +import org.jivesoftware.spark.SparkManager; +import org.jivesoftware.spark.component.BackgroundPanel; +import org.jivesoftware.spark.component.RolloverButton; +import org.jivesoftware.spark.plugin.ContextMenuListener; +import org.jivesoftware.spark.util.GraphicUtils; +import org.jivesoftware.spark.util.log.Log; +import org.jivesoftware.sparkimpl.preference.chat.ChatPreference; +import org.jivesoftware.sparkimpl.preference.chat.ChatPreferences; + +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.Icon; +import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JPopupMenu; +import javax.swing.JScrollPane; +import javax.swing.JSeparator; +import javax.swing.JSplitPane; +import javax.swing.KeyStroke; +import javax.swing.UIManager; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import javax.swing.text.BadLocationException; +import javax.swing.text.Document; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Component; +import java.awt.FlowLayout; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.Iterator; +import java.util.List; + +/** + * The base implementation of all ChatRoom conversations. You would implement this class to have most types of Chat. + */ +public abstract class ChatRoom extends BackgroundPanel implements ActionListener, PacketListener, DocumentListener { + private final JPanel chatPanel; + private final JSplitPane splitPane; + private final JLabel notificationLabel; + private final TranscriptWindow transcriptWindow; + private final ChatAreaSendField chatAreaButton; + private final ChatToolBar toolbar; + private final JScrollPane textScroller; + private final JPanel bottomPanel; + private final JPanel editorBar; + private JPanel chatWindowPanel; + + private final List packetIDList; + private final List messageListeners; + private List transcript; + private List fileDropListeners; + + private int unreadMessageCount; + + private boolean mousePressed; + + private ChatPreferences chatPreferences; + private List closingListeners = new ArrayList(); + + + final JSplitPane verticalSplit = new JSplitPane(JSplitPane.VERTICAL_SPLIT); + + + /** + * Initializes the base layout and base background color. + */ + protected ChatRoom() { + chatPanel = new JPanel(new GridBagLayout()); + transcriptWindow = new TranscriptWindow(); + splitPane = new JSplitPane(); + packetIDList = new ArrayList(); + notificationLabel = new JLabel(); + toolbar = new ChatToolBar(); + bottomPanel = new JPanel(); + + messageListeners = new ArrayList(); + transcript = new ArrayList(); + editorBar = new JPanel(new FlowLayout(FlowLayout.LEFT, 1, 1)); + fileDropListeners = new ArrayList(); + + transcriptWindow.addMouseListener(new MouseAdapter() { + public void mouseClicked(MouseEvent e) { + getChatInputEditor().requestFocus(); + } + }); + + textScroller = new JScrollPane(transcriptWindow); + + textScroller.getVerticalScrollBar().addMouseListener(new MouseAdapter() { + public void mousePressed(MouseEvent e) { + mousePressed = true; + } + + public void mouseReleased(MouseEvent e) { + mousePressed = false; + } + }); + + chatAreaButton = new ChatAreaSendField(SparkRes.getString(SparkRes.SEND)); + + getChatInputEditor().setSelectedTextColor((Color)UIManager.get("ChatInput.SelectedTextColor")); + getChatInputEditor().setSelectionColor((Color)UIManager.get("ChatInput.SelectionColor")); + + + init(); + + // Initally, set the right pane to null to keep it empty. + getSplitPane().setRightComponent(null); + + notificationLabel.setIcon(SparkRes.getImageIcon(SparkRes.BLANK_IMAGE)); + + + getTranscriptWindow().addContextMenuListener(new ContextMenuListener() { + public void poppingUp(Object component, JPopupMenu popup) { + Action saveAction = new AbstractAction() { + public void actionPerformed(ActionEvent actionEvent) { + saveTranscript(); + } + }; + saveAction.putValue(Action.NAME, "Save"); + saveAction.putValue(Action.SMALL_ICON, SparkRes.getImageIcon(SparkRes.SAVE_AS_16x16)); + + + popup.add(saveAction); + } + + public void poppingDown(JPopupMenu popup) { + + } + + public boolean handleDefaultAction(MouseEvent e) { + return false; + } + }); + + this.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke("F12"), "showDebugger"); + this.getActionMap().put("showDebugger", new AbstractAction("showDebugger") { + public void actionPerformed(ActionEvent evt) { + EnhancedDebuggerWindow window = EnhancedDebuggerWindow.getInstance(); + window.setVisible(true); + } + }); + + getTranscriptWindow().setTransferHandler(new ChatRoomTransferHandler(this)); + getChatInputEditor().setTransferHandler(new ChatRoomTransferHandler(this)); + + + add(toolbar, new GridBagConstraints(0, 0, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0)); + } + + // Setup base layout. + private void init() { + setLayout(new GridBagLayout()); + + + add(splitPane, new GridBagConstraints(0, 1, 1, 1, 1.0, 1.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0)); + add(notificationLabel, new GridBagConstraints(0, 2, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 2, 5, 0), 0, 0)); + + // Remove Default Beveled Borders + splitPane.setBorder(null); + verticalSplit.setBorder(null); + splitPane.setLeftComponent(verticalSplit); + + // Load Preferences for this instance + chatPreferences = (ChatPreferences)SparkManager.getPreferenceManager().getPreferenceData(ChatPreference.NAMESPACE); + + textScroller.setAutoscrolls(true); + + // Speed up scrolling. It was way too slow. + textScroller.getVerticalScrollBar().setBlockIncrement(50); + textScroller.getVerticalScrollBar().setUnitIncrement(20); + + chatWindowPanel = new JPanel(); + chatWindowPanel.setLayout(new GridBagLayout()); + chatWindowPanel.add(textScroller, new GridBagConstraints(0, 10, 1, 1, 1.0, 1.0, GridBagConstraints.WEST, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0)); + chatWindowPanel.setOpaque(false); + + // Layout Components + chatPanel.add(chatWindowPanel, new GridBagConstraints(0, 1, 1, 1, 1.0, 1.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0)); + + // Add edit buttons to Chat Room + editorBar.setOpaque(false); + chatPanel.setOpaque(false); + + + editorBar.add(new JSeparator(JSeparator.VERTICAL)); + + bottomPanel.setOpaque(false); + splitPane.setOpaque(false); + bottomPanel.setLayout(new GridBagLayout()); + bottomPanel.add(chatAreaButton, new GridBagConstraints(0, 1, 5, 1, 1.0, 1.0, GridBagConstraints.WEST, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 30)); + bottomPanel.add(editorBar, new GridBagConstraints(0, 0, 5, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0)); + verticalSplit.setOpaque(false); + + verticalSplit.setTopComponent(chatPanel); + verticalSplit.setBottomComponent(bottomPanel); + verticalSplit.setResizeWeight(1.0); + verticalSplit.setDividerSize(2); + + // Add listener to send button + chatAreaButton.getButton().addActionListener(this); + + // Add Key Listener to Send Field + getChatInputEditor().getDocument().addDocumentListener(this); + + // Add Key Listener to Send Field + getChatInputEditor().addKeyListener(new KeyAdapter() { + public void keyPressed(KeyEvent e) { + checkForEnter(e); + } + }); + + getChatInputEditor().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("ctrl F4"), "closeTheRoom"); + getChatInputEditor().getActionMap().put("closeTheRoom", new AbstractAction("closeTheRoom") { + public void actionPerformed(ActionEvent evt) { + final int ok = JOptionPane.showConfirmDialog(SparkManager.getMainWindow(), "Would you like to close this chat?", + "Confirmation", JOptionPane.YES_NO_OPTION); + if (ok == JOptionPane.OK_OPTION) { + // Leave this chat. + closeChatRoom(); + } + } + }); + } + + + // I would normally use the command pattern, but + // have no real use when dealing with just a couple options. + public void actionPerformed(ActionEvent e) { + sendMessage(); + + // Clear send field and disable send button + getChatInputEditor().clear(); + chatAreaButton.getButton().setEnabled(false); + } + + /** + * Creates and sends a message object from the text in + * the Send Field, using the default nickname specified in your + * Chat Preferences. + */ + protected abstract void sendMessage(); + + /** + * Creates a Message object from the given text and delegates to the room + * for sending. + * + * @param text the text to send. + */ + protected abstract void sendMessage(String text); + + /** + * Sends the current message. + * + * @param message - the message to send. + */ + public abstract void sendMessage(Message message); + + /** + * Returns the nickname of the current agent as specified in Chat + * Preferences. + * + * @return the nickname of the agent. + */ + public String getNickname() { + return chatPreferences.getNickname(); + } + + + /** + * The main entry point when receiving any messages. This will + * either handle a message from a customer or delegate itself + * as an agent handler. + * + * @param message - the message receieved. + */ + public void insertMessage(Message message) { + // Fire Message Filters + SparkManager.getChatManager().filterIncomingMessage(message); + + addToTranscript(message, true); + + fireMessageReceived(message); + } + + + /** + * Add a ChatResponse to the current discussion chat area. + * + * @param message the message to add to the transcript list + * @param updateDate true if you wish the date label to be updated with the + * date and time the message was received. + */ + public void addToTranscript(Message message, boolean updateDate) { + // Create message to persist. + final Message newMessage = new Message(); + newMessage.setTo(message.getTo()); + newMessage.setFrom(message.getFrom()); + newMessage.setBody(message.getBody()); + newMessage.setProperty("date", new Date()); + + transcript.add(newMessage); + + // Add current date if this is the current agent + if (updateDate && transcriptWindow.getLastUpdated() != null) { + // Set new label date + notificationLabel.setIcon(SparkRes.getImageIcon(SparkRes.SMALL_ABOUT_IMAGE)); + notificationLabel.setText("Last message received on " + SparkManager.DATE_SECOND_FORMATTER.format(transcriptWindow.getLastUpdated())); + } + + scrollToBottom(); + } + + /** + * Scrolls the chat window to the bottom. + */ + public void scrollToBottom() { + if (mousePressed) { + return; + } + + int chatLength = transcriptWindow.getDocument().getLength(); + transcriptWindow.setCaretPosition(chatLength); + } + + + /** + * Checks to see if the Send button should be enabled. + * + * @param e - the documentevent to react to. + */ + protected void checkForText(DocumentEvent e) { + final int length = e.getDocument().getLength(); + if (length > 0) { + chatAreaButton.getButton().setEnabled(true); + } + else { + chatAreaButton.getButton().setEnabled(false); + } + } + + /** + * Requests valid focus to the SendField. + */ + public void positionCursor() { + getChatInputEditor().setCaretPosition(getChatInputEditor().getCaretPosition()); + chatAreaButton.getChatInputArea().requestFocusInWindow(); + } + + + /** + * Disable the chat room. This is called when a chat has been either transfered over or + * the customer has left the chat room. + */ + public abstract void leaveChatRoom(); + + + /** + * Process incoming packets. + * + * @param packet - the packet to process + */ + public void processPacket(Packet packet) { + } + + + /** + * Returns the SendField component. + * + * @return the SendField ChatSendField. + */ + public ChatInputEditor getChatInputEditor() { + return chatAreaButton.getChatInputArea(); + } + + /** + * Returns the chatWindow components. + * + * @return the ChatWindow component. + */ + public TranscriptWindow getTranscriptWindow() { + return transcriptWindow; + } + + + // Check to see if an enter key was pressed. + private void checkForEnter(KeyEvent e) { + final KeyStroke keyStroke = KeyStroke.getKeyStroke(e.getKeyCode(), e.getModifiers()); + if (!keyStroke.equals(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, KeyEvent.SHIFT_DOWN_MASK)) && + e.getKeyChar() == KeyEvent.VK_ENTER) { + e.consume(); + sendMessage(); + getChatInputEditor().setText(""); + getChatInputEditor().setCaretPosition(0); + } + else if (keyStroke.equals(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, KeyEvent.SHIFT_DOWN_MASK))) { + final Document document = getChatInputEditor().getDocument(); + try { + document.insertString(getChatInputEditor().getCaretPosition(), "\n", null); + getChatInputEditor().requestFocusInWindow(); + chatAreaButton.getButton().setEnabled(true); + } + catch (BadLocationException badLoc) { + Log.error("Error when checking for enter:", badLoc); + } + + } + } + + /** + * Add a {@link MessageListener} to the current ChatRoom. + * + * @param listener - the MessageListener to add to the current ChatRoom. + */ + public void addMessageListener(MessageListener listener) { + messageListeners.add(listener); + } + + /** + * Remove the specified {@link MessageListener } from the current ChatRoom. + * + * @param listener - the MessageListener to remove from the current ChatRoom. + */ + public void removeMessageListener(MessageListener listener) { + messageListeners.remove(listener); + } + + private void fireMessageReceived(Message message) { + final Iterator iter = messageListeners.iterator(); + while (iter.hasNext()) { + ((MessageListener)iter.next()).messageReceived(this, message); + } + } + + protected void fireMessageSent(Message message) { + final Iterator iter = messageListeners.iterator(); + while (iter.hasNext()) { + ((MessageListener)iter.next()).messageSent(this, message); + } + } + + /** + * Returns a map of the current Chat Transcript which is a list of all + * ChatResponses and their order. You should retrieve this map to get + * any current chat transcript state. + * + * @return - the map of current chat responses. + */ + public List getTranscripts() { + return transcript; + } + + /** + * Disables the ChatRoom toolbar. + */ + public void disableToolbar() { + final int count = editorBar.getComponentCount(); + for (int i = 0; i < count; i++) { + final Object o = editorBar.getComponent(i); + if (o instanceof RolloverButton) { + final RolloverButton rb = (RolloverButton)o; + rb.setEnabled(false); + } + } + } + + /** + * Enable the ChatRoom toolbar. + */ + public void enableToolbar() { + final int count = editorBar.getComponentCount(); + for (int i = 0; i < count; i++) { + final Object o = editorBar.getComponent(i); + if (o instanceof RolloverButton) { + final RolloverButton rb = (RolloverButton)o; + rb.setEnabled(true); + } + } + } + + + /** + * Checks to see if the Send Button should be enabled depending on the + * current update in SendField. + * + * @param event the DocumentEvent from the sendField. + */ + public void removeUpdate(DocumentEvent event) { + checkForText(event); + } + + /** + * Checks to see if the Send button should be enabled. + * + * @param docEvent the document event. + */ + public void changedUpdate(DocumentEvent docEvent) { + // Do nothing. + } + + /** + * Return the splitpane used in this chat room. + * + * @return the splitpane used in this chat room. + */ + public JSplitPane getSplitPane() { + return splitPane; + } + + /** + * Returns the ChatPanel that contains the ChatWindow and SendField. + * + * @return the ChatPanel. + */ + public JPanel getChatPanel() { + return chatPanel; + } + + /** + * Close the ChatRoom. + */ + public void closeChatRoom() { + fireClosingListeners(); + } + + /** + * Get the Icon to be used in the tab holding + * this ChatRoom. + * + * @return - Icon to use + */ + public abstract Icon getTabIcon(); + + /** + * Get the roomname to use for this ChatRoom. + * + * @return - the Roomname of this ChatRoom. + */ + public abstract String getRoomname(); + + /** + * Get the title to use in the tab holding this ChatRoom. + * + * @return - the title to use. + */ + public abstract String getTabTitle(); + + /** + * Returns the title of this room to use. The title + * will be used in the title bar of the ChatRoom. + * + * @return - the title of this ChatRoom. + */ + public abstract String getRoomTitle(); + + /** + * Returns the Message.Type specific to this + * chat room. + * GroupChat is Message.Type.GROUP_CHAT + * Normal Chat is Message.TYPE.NORMAL + * + * @return the ChatRooms Message.TYPE + */ + public abstract Message.Type getChatType(); + + + /** + * Returns whether or not this ChatRoom is active. Note: carrying + * a conversation rather than being disabled, as it would be + * transcript mode. + * + * @return true if the chat room is active. + */ + public abstract boolean isActive(); + + + /** + * Returns the notification label. The notification label notifies the + * user of chat room activity, such as the date of the last message + * and typing notifications. + * + * @return the notification label. + */ + public JLabel getNotificationLabel() { + return notificationLabel; + } + + /** + * Adds a packetID to the packedIDList. The packetIDLlist + * keeps track of all messages coming into the chatroom. + * + * @param packetID the packetID to add. + */ + public void addPacketID(String packetID) { + packetIDList.add(packetID); + } + + /** + * Checks if the packetID has already been used. + * + * @param packetID the packetID to check for. + * @return true if the packetID already exists. + */ + public boolean packetIDExists(String packetID) { + return packetIDList.contains(packetID); + } + + /** + * Returns this instance of the chatroom. + * + * @return the current ChatRoom instance. + */ + public ChatRoom getChatRoom() { + return this; + } + + /** + * Returns the toolbar used on top of the chat room. + * + * @return the toolbar used on top of this chat room. + */ + public ChatToolBar getToolBar() { + return toolbar; + } + + + public void insertUpdate(DocumentEvent e) { + // Meant to be overriden + checkForText(e); + } + + + /** + * Override to save transcript in preferred room style. + */ + public void saveTranscript() { + getTranscriptWindow().saveTranscript(getTabTitle() + ".html", getTranscripts(), null); + } + + + /** + * Returns the button panel. The Button Panel contains all tool items + * above the send field. + * + * @return the chat's button panel. + */ + public JPanel getSendFieldToolbar() { + return editorBar; + } + + /** + * Used for the top toolbar. + */ + public class ChatToolBar extends JPanel { + private JPanel buttonPanel; + private JPanel rightPanel; + + /** + * Default Constructor. + */ + public ChatToolBar() { + buttonPanel = new JPanel(); + buttonPanel.setLayout(new FlowLayout(FlowLayout.LEFT)); + + rightPanel = new JPanel(); + rightPanel.setOpaque(false); + rightPanel.setLayout(new BorderLayout()); + + // Set Layout + setLayout(new GridBagLayout()); + + buttonPanel.setOpaque(false); + add(buttonPanel, new GridBagConstraints(1, 1, 1, 1, 0.0, 0.0, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0)); + setOpaque(false); + } + + /** + * Adds a new ChatRoomButton the CommandBar. + * + * @param button the button. + */ + public void addChatRoomButton(ChatRoomButton button) { + buttonPanel.add(button); + + // Make all JButtons the same size + Component[] comps = buttonPanel.getComponents(); + final int no = comps != null ? comps.length : 0; + + List buttons = new ArrayList(); + for (int i = 0; i < no; i++) { + Component component = comps[i]; + if (component instanceof JButton) { + buttons.add((JButton)component); + } + } + + GraphicUtils.makeSameSize((JComponent[])buttons.toArray(new JComponent[buttons.size()])); + } + + /** + * Removes the ChatRoomButton from the CommandBar. + * + * @param button the button. + */ + public void removeChatRoomButton(ChatRoomButton button) { + buttonPanel.remove(button); + } + + /** + * Sets the far-right component of the Commandbar. + * + * @param component the component. + */ + public void setRightComponent(Component component) { + rightPanel.add(component, BorderLayout.CENTER); + } + + + } + + /** + * Returns the number of unread messages in this ChatRoom. + * + * @return the number of unread messages. + */ + public int getUnreadMessageCount() { + return unreadMessageCount; + } + + /** + * Increases the number of unread messages by 1. + */ + public void increaseUnreadMessageCount() { + unreadMessageCount++; + } + + /** + * Resets the number of unread messages. + */ + public void clearUnreadMessageCount() { + unreadMessageCount = 0; + } + + /** + * Returns the bottom panel used in the ChatRoom. + * + * @return the bottomPane; + */ + public JPanel getBottomPanel() { + return bottomPanel; + } + + /** + * Returns the Container which holds the ChatWindow. + * + * @return the Container. + */ + public JPanel getChatWindowPanel() { + return chatWindowPanel; + } + + /** + * Adds a new FileDropListener to allow for Drag and Drop notifications + * of objects onto the ChatWindow. + * + * @param listener the listener. + */ + public void addFileDropListener(FileDropListener listener) { + fileDropListeners.add(listener); + } + + /** + * Remove the FileDropListener from ChatRoom. + * + * @param listener the listener. + */ + public void removeFileDropListener(FileDropListener listener) { + fileDropListeners.remove(listener); + } + + /** + * Notify all users that a collection of files has been dropped onto the ChatRoom. + * + * @param files the files dropped. + */ + public void fireFileDropListeners(Collection files) { + Iterator iter = new ArrayList(fileDropListeners).iterator(); + while (iter.hasNext()) { + ((FileDropListener)iter.next()).filesDropped(files, this); + } + } + + /** + * Returns the panel which contains the toolbar items, such as spell checker. + * + * @return the panel which contains the lower toolbar items. + */ + public JPanel getEditorBar() { + return editorBar; + } + + public void addClosingListener(ChatRoomClosingListener listener) { + closingListeners.add(listener); + } + + public void removeClosingListener(ChatRoomClosingListener listener) { + closingListeners.remove(listener); + } + + private void fireClosingListeners() { + Iterator iter = new ArrayList(closingListeners).iterator(); + while (iter.hasNext()) { + ChatRoomClosingListener listener = (ChatRoomClosingListener)iter.next(); + closingListeners.remove(listener); + listener.closing(); + } + } + + public JScrollPane getScrollPaneForTranscriptWindow() { + return textScroller; + } + + /** + * Return the "Send" button. + * + * @return the send button. + */ + public JButton getSendButton() { + return chatAreaButton.getButton(); + } + + public JSplitPane getVerticalSlipPane() { + return verticalSplit; + } +} + + diff --git a/src/java/org/jivesoftware/spark/ui/ChatRoomButton.java b/src/java/org/jivesoftware/spark/ui/ChatRoomButton.java new file mode 100644 index 00000000..b01458b3 --- /dev/null +++ b/src/java/org/jivesoftware/spark/ui/ChatRoomButton.java @@ -0,0 +1,95 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.ui; + +import javax.swing.Icon; +import javax.swing.JButton; + +import java.awt.Insets; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; + +/** + * Button to use with ChatRooms to allow for conformity in the Chat Room look and feel. + */ +public class ChatRoomButton extends JButton { + + /** + * Create a new ChatRoomButton. + */ + public ChatRoomButton() { + decorate(); + } + + /** + * Create a new ChatRoomButton + * + * @param icon the icon to use on the button. + */ + public ChatRoomButton(Icon icon) { + super(icon); + decorate(); + } + + /** + * Create a new ChatRoomButton. + * + * @param text the button text. + * @param icon the button icon. + */ + public ChatRoomButton(String text, Icon icon) { + super(text, icon); + decorate(); + } + + /** + * Creates a new ChatRoomButton. + * + * @param text the text to display on the button. + */ + public ChatRoomButton(String text) { + super(text); + + decorate(); + } + + + /** + * Decorates the button with the approriate UI configurations. + */ + private void decorate() { + setBorderPainted(false); + setOpaque(true); + + setContentAreaFilled(false); + setMargin(new Insets(0, 0, 0, 0)); + + addMouseListener(new MouseAdapter() { + public void mouseEntered(MouseEvent e) { + if (isEnabled()) { + setBorderPainted(true); + setContentAreaFilled(true); + } + } + + public void mouseExited(MouseEvent e) { + setBorderPainted(false); + setContentAreaFilled(false); + } + }); + + setVerticalTextPosition(JButton.BOTTOM); + setHorizontalTextPosition(JButton.CENTER); + setIconTextGap(2); + } + + +} diff --git a/src/java/org/jivesoftware/spark/ui/ChatRoomClosingListener.java b/src/java/org/jivesoftware/spark/ui/ChatRoomClosingListener.java new file mode 100644 index 00000000..505d9ae8 --- /dev/null +++ b/src/java/org/jivesoftware/spark/ui/ChatRoomClosingListener.java @@ -0,0 +1,22 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.ui; + + +/** + * Implement this interface to listen for ChatRooms closing. + * + * @author Derek DeMoro + */ +public interface ChatRoomClosingListener { + + void closing(); +} diff --git a/src/java/org/jivesoftware/spark/ui/ChatRoomListener.java b/src/java/org/jivesoftware/spark/ui/ChatRoomListener.java new file mode 100644 index 00000000..653a7788 --- /dev/null +++ b/src/java/org/jivesoftware/spark/ui/ChatRoomListener.java @@ -0,0 +1,73 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.ui; + +/** + * The ChatRoomListener interface is one of the interfaces extension + * writers use to add functionality to Spark. + *

        + * In general, you implement this interface in order to listen + * for ChatRoom activity, such as a ChatRoom opening, closing, or being + * activated. + */ +public interface ChatRoomListener { + + /** + * Invoked by ChatRooms when a new ChatRoom has been opened. + * + * @param room - the ChatRoom that has been opened. + * @see ChatContainer + */ + void chatRoomOpened(ChatRoom room); + + /** + * Invoked by ChatRooms when a ChatRoom has been left, but not + * closed. + * + * @param room - the ChatRoom that has been left. + * @see ChatContainer + */ + void chatRoomLeft(ChatRoom room); + + /** + * Invoke by ChatRooms when a ChatRoom has been closed. + * + * @param room - the ChatRoom that has been closed. + */ + void chatRoomClosed(ChatRoom room); + + /** + * Invoked by ChatRooms when a ChatRoom has been activated. + * i.e. it has already been opened, but was deactivated when the user + * selected a new chat room, but now has selected the old one. + * + * @param room - the ChatRoom that has been selected. + */ + void chatRoomActivated(ChatRoom room); + + /** + * Invoked by ChatRooms when a person has joined a chat room. + * + * @param room - the chat room the person has joined + * @param userid - the userid of the person who has joined + */ + void userHasJoined(ChatRoom room, String userid); + + /** + * Invoked by ChatRooms when a person has left a chat room. + * + * @param room - the chat room the person has left + * @param userid - the userid of the person who has left + */ + void userHasLeft(ChatRoom room, String userid); + + +} \ No newline at end of file diff --git a/src/java/org/jivesoftware/spark/ui/ChatRoomListenerAdapter.java b/src/java/org/jivesoftware/spark/ui/ChatRoomListenerAdapter.java new file mode 100644 index 00000000..187378a6 --- /dev/null +++ b/src/java/org/jivesoftware/spark/ui/ChatRoomListenerAdapter.java @@ -0,0 +1,56 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.ui; + +/** + * An abstract adapter class for receiving Chat Room Events. + * The methods in this class are empty. This class exists as + * convenience for creating listener objects. + *

        + * Chat Room events let you track when a room is opened, closed, joined, left and activated. + *

        + * Extend this class to methods for the events of interest. (If you implement the + * ChatRoomListener interface, you have to define all of + * the methods in it. This abstract class defines null methods for them + * all, so you can only have to define methods for events you care about.) + *

        + * Create a listener object using the extended class and then register it with + * the ChatManager's addChatRoomListener method. + * + * @author Derek DeMoro + * @see ChatRoomListener + */ +public abstract class ChatRoomListenerAdapter implements ChatRoomListener { + + public void chatRoomOpened(ChatRoom room) { + + } + + public void chatRoomLeft(ChatRoom room) { + + } + + public void chatRoomClosed(ChatRoom room) { + + } + + public void chatRoomActivated(ChatRoom room) { + + } + + public void userHasJoined(ChatRoom room, String userid) { + + } + + public void userHasLeft(ChatRoom room, String userid) { + + } +} diff --git a/src/java/org/jivesoftware/spark/ui/ChatRoomNotFoundException.java b/src/java/org/jivesoftware/spark/ui/ChatRoomNotFoundException.java new file mode 100644 index 00000000..8bf9c56f --- /dev/null +++ b/src/java/org/jivesoftware/spark/ui/ChatRoomNotFoundException.java @@ -0,0 +1,27 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.ui; + +/** + * Thrown when a Chat Room was not found. + * + * @author Derek DeMoro + */ +public class ChatRoomNotFoundException extends Exception { + + public ChatRoomNotFoundException() { + super(); + } + + public ChatRoomNotFoundException(String msg) { + super(msg); + } +} \ No newline at end of file diff --git a/src/java/org/jivesoftware/spark/ui/ChatRoomPlugin.java b/src/java/org/jivesoftware/spark/ui/ChatRoomPlugin.java new file mode 100644 index 00000000..1ac3e703 --- /dev/null +++ b/src/java/org/jivesoftware/spark/ui/ChatRoomPlugin.java @@ -0,0 +1,61 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.ui; + +import javax.swing.Icon; +import javax.swing.JComponent; + +/** + * Provides a mechanism for users to register themselves as ChatRoomPlugin objects. This allows + * users to initialize their own UI's and attach themselves to all ChatRooms for added feature sets. + */ +public interface ChatRoomPlugin { + + /** + * Sets the ChatRoom to attach to. + * + * @param room the ChatRoom that the ChatRoomPlugin will be attaching to. + */ + void setChatRoom(ChatRoom room); + + /** + * Called whenever the tab containing the ChatRoomPlugin is clicked. + */ + void tabSelected(); + + /** + * Return the name of the title you wish to identify this ChatRoomPlugin by. + * + * @return the title of the tab panel containing the + */ + String getTabTitle(); + + /** + * Return the Icon to use on the tab containing the ChatRoomPlugin. + * + * @return the icon to show on the ChatRoomPlugin tab. + */ + Icon getTabIcon(); + + /** + * Return the tooltip to use on a mouseover of the tab. + * + * @return the tooltip to use on a mouseover of the tab. + */ + String getTabToolTip(); + + /** + * Return's the GUI for the ChatRoomPlugin. + * + * @return the GUI of the ChatRoomPlugin. + */ + JComponent getGUI(); +} diff --git a/src/java/org/jivesoftware/spark/ui/ChatRoomTransferHandler.java b/src/java/org/jivesoftware/spark/ui/ChatRoomTransferHandler.java new file mode 100644 index 00000000..a55a3ef7 --- /dev/null +++ b/src/java/org/jivesoftware/spark/ui/ChatRoomTransferHandler.java @@ -0,0 +1,132 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.ui; + +import org.jivesoftware.spark.util.log.Log; + +import javax.swing.JComponent; +import javax.swing.TransferHandler; + +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.Transferable; +import java.awt.datatransfer.UnsupportedFlavorException; +import java.io.IOException; +import java.util.Collection; + +/** + * Handler for drag and dropping of files unto a ChatWindow. + */ +public class ChatRoomTransferHandler extends TransferHandler { + + private ChatRoom chatRoom; + + private static final DataFlavor flavors[] = {DataFlavor.javaFileListFlavor, DataFlavor.stringFlavor}; + + public ChatRoomTransferHandler(ChatRoom chatRoom) { + this.chatRoom = chatRoom; + } + + public int getSourceActions(JComponent c) { + return TransferHandler.COPY_OR_MOVE; + } + + + public boolean canImport(JComponent comp, DataFlavor flavor[]) { + for (int i = 0, n = flavor.length; i < n; i++) { + for (int j = 0, m = flavors.length; j < m; j++) { + if (flavor[i].equals(flavors[j])) { + return true; + } + } + } + return false; + } + + protected void exportDone(JComponent c, Transferable data, int action) { + + } + + + public Transferable createTransferable(JComponent comp) { + if (comp instanceof TranscriptWindow) { + return new TranscriptWindowTransferable((TranscriptWindow)comp); + } + + return null; + } + + public boolean importData(JComponent comp, Transferable t) { + if (t.isDataFlavorSupported(flavors[0])) { + try { + Object o = t.getTransferData(flavors[0]); + if (o instanceof java.util.Collection) { + Collection files = (Collection)o; + + // Otherwise fire files dropped event. + chatRoom.fireFileDropListeners(files); + return true; + } + } + catch (UnsupportedFlavorException e) { + Log.error(e); + } + catch (IOException e) { + Log.error(e); + } + } + else if (t.isDataFlavorSupported(flavors[1])) { + try { + Object o = t.getTransferData(flavors[1]); + if (o instanceof String) { + // Otherwise fire files dropped event. + chatRoom.getChatInputEditor().insert((String)o); + return true; + } + } + catch (Exception e) { + Log.error(e); + } + + } + return false; + } + + public class TranscriptWindowTransferable implements Transferable { + + private TranscriptWindow item; + + public TranscriptWindowTransferable(TranscriptWindow item) { + this.item = item; + } + + // Returns supported flavors + public DataFlavor[] getTransferDataFlavors() { + return new DataFlavor[]{DataFlavor.stringFlavor}; + } + + // Returns true if flavor is supported + public boolean isDataFlavorSupported(DataFlavor flavor) { + return DataFlavor.stringFlavor.equals(flavor); + } + + // Returns Selected Text + public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException { + if (!DataFlavor.stringFlavor.equals(flavor)) { + throw new UnsupportedFlavorException(flavor); + } + return item.getSelectedText(); + } + } + + +} + + diff --git a/src/java/org/jivesoftware/spark/ui/ContactGroup.java b/src/java/org/jivesoftware/spark/ui/ContactGroup.java new file mode 100644 index 00000000..7e036114 --- /dev/null +++ b/src/java/org/jivesoftware/spark/ui/ContactGroup.java @@ -0,0 +1,682 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.ui; + +import org.jivesoftware.spark.SparkManager; +import org.jivesoftware.spark.component.VerticalFlowLayout; +import org.jivesoftware.spark.component.panes.CollapsiblePane; +import org.jivesoftware.spark.component.renderer.JPanelRenderer; +import org.jivesoftware.spark.util.GraphicUtils; + +import javax.swing.BorderFactory; +import javax.swing.DefaultListModel; +import javax.swing.JList; +import javax.swing.JPanel; +import javax.swing.JWindow; + +import java.awt.Color; +import java.awt.Component; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.Point; +import java.awt.Toolkit; +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; + +/** + * Container representing a RosterGroup within the Contact List. + */ +public class ContactGroup extends CollapsiblePane implements MouseListener { + private List contactItems = new ArrayList(); + private String groupName; + private List listeners = new ArrayList(); + private DefaultListModel model = new DefaultListModel(); + private JList list; + private boolean sharedGroup; + private JPanel listPanel; + + // Used to display no contacts in list. + private final ContactItem noContacts = new ContactItem("There are no online contacts in this group.", null); + + private JWindow window = new JWindow(); + + private List contactGroups = new ArrayList(); + + /** + * Create a new ContactGroup. + * + * @param groupName the name of the new ContactGroup. + */ + public ContactGroup(String groupName) { + list = new JList(model) { + public String getToolTipText(MouseEvent event) { + window.setVisible(false); + window = new JWindow(); + window.setFocusableWindowState(false); + int loc = list.locationToIndex(event.getPoint()); + Point point = list.indexToLocation(loc); + + ContactItem item = (ContactItem)model.getElementAt(loc); + if (item == null || item.getFullJID() == null) { + return null; + } + + ContactInfo info = new ContactInfo(item); + window.getContentPane().add(info); + window.pack(); + info.setBorder(BorderFactory.createEtchedBorder()); + + Point mainWindowLocation = SparkManager.getMainWindow().getLocationOnScreen(); + Point listLocation = list.getLocationOnScreen(); + + int x = (int)mainWindowLocation.getX() + SparkManager.getMainWindow().getWidth(); + + final Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); + if ((int)screenSize.getWidth() - 250 >= x) { + window.setLocation(x, (int)listLocation.getY() + (int)point.getY()); + window.setVisible(true); + } + else { + window.setLocation((int)mainWindowLocation.getX() - 250, (int)listLocation.getY() + (int)point.getY()); + window.setVisible(true); + } + return null; + } + }; + + setTitle(getGroupTitle(groupName)); + + // Use JPanel Renderer + list.setCellRenderer(new JPanelRenderer()); + + this.groupName = groupName; + + listPanel = new JPanel(new VerticalFlowLayout(VerticalFlowLayout.TOP, 0, 0, true, false)); + listPanel.add(list, listPanel); + this.setContentPane(listPanel); + + if (!isOfflineGroup()) { + list.setDragEnabled(true); + list.setTransferHandler(new ContactGroupTransferHandler()); + } + + // Allow for mouse events to take place on the title bar + getTitlePane().addMouseListener(new MouseAdapter() { + public void mousePressed(MouseEvent e) { + if (e.isPopupTrigger()) { + checkPopup(e); + } + } + + public void mouseReleased(MouseEvent e) { + checkPopup(e); + } + + public void mouseClicked(MouseEvent e) { + } + + public void checkPopup(MouseEvent e) { + if (e.isPopupTrigger()) { + e.consume(); + fireContactGroupPopupEvent(e); + } + } + }); + + // Items should have selection listener + list.addMouseListener(this); + + list.addKeyListener(new KeyListener() { + public void keyTyped(KeyEvent keyEvent) { + + } + + public void keyPressed(KeyEvent keyEvent) { + if (keyEvent.getKeyChar() == KeyEvent.VK_ENTER) { + ContactItem item = (ContactItem)list.getSelectedValue(); + fireContactItemDoubleClicked(item); + } + } + + public void keyReleased(KeyEvent keyEvent) { + + } + }); + + noContacts.getNicknameLabel().setFont(new Font("Dialog", Font.PLAIN, 11)); + noContacts.getNicknameLabel().setForeground(Color.GRAY); + model.addElement(noContacts); + } + + /** + * Adds a ContactItem to the ContactGroup. + * + * @param item the ContactItem. + */ + public void addContactItem(ContactItem item) { + if (model.getSize() == 1 && model.getElementAt(0) == noContacts) { + model.remove(0); + } + + if ("Offline Group".equals(groupName)) { + item.getNicknameLabel().setFont(new Font("Dialog", Font.PLAIN, 11)); + item.getNicknameLabel().setForeground(Color.GRAY); + } + + item.setGroupName(getGroupName()); + contactItems.add(item); + + Collections.sort(contactItems, itemComparator); + + int index = contactItems.indexOf(item); + + + Object[] objs = list.getSelectedValues(); + + model.insertElementAt(item, index); + + int[] intList = new int[objs.length]; + for (int i = 0; i < objs.length; i++) { + ContactItem contact = (ContactItem)objs[i]; + intList[i] = model.indexOf(contact); + } + + if (intList.length > 0) { + list.setSelectedIndices(intList); + } + + fireContactItemAdded(item); + } + + /** + * Call whenever the UI needs to be updated. + */ + public void fireContactGroupUpdated() { + list.validate(); + list.repaint(); + updateTitle(); + } + + public void addContactGroup(ContactGroup contactGroup) { + final JPanel panel = new JPanel(new GridBagLayout()); + panel.add(contactGroup, new GridBagConstraints(0, 0, 1, 1, 1.0, 1.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(2, 15, 0, 0), 0, 0)); + panel.setBackground(Color.white); + contactGroup.setSubPane(true); + + // contactGroup.setStyle(CollapsiblePane.TREE_STYLE); + listPanel.add(panel); + contactGroups.add(contactGroup); + } + + public void removeContactGroup(ContactGroup contactGroup) { + Component[] comps = listPanel.getComponents(); + for (int i = 0; i < comps.length; i++) { + Component comp = comps[i]; + if (comp instanceof JPanel) { + JPanel panel = (JPanel)comp; + ContactGroup group = (ContactGroup)panel.getComponent(0); + if (group == contactGroup) { + listPanel.remove(panel); + break; + } + } + } + + + contactGroups.remove(contactGroup); + } + + public void setPanelBackground(Color color) { + Component[] comps = listPanel.getComponents(); + for (int i = 0; i < comps.length; i++) { + Component comp = comps[i]; + if (comp instanceof JPanel) { + JPanel panel = (JPanel)comp; + panel.setBackground(color); + } + } + + } + + public ContactGroup getContactGroup(String groupName) { + final Iterator groups = new ArrayList(contactGroups).iterator(); + while (groups.hasNext()) { + ContactGroup group = (ContactGroup)groups.next(); + if (group.getGroupName().equals(groupName)) { + return group; + } + } + + return null; + } + + /** + * Removes a ContactItem. + * + * @param item the ContactItem to remove. + */ + public void removeContactItem(ContactItem item) { + contactItems.remove(item); + + model.removeElement(item); + updateTitle(); + + fireContactItemRemoved(item); + } + + /** + * Returns a ContactItem by the nickname the user has been assigned. + * + * @param nickname the nickname of the user. + * @return the ContactItem. + */ + public ContactItem getContactItemByNickname(String nickname) { + final Iterator iter = new ArrayList(contactItems).iterator(); + while (iter.hasNext()) { + ContactItem item = (ContactItem)iter.next(); + if (item.getNickname().equals(nickname)) { + return item; + } + } + return null; + } + + /** + * Returns a ContactItem by the users bare bareJID. + * + * @param bareJID the bareJID of the user. + * @return the ContactItem. + */ + public ContactItem getContactItemByJID(String bareJID) { + final Iterator iter = new ArrayList(contactItems).iterator(); + while (iter.hasNext()) { + ContactItem item = (ContactItem)iter.next(); + if (item.getFullJID().equals(bareJID)) { + return item; + } + } + return null; + } + + /** + * Returns all ContactItems in the ContactGroup. + * + * @return all ContactItems. + */ + public List getContactItems() { + return contactItems; + } + + /** + * Returns the name of the ContactGroup. + * + * @return the name of the ContactGroup. + */ + public String getGroupName() { + return groupName; + } + + + public void mouseClicked(MouseEvent e) { + + Object o = list.getSelectedValue(); + if (!(o instanceof ContactItem)) { + return; + } + + // Iterator through rest + ContactItem item = (ContactItem)o; + + if (e.getClickCount() == 2) { + fireContactItemDoubleClicked(item); + } + else if (e.getClickCount() == 1) { + fireContactItemClicked(item); + } + } + + public void mouseEntered(MouseEvent e) { + int loc = list.locationToIndex(e.getPoint()); + + Object o = model.getElementAt(loc); + if (!(o instanceof ContactItem)) { + return; + } + + ContactItem item = (ContactItem)o; + if (item == null) { + return; + } + + list.setCursor(GraphicUtils.HAND_CURSOR); + } + + public void mouseExited(MouseEvent e) { + window.setVisible(false); + + int loc = list.locationToIndex(e.getPoint()); + + Object o = model.getElementAt(loc); + if (!(o instanceof ContactItem)) { + return; + } + + ContactItem item = (ContactItem)o; + if (item == null) { + return; + } + list.setCursor(GraphicUtils.DEFAULT_CURSOR); + + } + + public void mousePressed(MouseEvent e) { + checkPopup(e); + } + + public void mouseReleased(MouseEvent e) { + checkPopup(e); + } + + private void checkPopup(MouseEvent e) { + if (e.isPopupTrigger()) { + // Check for multi selection + int[] indeces = list.getSelectedIndices(); + List selection = new ArrayList(); + for (int i = 0; i < indeces.length; i++) { + selection.add(model.getElementAt(indeces[i])); + } + + if (selection.size() > 1) { + firePopupEvent(e, selection); + return; + } + + // Otherwise, handle single selection + int index = list.locationToIndex(e.getPoint()); + + Object o = model.getElementAt(index); + if (!(o instanceof ContactItem)) { + return; + } + ContactItem item = (ContactItem)o; + list.setSelectedIndex(index); + + firePopupEvent(e, item); + } + } + + /** + * Add a ContactGroupListener. + * + * @param listener the ContactGroupListener. + */ + public void addContactGroupListener(ContactGroupListener listener) { + listeners.add(listener); + } + + /** + * Removes a ContactGroupListener. + * + * @param listener the ContactGroupListener. + */ + public void removeContactGroupListener(ContactGroupListener listener) { + listeners.remove(listener); + } + + private void fireContactItemClicked(ContactItem item) { + final Iterator iter = new ArrayList(listeners).iterator(); + while (iter.hasNext()) { + ((ContactGroupListener)iter.next()).contactItemClicked(item); + } + } + + private void fireContactItemDoubleClicked(ContactItem item) { + final Iterator iter = new ArrayList(listeners).iterator(); + while (iter.hasNext()) { + ((ContactGroupListener)iter.next()).contactItemDoubleClicked(item); + } + } + + + private void firePopupEvent(MouseEvent e, ContactItem item) { + final Iterator iter = new ArrayList(listeners).iterator(); + while (iter.hasNext()) { + ((ContactGroupListener)iter.next()).showPopup(e, item); + } + } + + private void firePopupEvent(MouseEvent e, Collection items) { + final Iterator iter = new ArrayList(listeners).iterator(); + while (iter.hasNext()) { + ((ContactGroupListener)iter.next()).showPopup(e, items); + } + } + + private void fireContactGroupPopupEvent(MouseEvent e) { + final Iterator iter = new ArrayList(listeners).iterator(); + while (iter.hasNext()) { + ((ContactGroupListener)iter.next()).contactGroupPopup(e, this); + } + } + + private void fireContactItemAdded(ContactItem item) { + final Iterator iter = new ArrayList(listeners).iterator(); + while (iter.hasNext()) { + ((ContactGroupListener)iter.next()).contactItemAdded(item); + } + } + + private void fireContactItemRemoved(ContactItem item) { + final Iterator iter = new ArrayList(listeners).iterator(); + while (iter.hasNext()) { + ((ContactGroupListener)iter.next()).contactItemRemoved(item); + } + } + + private void updateTitle() { + if ("Offline Group".equals(groupName)) { + setTitle("Offline Group"); + return; + } + + int count = 0; + List list = new ArrayList(getContactItems()); + int size = list.size(); + for (int i = 0; i < size; i++) { + ContactItem it = (ContactItem)list.get(i); + if (it.isAvailable()) { + count++; + } + } + + setTitle(getGroupTitle(groupName) + " (" + count + " online)"); + + + if (model.getSize() == 0) { + model.addElement(noContacts); + } + } + + /** + * Returns the containing JList of the ContactGroup. + * + * @return the JList. + */ + public JList getList() { + return list; + } + + /** + * Clears all selections within this group. + */ + public void clearSelection() { + list.clearSelection(); + } + + /** + * Returns true if the ContactGroup contains available users. + * + * @return true if the ContactGroup contains available users. + */ + public boolean hasAvailableContacts() { + final Iterator iter = contactGroups.iterator(); + while (iter.hasNext()) { + ContactGroup group = (ContactGroup)iter.next(); + + if (group.hasAvailableContacts()) { + return true; + } + } + + Iterator contacts = getContactItems().iterator(); + while (contacts.hasNext()) { + ContactItem item = (ContactItem)contacts.next(); + if (item.getPresence() != null) { + return true; + } + } + return false; + } + + /** + * Sorts ContactItems. + */ + final Comparator itemComparator = new Comparator() { + public int compare(Object contactItemOne, Object contactItemTwo) { + final ContactItem item1 = (ContactItem)contactItemOne; + final ContactItem item2 = (ContactItem)contactItemTwo; + return item1.getNickname().toLowerCase().compareTo(item2.getNickname().toLowerCase()); + + } + }; + + /** + * Returns true if this ContactGroup is the Offline Group. + * + * @return true if OfflineGroup. + */ + public boolean isOfflineGroup() { + return "Offline Group".equals(getGroupName()); + } + + /** + * Returns true if this ContactGroup is the Unfiled Group. + * + * @return true if UnfiledGroup. + */ + public boolean isUnfiledGroup() { + return "Unfiled".equals(getGroupName()); + } + + public String toString() { + return getGroupName(); + } + + /** + * Returns true if ContactGroup is a Shared Group. + * + * @return true if Shared Group. + */ + public boolean isSharedGroup() { + return sharedGroup; + } + + /** + * Set to true if this ContactGroup is a shared Group. + * + * @param sharedGroup true if shared group. + */ + protected void setSharedGroup(boolean sharedGroup) { + this.sharedGroup = sharedGroup; + if (sharedGroup) { + //setIcon(LaRes.getImageIcon(LaRes.SMALL_ALL_AGENTS_IMAGE)); + // Allow for mouse events to take place on the title bar + setToolTipText(getGroupName() + " is a Shared Group"); + //setHorizontalTextPosition(JLabel.LEFT); + } + } + + /** + * Returns all Selected Contacts within the ContactGroup. + * + * @return all selected ContactItems. + */ + public List getSelectedContacts() { + final List items = new ArrayList(); + Object[] selections = list.getSelectedValues(); + final int no = selections != null ? selections.length : 0; + for (int i = 0; i < no; i++) { + ContactItem item = (ContactItem)selections[i]; + items.add(item); + } + return items; + } + + public JPanel getContainerPanel() { + return listPanel; + } + + public Collection getContactGroups() { + return contactGroups; + } + + /** + * Lets make sure that the panel doesn't stretch past the + * scrollpane view pane. + * + * @return the preferred dimension + */ + public Dimension getPreferredSize() { + final Dimension size = super.getPreferredSize(); + size.width = 0; + return size; + } + + public void setGroupName(String groupName) { + this.groupName = groupName; + } + + public String getGroupTitle(String title) { + int lastIndex = title.lastIndexOf("::"); + if (lastIndex != -1) { + title = title.substring(lastIndex + 2); + } + + return title; + } + + public boolean isSubGroup(String groupName) { + return groupName.indexOf("::") != -1; + } + + public boolean isSubGroup() { + return isSubGroup(getGroupName()); + } + + public JPanel getListPanel() { + return listPanel; + } + +} + + diff --git a/src/java/org/jivesoftware/spark/ui/ContactGroupListener.java b/src/java/org/jivesoftware/spark/ui/ContactGroupListener.java new file mode 100644 index 00000000..4071c4d4 --- /dev/null +++ b/src/java/org/jivesoftware/spark/ui/ContactGroupListener.java @@ -0,0 +1,78 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.ui; + +import java.awt.event.MouseEvent; +import java.util.Collection; + +/** + * The ContactGroupListener interface is one of the interfaces extension writers use to add functionality to Spark. + *

        + * In general, you implement this interface in order to listen for mouse and key events on ContactGroups within + * the Spark client ContactList. + */ +public interface ContactGroupListener { + + /** + * Notifies a user that a new ContactItem has been added to the ContactGroup. + * + * @param item the ContactItem. + */ + public void contactItemAdded(ContactItem item); + + /** + * Notifies the user that a ContactItem has been removed from a ContactGroup. + * + * @param item the ContactItem removed. + */ + public void contactItemRemoved(ContactItem item); + + /** + * Notifies the user that a ContactItem within the ContactGroup has been double-clicked. + * + * @param item the ContactItem double clicked. + */ + public void contactItemDoubleClicked(ContactItem item); + + /** + * Notifies the user that a ContactItem within the ContactGroup has been clicked. + * + * @param item the ContactItem clicked. + */ + public void contactItemClicked(ContactItem item); + + /** + * Notifies the user that a popup call has occured on the ContactGroup. + * + * @param e the MouseEvent that triggered the event. + * @param item the ContactItem clicked within the ContactGroup. + * @deprecated see ContextMenuListener + */ + public void showPopup(MouseEvent e, ContactItem item); + + /** + * Notifies the user that a popup call has occured on the ContactGroup. + * + * @param e the MouseEvent that triggered the event. + * @param items the ContactItems within the ContactGroup. + * @deprecated see ContextMenuListener + */ + public void showPopup(MouseEvent e, Collection items); + + /** + * Notifies the user that a Popup event has occured on the ContactGroup title + * bar. + * + * @param e the MouseEvent that triggered the event. + * @param group the ContactGroup. + */ + public void contactGroupPopup(MouseEvent e, ContactGroup group); +} diff --git a/src/java/org/jivesoftware/spark/ui/ContactGroupTransferHandler.java b/src/java/org/jivesoftware/spark/ui/ContactGroupTransferHandler.java new file mode 100644 index 00000000..3fe62ba9 --- /dev/null +++ b/src/java/org/jivesoftware/spark/ui/ContactGroupTransferHandler.java @@ -0,0 +1,317 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.ui; + +import org.jivesoftware.smack.Roster; +import org.jivesoftware.smack.RosterEntry; +import org.jivesoftware.smack.RosterGroup; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.spark.SparkManager; +import org.jivesoftware.spark.util.SwingWorker; +import org.jivesoftware.spark.util.log.Log; + +import javax.swing.DefaultListModel; +import javax.swing.JComponent; +import javax.swing.JList; +import javax.swing.TransferHandler; + +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.Transferable; +import java.awt.datatransfer.UnsupportedFlavorException; +import java.io.IOException; +import java.util.Collection; +import java.util.Iterator; + +public class ContactGroupTransferHandler extends TransferHandler { + + + private static final DataFlavor flavors[] = {DataFlavor.imageFlavor, DataFlavor.javaFileListFlavor}; + + + public int getSourceActions(JComponent c) { + return TransferHandler.MOVE; + } + + + public boolean canImport(JComponent comp, DataFlavor flavor[]) { + if (!(comp instanceof JList)) { + return false; + } + + + JList list = (JList)comp; + ContactGroup group = getContactGroup(list); + if ((group.isSharedGroup() && !flavor[0].equals(DataFlavor.javaFileListFlavor)) || group.isUnfiledGroup() || group.isOfflineGroup() || (!group.hasAvailableContacts() && flavor[0].equals(DataFlavor.javaFileListFlavor))) { + return false; + } + + for (int i = 0, n = flavor.length; i < n; i++) { + for (int j = 0, m = flavors.length; j < m; j++) { + if (flavor[i].equals(flavors[j])) { + return true; + } + } + } + return false; + } + + protected void exportDone(JComponent c, Transferable data, int action) { + if (data == null) { + return; + } + + if (c instanceof JList) { + JList list = (JList)c; + ContactGroup group = getContactGroup(list); + try { + ContactItem item = (ContactItem)data.getTransferData(flavors[0]); + if (action == MOVE) { + } + } + catch (UnsupportedFlavorException e) { + Log.error(e); + } + catch (IOException e) { + Log.error(e); + } + + } + } + + + public Transferable createTransferable(JComponent comp) { + + if (comp instanceof JList) { + JList list = (JList)comp; + ContactItem source = (ContactItem)list.getSelectedValue(); + Transferable transferable = new ContactItemTransferable(source); + return transferable; + } + return null; + } + + public boolean importData(JComponent comp, Transferable t) { + if (comp instanceof JList) { + JList list = (JList)comp; + ContactGroup group = getContactGroup(list); + + if (t.isDataFlavorSupported(flavors[0])) { + try { + ContactItem item = (ContactItem)t.getTransferData(flavors[0]); + int index = list.getSelectedIndex(); + DefaultListModel model = (DefaultListModel)list.getModel(); + int size = model.getSize(); + for (int i = 0; i < size; i++) { + ContactItem it = (ContactItem)model.getElementAt(i); + if (it.getNickname().equals(item.getNickname())) { + return false; + } + } + + addContactItem(group, item); + return true; + } + catch (UnsupportedFlavorException ignored) { + } + catch (IOException ignored) { + } + } + else if (t.isDataFlavorSupported(flavors[1])) { + try { + Object o = t.getTransferData(flavors[1]); + if (o instanceof java.util.Collection) { + Collection files = (Collection)o; + ContactItem source = (ContactItem)list.getSelectedValue(); + if (source == null || source.getFullJID() == null) { + return false; + } + + // Otherwise fire files dropped event. + SparkManager.getWorkspace().getContactList().fireFilesDropped(files, source); + } + } + catch (UnsupportedFlavorException e) { + Log.error(e); + } + catch (IOException e) { + Log.error(e); + } + } + } + return false; + } + + public class ContactItemTransferable implements Transferable { + + private ContactItem item; + + public ContactItemTransferable(ContactItem item) { + this.item = item; + } + + // Returns supported flavors + public DataFlavor[] getTransferDataFlavors() { + return new DataFlavor[]{DataFlavor.imageFlavor}; + } + + // Returns true if flavor is supported + public boolean isDataFlavorSupported(DataFlavor flavor) { + return DataFlavor.imageFlavor.equals(flavor); + } + + // Returns image + public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException { + if (!DataFlavor.imageFlavor.equals(flavor)) { + throw new UnsupportedFlavorException(flavor); + } + return item; + } + } + + private ContactGroup getContactGroup(JList list) { + ContactList contactList = SparkManager.getWorkspace().getContactList(); + Iterator groups = contactList.getContactGroups().iterator(); + while (groups.hasNext()) { + ContactGroup group = (ContactGroup)groups.next(); + if (group.getList() == list) { + return group; + } + + ContactGroup subGroup = getSubContactGroup(group, list); + if (subGroup != null) { + return subGroup; + } + } + return null; + } + + private ContactGroup getSubContactGroup(ContactGroup group, JList list) { + Iterator subGroups = group.getContactGroups().iterator(); + while (subGroups.hasNext()) { + ContactGroup g = (ContactGroup)subGroups.next(); + if (g.getList() == list) { + return g; + } + + // Search subs + ContactGroup g1 = getSubContactGroup(g, list); + if (g1 != null) { + return g1; + } + } + + return null; + } + + + private ContactGroup getContactGroup(String groupName) { + ContactList contactList = SparkManager.getWorkspace().getContactList(); + ContactGroup group = contactList.getContactGroup(groupName); + return group; + } + + private void addContactItem(final ContactGroup contactGroup, final ContactItem item) { + ContactItem newContact = new ContactItem(item.getNickname(), item.getFullJID()); + newContact.setPresence(item.getPresence()); + newContact.setIcon(item.getIcon()); + newContact.getNicknameLabel().setFont(item.getNicknameLabel().getFont()); + contactGroup.addContactItem(newContact); + contactGroup.clearSelection(); + + final ContactGroup oldGroup = getContactGroup(item.getGroupName()); + + SwingWorker worker = new SwingWorker() { + public Object construct() { + Roster roster = SparkManager.getConnection().getRoster(); + RosterEntry entry = roster.getEntry(item.getFullJID()); + + Iterator iter = roster.getGroups(); + RosterGroup groupFound = null; + while (iter.hasNext()) { + RosterGroup group = (RosterGroup)iter.next(); + if (group.getName().equals(contactGroup.getGroupName())) { + try { + groupFound = group; + group.addEntry(entry); + } + catch (XMPPException e1) { + Log.error(e1); + } + } + } + + // This is a new group + if (groupFound == null) { + groupFound = roster.createGroup(contactGroup.getGroupName()); + try { + groupFound.addEntry(entry); + } + catch (XMPPException e) { + Log.error(e); + } + } + return "ok"; + } + + public void finished() { + // Now try and remove the group from the old one. + removeContactItem(oldGroup, item); + } + + }; + + worker.start(); + } + + + public boolean removeContactItem(ContactGroup contactGroup, ContactItem item) { + if (contactGroup.isSharedGroup()) { + return false; + } + + if (contactGroup.isUnfiledGroup()) { + contactGroup.removeContactItem(item); + contactGroup.fireContactGroupUpdated(); + return true; + } + + // Remove entry from Roster Group + Roster roster = SparkManager.getConnection().getRoster(); + RosterEntry entry = roster.getEntry(item.getFullJID()); + + Iterator groups = roster.getGroups(); + RosterGroup rosterGroup = null; + while (groups.hasNext()) { + RosterGroup group = (RosterGroup)groups.next(); + if (group.getName().equals(contactGroup.getGroupName())) { + try { + rosterGroup = group; + group.removeEntry(entry); + } + catch (XMPPException e1) { + return false; + } + } + } + + if (rosterGroup == null) { + return false; + } + + if (!rosterGroup.contains(entry)) { + contactGroup.removeContactItem(item); + return true; + } + + return false; + } +} + diff --git a/src/java/org/jivesoftware/spark/ui/ContactInfo.java b/src/java/org/jivesoftware/spark/ui/ContactInfo.java new file mode 100644 index 00000000..9d150ae6 --- /dev/null +++ b/src/java/org/jivesoftware/spark/ui/ContactInfo.java @@ -0,0 +1,107 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.ui; + +import org.jivesoftware.resource.SparkRes; +import org.jivesoftware.spark.component.WrappedLabel; +import org.jivesoftware.spark.component.borders.PartialLineBorder; +import org.jivesoftware.spark.util.GraphicUtils; +import org.jivesoftware.spark.util.log.Log; + +import javax.swing.BorderFactory; +import javax.swing.ImageIcon; +import javax.swing.JLabel; +import javax.swing.JPanel; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Image; +import java.awt.Insets; +import java.net.MalformedURLException; +import java.net.URL; + +/** + * Represents the UI for the "ToolTip" functionallity in the ContactList. + * + * @author Derek DeMoro + */ +public class ContactInfo extends JPanel { + private ContactItem contactItem; + + public ContactInfo(ContactItem contactItem) { + this.contactItem = contactItem; + + setLayout(new GridBagLayout()); + setBackground(Color.white); + + + final WrappedLabel nicknameLabel = new WrappedLabel(); + final WrappedLabel statusLabel = new WrappedLabel(); + final JLabel fullJIDLabel = new JLabel(); + final JLabel imageLabel = new JLabel(); + + add(nicknameLabel, new GridBagConstraints(0, 0, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 0, 5), 100, 0)); + add(statusLabel, new GridBagConstraints(0, 1, 1, 1, 1.0, 0.0, GridBagConstraints.NORTHWEST, GridBagConstraints.HORIZONTAL, new Insets(0, 2, 5, 5), 0, 0)); + add(fullJIDLabel, new GridBagConstraints(0, 2, 2, 1, 0.0, 1.0, GridBagConstraints.SOUTH, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); + add(imageLabel, new GridBagConstraints(1, 0, 1, 2, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 0, 5, 5), 0, 0)); + + nicknameLabel.setFont(new Font("Dialog", Font.BOLD, 12)); + statusLabel.setFont(new Font("Dialog", Font.PLAIN, 12)); + statusLabel.setForeground(Color.gray); + fullJIDLabel.setFont(new Font("Dialog", Font.PLAIN, 12)); + fullJIDLabel.setForeground(Color.gray); + + nicknameLabel.setText(contactItem.getNickname()); + nicknameLabel.setBorder(BorderFactory.createMatteBorder(0, 0, 1, 0, Color.lightGray)); + fullJIDLabel.setBorder(BorderFactory.createMatteBorder(1, 0, 0, 0, Color.lightGray)); + + statusLabel.setText(contactItem.getStatus()); + fullJIDLabel.setText(contactItem.getFullJID()); + + + try { + URL avatarURL = contactItem.getAvatarURL(); + ImageIcon icon = null; + if (avatarURL != null) { + icon = new ImageIcon(avatarURL); + } + + if (icon != null && icon.getIconHeight() > 1) { + icon = GraphicUtils.scaleImageIcon(icon, 96, 96); + imageLabel.setIcon(icon); + + imageLabel.setBorder(new PartialLineBorder(Color.gray, 1)); + } + else { + icon = new ImageIcon(SparkRes.getImageIcon(SparkRes.BLANK_24x24).getImage().getScaledInstance(1, 64, Image.SCALE_SMOOTH)); + imageLabel.setIcon(icon); + + + } + } + catch (MalformedURLException e) { + Log.error(e); + } + } + + public ContactItem getContactItem() { + return contactItem; + } + + public Dimension getPreferredSize() { + final Dimension size = super.getPreferredSize(); + size.width = 250; + return size; + } +} diff --git a/src/java/org/jivesoftware/spark/ui/ContactItem.java b/src/java/org/jivesoftware/spark/ui/ContactItem.java new file mode 100644 index 00000000..29e447a2 --- /dev/null +++ b/src/java/org/jivesoftware/spark/ui/ContactItem.java @@ -0,0 +1,485 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.ui; + +import org.jivesoftware.resource.SparkRes; +import org.jivesoftware.smack.Roster; +import org.jivesoftware.smack.RosterEntry; +import org.jivesoftware.smack.packet.DefaultPacketExtension; +import org.jivesoftware.smack.packet.PacketExtension; +import org.jivesoftware.smack.packet.Presence; +import org.jivesoftware.smack.packet.RosterPacket; +import org.jivesoftware.smackx.packet.VCard; +import org.jivesoftware.spark.SparkManager; +import org.jivesoftware.spark.ui.status.StatusItem; +import org.jivesoftware.spark.util.GraphicUtils; +import org.jivesoftware.spark.util.ModelUtil; +import org.jivesoftware.spark.util.URLFileSystem; +import org.jivesoftware.spark.util.log.Log; +import org.jivesoftware.sparkimpl.profile.VCardManager; + +import javax.imageio.ImageIO; +import javax.swing.Icon; +import javax.swing.ImageIcon; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.UIManager; + +import java.awt.Color; +import java.awt.Font; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.event.MouseEvent; +import java.awt.image.BufferedImage; +import java.io.File; +import java.net.MalformedURLException; +import java.net.URL; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.List; + +public class ContactItem extends JPanel { + private JLabel imageLabel; + private JLabel nicknameLabel; + private JLabel descriptionLabel; + private String nickname; + private String fullJID; + private Icon icon; + + private String status; + private String groupName; + + boolean available; + + private Presence presence; + + private String hash = ""; + + private Date awayTime; + private List presenceHistory = new ArrayList(); + private File contactsDir = null; + + private ContactItemHandler handler; + private JLabel sideIcon; + + public ContactItem(String nickname, String fullJID) { + setLayout(new GridBagLayout()); + + contactsDir = new File(SparkManager.getUserDirectory(), "contacts"); + + nicknameLabel = new JLabel(); + descriptionLabel = new JLabel(); + imageLabel = new JLabel(); + sideIcon = new JLabel(); + + nicknameLabel.setHorizontalTextPosition(JLabel.LEFT); + nicknameLabel.setHorizontalAlignment(JLabel.LEFT); + nicknameLabel.setText(nickname); + + + descriptionLabel.setFont(new Font("Dialog", Font.PLAIN, 11)); + descriptionLabel.setForeground((Color)UIManager.get("ContactItemDescription.foreground")); + descriptionLabel.setHorizontalTextPosition(JLabel.LEFT); + descriptionLabel.setHorizontalAlignment(JLabel.LEFT); + + + this.setOpaque(true); + + add(imageLabel, new GridBagConstraints(0, 0, 1, 2, 0.0, 0.0, GridBagConstraints.NORTH, GridBagConstraints.HORIZONTAL, new Insets(0, 10, 0, 0), 0, 0)); + add(nicknameLabel, new GridBagConstraints(1, 0, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(0, 5, 0, 0), 0, 0)); + add(descriptionLabel, new GridBagConstraints(2, 0, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(0, 5, 2, 0), 0, 0)); + add(sideIcon, new GridBagConstraints(3, 0, 1, 2, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(0, 5, 0, 0), 0, 0)); + + setNickname(nickname); + setFullJID(fullJID); + } + + public String getNickname() { + return nickname; + } + + public void setNickname(String nickname) { + this.nickname = nickname; + nicknameLabel.setText(nickname); + } + + public String getFullJID() { + return fullJID; + } + + public void setFullJID(String fullJID) { + this.fullJID = fullJID; + } + + public Icon getIcon() { + return icon; + } + + public void setIcon(Icon icon) { + this.icon = icon; + imageLabel.setIcon(icon); + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + public String getContactJID() { + return fullJID; + } + + + public String getToolTipText(MouseEvent e) { + if (true) { + return null; + } + if (getFullJID() == null) { + return nicknameLabel.getText(); + } + + URL imageURL = null; + String fileLocation = ""; + try { + imageURL = getAvatarURL(); + if (imageURL != null) { + fileLocation = imageURL.toExternalForm(); + } + } + catch (MalformedURLException ee) { + Log.error(ee); + } + + String jid = getFullJID(); + if (getPresence() != null) { + jid = getPresence().getFrom(); + } + + if (status == null) { + status = "Offline"; + } + + Roster roster = SparkManager.getConnection().getRoster(); + RosterEntry entry = roster.getEntry(getFullJID()); + String subscriptionStatus = ""; + if (entry != null && !entry.getType().toString().equals("both")) { + subscriptionStatus = "Subscription:" + entry.getType().toString() + ""; + } + + String imageHTML = ""; + if (imageURL == null || URLFileSystem.url2File(imageURL).isDirectory()) { + imageHTML = ""; + } + + String awayText = ""; + if (awayTime != null) { + Date now = new Date(); + long time = now.getTime() - awayTime.getTime(); + awayText = "Away since:" + ModelUtil.getTimeFromLong(time) + ""; + } + + return "" + imageHTML + "" + + "" + + "" + subscriptionStatus + awayText + + "
        " + getNickname() + "
        JID:" + jid + "
        Status:" + status + "
        "; + } + + + public String getGroupName() { + return groupName; + } + + public void setGroupName(String groupName) { + this.groupName = groupName; + } + + public boolean isAvailable() { + return available; + } + + public void setAvailable(boolean available) { + this.available = available; + } + + public JLabel getNicknameLabel() { + return nicknameLabel; + } + + public JLabel getDescriptionLabel() { + return descriptionLabel; + } + + public Presence getPresence() { + return presence; + } + + public void setPresence(Presence presence) { + // Store the presence history. + storePresenceHistory(presence); + + this.presence = presence; + if (presence != null) { + PacketExtension ext = presence.getExtension("x", "vcard-temp:x:update"); + + if (ext != null) { + DefaultPacketExtension o = (DefaultPacketExtension)ext; + String hash = o.getValue("photo"); + if (hash != null) { + this.hash = hash; + + if (!hashExists(hash)) { + updateAvatar(hash); + } + } + } + } + + updatePresenceIcon(presence); + } + + private boolean hashExists(String hash) { + contactsDir.mkdirs(); + + final File imageFile = new File(contactsDir, hash); + return imageFile.exists(); + } + + public URL getAvatarURL() throws MalformedURLException { + contactsDir.mkdirs(); + + if (ModelUtil.hasLength(hash)) { + final File imageFile = new File(contactsDir, hash); + if (imageFile.exists()) { + return imageFile.toURL(); + } + } + + return null; + } + + private void updateAvatar(final String hash) { + Thread updateAvatarThread = new Thread(new Runnable() { + public void run() { + contactsDir.mkdirs(); + + final File imageFile = new File(contactsDir, hash); + + VCard vcard = SparkManager.getVCardManager().getVCard(getFullJID()); + + try { + byte[] bytes = vcard.getAvatar(); + if (bytes != null) { + ImageIcon icon = new ImageIcon(bytes); + icon = VCardManager.scale(icon); + if (icon != null && icon.getIconWidth() != -1) { + BufferedImage image = GraphicUtils.convert(icon.getImage()); + ImageIO.write(image, "PNG", imageFile); + } + } + + SparkManager.getVCardManager().addVCard(getFullJID(), vcard); + } + catch (Exception e) { + Log.error("Unable to update avatar in Contact Item.", e); + } + } + }); + + updateAvatarThread.start(); + } + + public String toString() { + return nicknameLabel.getText(); + } + + private void storePresenceHistory(Presence presence) { + final SimpleDateFormat formatter = new SimpleDateFormat("MM/dd/yyyy h:mm:ss a"); + String date = formatter.format(new Date()); + + if (presence == null) { + if (this.presence != null && (this.presence.getMode() == Presence.Mode.AVAILABLE || this.presence.getMode() == Presence.Mode.CHAT)) { + awayTime = new Date(); + presenceHistory.add("Signed out at " + date); + } + return; + } + + // Add away time. + if (presence.getMode() == Presence.Mode.AVAILABLE || presence.getMode() == Presence.Mode.CHAT) { + awayTime = null; + + if (this.presence == null || (this.presence.getMode() != Presence.Mode.AVAILABLE || this.presence.getMode() != Presence.Mode.CHAT)) { + String status = presence.getStatus(); + if (!ModelUtil.hasLength(status)) { + status = "Available"; + } + + presenceHistory.add(status + " at " + date); + } + } + else if (this.presence != null && (this.presence.getMode() == Presence.Mode.AVAILABLE || this.presence.getMode() == Presence.Mode.CHAT)) { + if (presence != null && presence.getMode() != Presence.Mode.AVAILABLE && presence.getMode() != Presence.Mode.CHAT) { + awayTime = new Date(); + String status = presence.getStatus(); + if (!ModelUtil.hasLength(status)) { + status = "Away"; + } + + presenceHistory.add(status + " at " + date); + } + else { + awayTime = null; + if (!ModelUtil.hasLength(status)) { + status = "Away"; + } + + presenceHistory.add(status + " at " + date); + } + } + } + + public Collection getPresenceHistory() { + return presenceHistory; + } + + private void updatePresenceIcon(Presence presence) { + if (handler != null) { + handler.handlePresence(presence); + return; + } + + + String status = presence != null ? presence.getStatus() : null; + Icon statusIcon = SparkRes.getImageIcon(SparkRes.GREEN_BALL); + boolean isAvailable = false; + if (status == null && presence != null) { + Presence.Mode mode = presence.getMode(); + if (mode == Presence.Mode.AVAILABLE) { + status = "Available"; + isAvailable = true; + } + else if (mode == Presence.Mode.AWAY) { + status = "I'm away"; + statusIcon = SparkRes.getImageIcon(SparkRes.IM_AWAY); + } + else if (mode == Presence.Mode.CHAT) { + status = "I'm free to chat"; + } + else if (mode == Presence.Mode.DO_NOT_DISTURB) { + status = "Do not disturb"; + statusIcon = SparkRes.getImageIcon(SparkRes.IM_AWAY); + } + else if (mode == Presence.Mode.EXTENDED_AWAY) { + status = "Extended away"; + statusIcon = SparkRes.getImageIcon(SparkRes.IM_AWAY); + } + } + else if (presence != null && (presence.getMode() == Presence.Mode.DO_NOT_DISTURB || presence.getMode() == Presence.Mode.AWAY || presence.getMode() == Presence.Mode.EXTENDED_AWAY)) { + statusIcon = SparkRes.getImageIcon(SparkRes.IM_AWAY); + } + else if (presence != null && presence.getType() == Presence.Type.AVAILABLE) { + isAvailable = true; + } + else if (presence == null) { + getNicknameLabel().setFont(new Font("Dialog", Font.PLAIN, 11)); + getNicknameLabel().setForeground((Color)UIManager.get("ContactItemOffline.color")); + + RosterEntry entry = SparkManager.getConnection().getRoster().getEntry(getFullJID()); + if (entry != null && (entry.getType() == RosterPacket.ItemType.NONE || entry.getType() == RosterPacket.ItemType.FROM) + && RosterPacket.ItemStatus.SUBSCRIPTION_PENDING == entry.getStatus()) { + // Do not move out of group. + setIcon(SparkRes.getImageIcon(SparkRes.SMALL_QUESTION)); + getNicknameLabel().setFont(new Font("Dialog", Font.PLAIN, 11)); + setStatusText("Pending"); + } + else { + setIcon(null); + setFont(new Font("Dialog", Font.PLAIN, 11)); + getNicknameLabel().setFont(new Font("Dialog", Font.PLAIN, 11)); + setAvailable(false); + setStatusText(""); + } + + sideIcon.setIcon(null); + setAvailable(false); + return; + } + + StatusItem statusItem = SparkManager.getWorkspace().getStatusBar().getItemFromPresence(presence); + if (statusItem != null) { + setIcon(statusItem.getIcon()); + } + else { + setIcon(statusIcon); + } + if (status != null) { + setStatus(status); + } + + if (status != null && status.indexOf("phone") != -1) { + statusIcon = SparkRes.getImageIcon(SparkRes.ON_PHONE_IMAGE); + setIcon(statusIcon); + } + + // Always change nickname label to black. + getNicknameLabel().setForeground((Color)UIManager.get("ContactItemNickname.foreground")); + + + if (isAvailable) { + getNicknameLabel().setFont(new Font("Dialog", Font.PLAIN, 11)); + if ("Online".equals(status) || "Available".equalsIgnoreCase(status)) { + setStatusText(""); + } + else { + setStatusText(status); + } + + } + else if (presence != null) { + getNicknameLabel().setFont(new Font("Dialog", Font.ITALIC, 11)); + if (status != null) { + setStatusText(status); + } + } + + setAvailable(true); + } + + public void setHandler(ContactItemHandler handler) { + this.handler = handler; + } + + public ContactItemHandler getHandler() { + return handler; + } + + public void removeHandler() { + this.handler = null; + } + + public void setStatusText(String status) { + setStatus(status); + + if (ModelUtil.hasLength(status)) { + getDescriptionLabel().setText(" - " + status); + } + else { + getDescriptionLabel().setText(""); + } + } + +} diff --git a/src/java/org/jivesoftware/spark/ui/ContactItemHandler.java b/src/java/org/jivesoftware/spark/ui/ContactItemHandler.java new file mode 100644 index 00000000..2c7bf2ec --- /dev/null +++ b/src/java/org/jivesoftware/spark/ui/ContactItemHandler.java @@ -0,0 +1,34 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.ui; + +import org.jivesoftware.smack.packet.Presence; + +/** + * The ContactItemHandler allows users to customize the actions that take place within + * a ContactItem within a users presence changes or the item is double clicked. + */ +public interface ContactItemHandler { + + /** + * The users presence has been changed. + * + * @param presence the users new presence. + */ + boolean handlePresence(Presence presence); + + /** + * The ContactItem has been double-clicked by the user. + * + * @return true if you wish to handle the double-click event. + */ + boolean handleDoubleClick(); +} diff --git a/src/java/org/jivesoftware/spark/ui/ContactList.java b/src/java/org/jivesoftware/spark/ui/ContactList.java new file mode 100644 index 00000000..96339a01 --- /dev/null +++ b/src/java/org/jivesoftware/spark/ui/ContactList.java @@ -0,0 +1,2048 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.ui; + +import org.jivesoftware.MainWindowListener; +import org.jivesoftware.Spark; +import org.jivesoftware.resource.SparkRes; +import org.jivesoftware.smack.ConnectionListener; +import org.jivesoftware.smack.PacketListener; +import org.jivesoftware.smack.Roster; +import org.jivesoftware.smack.RosterEntry; +import org.jivesoftware.smack.RosterGroup; +import org.jivesoftware.smack.RosterListener; +import org.jivesoftware.smack.XMPPConnection; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.filter.PacketFilter; +import org.jivesoftware.smack.filter.PacketTypeFilter; +import org.jivesoftware.smack.packet.Message; +import org.jivesoftware.smack.packet.Packet; +import org.jivesoftware.smack.packet.Presence; +import org.jivesoftware.smack.packet.RosterPacket; +import org.jivesoftware.smack.packet.StreamError; +import org.jivesoftware.smack.util.StringUtils; +import org.jivesoftware.smackx.SharedGroupManager; +import org.jivesoftware.smackx.packet.LastActivity; +import org.jivesoftware.spark.ChatManager; +import org.jivesoftware.spark.SparkManager; +import org.jivesoftware.spark.Workspace; +import org.jivesoftware.spark.component.InputDialog; +import org.jivesoftware.spark.component.MessageDialog; +import org.jivesoftware.spark.component.RolloverButton; +import org.jivesoftware.spark.component.VerticalFlowLayout; +import org.jivesoftware.spark.component.WrappedLabel; +import org.jivesoftware.spark.plugin.ContextMenuListener; +import org.jivesoftware.spark.plugin.Plugin; +import org.jivesoftware.spark.ui.rooms.ChatRoomImpl; +import org.jivesoftware.spark.ui.status.StatusBar; +import org.jivesoftware.spark.util.ModelUtil; +import org.jivesoftware.spark.util.ResourceUtils; +import org.jivesoftware.spark.util.SwingWorker; +import org.jivesoftware.spark.util.log.Log; +import org.jivesoftware.sparkimpl.profile.VCardManager; +import org.jivesoftware.sparkimpl.settings.local.LocalPreferences; +import org.jivesoftware.sparkimpl.settings.local.SettingsManager; + +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.BorderFactory; +import javax.swing.JButton; +import javax.swing.JCheckBoxMenuItem; +import javax.swing.JComponent; +import javax.swing.JDialog; +import javax.swing.JEditorPane; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JMenu; +import javax.swing.JMenuItem; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JPopupMenu; +import javax.swing.JScrollPane; +import javax.swing.JTextField; +import javax.swing.JToolBar; +import javax.swing.KeyStroke; +import javax.swing.SwingUtilities; +import javax.swing.UIManager; + +import java.awt.BorderLayout; +import java.awt.CardLayout; +import java.awt.Color; +import java.awt.Component; +import java.awt.Font; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; +import java.awt.event.MouseEvent; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.util.StringTokenizer; +import java.util.Timer; +import java.util.TimerTask; + +public final class ContactList extends JPanel implements ActionListener, ContactGroupListener, Plugin, RosterListener, ConnectionListener { + private JPanel mainPanel = new JPanel(); + private JScrollPane treeScroller; + private final List groupList = new ArrayList(); + private final RolloverButton addingGroupButton; + + private ContactItem activeItem; + private ContactGroup activeGroup; + private ContactGroup unfiledGroup = new ContactGroup("Unfiled"); + + + // Create Menus + private JMenuItem addContactMenu; + private JMenuItem addContactGroupMenu; + private JMenuItem removeContactFromGroupMenu; + private JMenuItem chatMenu; + private JMenuItem renameMenu; + + private ContactGroup offlineGroup; + final JCheckBoxMenuItem showHideMenu = new JCheckBoxMenuItem(); + + private List sharedGroups = new ArrayList(); + + private final List contextListeners = new ArrayList(); + + private List initialPresences = new ArrayList(); + private final Timer presenceTimer = new Timer(); + private final List dndListeners = new ArrayList(); + private final List contactListListeners = new ArrayList(); + private Properties props; + private File propertiesFile; + + private LocalPreferences localPreferences; + + + final private static String ROSTER_PANEL = "ROSTER_PANEL"; + final private static String RETRY_PANEL = "RETRY_PANEL"; + + + // Command Bar + private RolloverButton viewOnline; + + + private RetryPanel retryPanel; + private RetryPanel.ReconnectListener reconnectListener; + + public ContactList() { + // Load Local Preferences + localPreferences = SettingsManager.getLocalPreferences(); + + offlineGroup = new ContactGroup("Offline Group"); + + JToolBar toolbar = new JToolBar(); + toolbar.setFloatable(false); + addContactMenu = new JMenuItem("Add Contact", SparkRes.getImageIcon(SparkRes.USER1_ADD_16x16)); + addContactGroupMenu = new JMenuItem("Add Contact Group", SparkRes.getImageIcon(SparkRes.SMALL_ADD_IMAGE)); + + removeContactFromGroupMenu = new JMenuItem("Remove from Group", SparkRes.getImageIcon(SparkRes.SMALL_DELETE)); + chatMenu = new JMenuItem("Start a Chat", SparkRes.getImageIcon(SparkRes.SMALL_MESSAGE_IMAGE)); + renameMenu = new JMenuItem("Rename", SparkRes.getImageIcon(SparkRes.DESKTOP_IMAGE)); + + addContactMenu.addActionListener(this); + removeContactFromGroupMenu.addActionListener(this); + chatMenu.addActionListener(this); + renameMenu.addActionListener(this); + + + setLayout(new CardLayout()); + + addingGroupButton = new RolloverButton(SparkRes.getImageIcon(SparkRes.ADD_CONTACT_IMAGE)); + + RolloverButton groupChatButton = new RolloverButton(SparkRes.getImageIcon(SparkRes.JOIN_GROUPCHAT_IMAGE)); + toolbar.add(addingGroupButton); + toolbar.add(groupChatButton); + + addingGroupButton.addActionListener(this); + + mainPanel.setLayout(new VerticalFlowLayout(VerticalFlowLayout.TOP, 0, 0, true, false)); + mainPanel.setBackground((Color)UIManager.get("List.background")); + treeScroller = new JScrollPane(mainPanel); + treeScroller.setBorder(BorderFactory.createEmptyBorder()); + treeScroller.getVerticalScrollBar().setBlockIncrement(50); + treeScroller.getVerticalScrollBar().setUnitIncrement(20); + + add(treeScroller, ROSTER_PANEL); + + retryPanel = new RetryPanel(); + add(retryPanel, RETRY_PANEL); + + // Load Properties file + props = new Properties(); + // Save to properties file. + propertiesFile = new File(new File(Spark.getUserHome(), "Spark"), "groups.properties"); + try { + props.load(new FileInputStream(propertiesFile)); + } + catch (IOException e) { + // File does not exist. + } + + // Add ActionListener(s) to menus + addContactGroup(offlineGroup); + + showHideMenu.setSelected(false); + + // Add KeyMappings + getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("control N"), "searchContacts"); + getActionMap().put("searchContacts", new AbstractAction("searchContacts") { + public void actionPerformed(ActionEvent evt) { + searchContacts(""); + } + }); + + // Save state on shutdown. + SparkManager.getMainWindow().addMainWindowListener(new MainWindowListener() { + public void shutdown() { + saveState(); + } + + public void mainWindowActivated() { + + } + + public void mainWindowDeactivated() { + + } + }); + + SparkManager.getConnection().addConnectionListener(this); + + // Get command panel and add View Online/Offline, Add Contact + StatusBar statusBar = SparkManager.getWorkspace().getStatusBar(); + JPanel commandPanel = statusBar.getCommandPanel(); + + viewOnline = new RolloverButton(SparkRes.getImageIcon(SparkRes.VIEW_IMAGE)); + commandPanel.add(viewOnline); + viewOnline.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + showEmptyGroups(!showHideMenu.isSelected()); + } + }); + + final RolloverButton addContactButton = new RolloverButton(SparkRes.getImageIcon(SparkRes.SMALL_ADD_IMAGE)); + commandPanel.add(addContactButton); + addContactButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + new RosterDialog().showRosterDialog(); + } + }); + + + } + + /** + * Updates the users presence. + * + * @param presence the user to update. + */ + private void updateUserPresence(Presence presence) { + if (presence == null) { + return; + } + + Roster roster = SparkManager.getConnection().getRoster(); + + final String bareJID = StringUtils.parseBareAddress(presence.getFrom()); + + RosterEntry entry = roster.getEntry(bareJID); + boolean isPending = entry != null && (entry.getType() == RosterPacket.ItemType.NONE || entry.getType() == RosterPacket.ItemType.FROM) + && RosterPacket.ItemStatus.SUBSCRIPTION_PENDING == entry.getStatus(); + + // If online, check to see if they are in the offline group. + // If so, remove from offline group and add to all groups they + // belong to. + if (presence.getType() == Presence.Type.AVAILABLE && offlineGroup.getContactItemByJID(bareJID) != null) { + changeOfflineToOnline(bareJID, entry, presence); + } + else if (presence.getFrom().indexOf("workgroup.") != -1) { + changeOfflineToOnline(bareJID, entry, presence); + } + + // If online, but not in offline group, update presence. + else if (presence.getType() == Presence.Type.AVAILABLE) { + final Iterator groupIterator = groupList.iterator(); + while (groupIterator.hasNext()) { + ContactGroup group = (ContactGroup)groupIterator.next(); + ContactItem item = group.getContactItemByJID(bareJID); + if (item != null) { + item.setPresence(presence); + group.fireContactGroupUpdated(); + } + } + } + + // If not available, move to offline group. + else if (presence.getType() == Presence.Type.UNAVAILABLE && !isPending) { + final Iterator groupIterator = new ArrayList(groupList).iterator(); + while (groupIterator.hasNext()) { + ContactGroup group = (ContactGroup)groupIterator.next(); + ContactItem item = group.getContactItemByJID(bareJID); + if (item != null) { + item.setPresence(null); + + // Check for ContactItemHandler. + if (item.getHandler() == null || !item.getHandler().handlePresence(presence)) { + group.removeContactItem(item); + checkGroup(group); + + if (offlineGroup.getContactItemByJID(item.getFullJID()) == null) { + offlineGroup.addContactItem(item); + offlineGroup.fireContactGroupUpdated(); + } + } + else { + group.fireContactGroupUpdated(); + } + } + } + } + + } + + private void moveToOfflineGroup(String bareJID) { + final Iterator groupIterator = new ArrayList(groupList).iterator(); + while (groupIterator.hasNext()) { + ContactGroup group = (ContactGroup)groupIterator.next(); + ContactItem item = group.getContactItemByJID(bareJID); + if (item != null) { + item.setPresence(null); + + // Check for ContactItemHandler. + group.removeContactItem(item); + checkGroup(group); + + if (offlineGroup.getContactItemByJID(item.getFullJID()) == null) { + offlineGroup.addContactItem(item); + offlineGroup.fireContactGroupUpdated(); + } + } + } + } + + private void changeOfflineToOnline(String bareJID, RosterEntry entry, Presence presence) { + // Move out of offline group. Add to all groups. + ContactItem offlineItem = offlineGroup.getContactItemByJID(bareJID); + if (offlineItem == null) { + return; + } + + offlineGroup.removeContactItem(offlineItem); + + // Add To all groups it belongs to. + Iterator groups = entry.getGroups(); + boolean isFiled = groups.hasNext(); + + while (groups.hasNext()) { + RosterGroup rosterGroup = (RosterGroup)groups.next(); + ContactGroup contactGroup = getContactGroup(rosterGroup.getName()); + if (contactGroup != null) { + String name = entry.getName(); + if (name == null) { + name = entry.getUser(); + } + if (contactGroup.getContactItemByJID(entry.getUser()) == null) { + ContactItem contactItem = new ContactItem(name, entry.getUser()); + contactItem.setPresence(presence); + contactItem.setAvailable(true); + + contactGroup.addContactItem(contactItem); + + toggleGroupVisibility(contactGroup.getGroupName(), true); + + contactGroup.fireContactGroupUpdated(); + } + else if (contactGroup.getContactItemByJID(entry.getUser()) != null) { + // If the user is in their, but without a nice presence. + ContactItem contactItem = contactGroup.getContactItemByJID(entry.getUser()); + contactItem.setPresence(presence); + contactItem.setAvailable(true); + toggleGroupVisibility(contactGroup.getGroupName(), true); + + contactGroup.fireContactGroupUpdated(); + } + } + } + + if (!isFiled) { + String name = entry.getName(); + if (name == null) { + name = entry.getUser(); + } + ContactItem contactItem = new ContactItem(name, entry.getUser()); + unfiledGroup.addContactItem(contactItem); + contactItem.setPresence(presence); + contactItem.setAvailable(true); + + unfiledGroup.setVisible(true); + unfiledGroup.fireContactGroupUpdated(); + } + } + + + private void buildContactList() { + XMPPConnection con = SparkManager.getConnection(); + final Roster roster = con.getRoster(); + + + roster.addRosterListener(this); + + final Iterator rosterGroups = roster.getGroups(); + while (rosterGroups.hasNext()) { + RosterGroup group = (RosterGroup)rosterGroups.next(); + ContactGroup contactGroup = new ContactGroup(group.getName()); + addContactGroup(contactGroup); + + Iterator entries = group.getEntries(); + while (entries.hasNext()) { + RosterEntry entry = (RosterEntry)entries.next(); + String name = entry.getName(); + if (name == null) { + name = entry.getUser(); + } + + ContactItem contactItem = new ContactItem(name, entry.getUser()); + contactItem.setPresence(null); + if ((entry.getType() == RosterPacket.ItemType.NONE || entry.getType() == RosterPacket.ItemType.FROM) + && RosterPacket.ItemStatus.SUBSCRIPTION_PENDING == entry.getStatus()) { + // Add to contact group. + contactGroup.addContactItem(contactItem); + contactGroup.setVisible(true); + } + else { + if (offlineGroup.getContactItemByJID(entry.getUser()) == null) { + offlineGroup.addContactItem(contactItem); + } + } + } + + } + + // Add Unfiled Group + addContactGroup(unfiledGroup); + final Iterator unfiledEntries = roster.getUnfiledEntries(); + while (unfiledEntries.hasNext()) { + RosterEntry entry = (RosterEntry)unfiledEntries.next(); + String name = entry.getName(); + if (name == null) { + name = entry.getUser(); + } + + ContactItem contactItem = new ContactItem(name, entry.getUser()); + offlineGroup.addContactItem(contactItem); + } + unfiledGroup.setVisible(false); + } + + /** + * Called when NEW entries are added. + * + * @param addresses the addressss added. + */ + public void entriesAdded(final Collection addresses) { + SwingUtilities.invokeLater(new Runnable() { + public void run() { + Roster roster = SparkManager.getConnection().getRoster(); + + Iterator jids = addresses.iterator(); + while (jids.hasNext()) { + String jid = (String)jids.next(); + RosterEntry entry = roster.getEntry(jid); + addUser(entry); + } + } + }); + } + + private void addUser(RosterEntry entry) { + String name = entry.getName(); + if (!ModelUtil.hasLength(name)) { + name = entry.getUser(); + } + + String nickname = entry.getName(); + if (!ModelUtil.hasLength(nickname)) { + nickname = entry.getUser(); + } + + ContactItem newContactItem = new ContactItem(nickname, entry.getUser()); + + // Update users icon + Presence presence = SparkManager.getConnection().getRoster().getPresence(entry.getUser()); + newContactItem.setPresence(presence); + + + if (entry != null && (entry.getType() == RosterPacket.ItemType.NONE || entry.getType() == RosterPacket.ItemType.FROM)) { + // Ignore, since the new user is pending to be added. + final Iterator groups = entry.getGroups(); + while (groups.hasNext()) { + final RosterGroup group = (RosterGroup)groups.next(); + ContactGroup contactGroup = getContactGroup(group.getName()); + if (contactGroup == null) { + contactGroup = new ContactGroup(group.getName()); + addContactGroup(contactGroup); + } + contactGroup.addContactItem(newContactItem); + } + return; + } + else { + offlineGroup.addContactItem(newContactItem); + } + + + if (presence != null) { + updateUserPresence(presence); + } + } + + private ContactItem addNewContactItem(String groupName, String nickname, String jid) { + // Create User Node + ContactGroup contactGroup = getContactGroup(groupName); + ContactItem contactItem = new ContactItem(nickname, jid); + contactGroup.addContactItem(contactItem); + contactGroup.setVisible(true); + + validateTree(); + return contactItem; + } + + + /** + * Handle when the Roster changes based on subscription notices. + * + * @param addresses + */ + public void entriesUpdated(final Collection addresses) { + SwingUtilities.invokeLater(new Runnable() { + public void run() { + Roster roster = SparkManager.getConnection().getRoster(); + + Iterator jids = addresses.iterator(); + while (jids.hasNext()) { + String jid = (String)jids.next(); + RosterEntry rosterEntry = roster.getEntry(jid); + if (rosterEntry != null) { + // Check for new Roster Groups and add them if they do not exist. + Iterator rosterGroups = rosterEntry.getGroups(); + + boolean isUnfiled = true; + while (rosterGroups.hasNext()) { + isUnfiled = false; + RosterGroup group = (RosterGroup)rosterGroups.next(); + + // Handle if this is a new Entry in a new Group. + if (getContactGroup(group.getName()) == null) { + // Create group. + ContactGroup contactGroup = new ContactGroup(group.getName()); + contactGroup.setVisible(false); + addContactGroup(contactGroup); + contactGroup = getContactGroup(group.getName()); + String name = rosterEntry.getName(); + if (name == null) { + name = rosterEntry.getUser(); + } + + ContactItem contactItem = new ContactItem(name, rosterEntry.getUser()); + contactGroup.addContactItem(contactItem); + Presence presence = roster.getPresence(jid); + contactItem.setPresence(presence); + if (presence != null) { + contactGroup.setVisible(true); + } + } + else { + ContactGroup contactGroup = getContactGroup(group.getName()); + ContactItem item = contactGroup.getContactItemByJID(jid); + + // Check to see if this entry is new to a pre-existing group. + if (item == null) { + String name = rosterEntry.getName(); + if (name == null) { + name = rosterEntry.getUser(); + } + item = new ContactItem(name, rosterEntry.getUser()); + Presence presence = roster.getPresence(jid); + item.setPresence(presence); + if (presence != null) { + contactGroup.addContactItem(item); + contactGroup.fireContactGroupUpdated(); + } + } + + // If not, just update their presence. + else { + Presence presence = roster.getPresence(jid); + item.setPresence(presence); + updateUserPresence(presence); + contactGroup.fireContactGroupUpdated(); + } + } + } + + // Now check to see if groups have been modified or removed. This is used + // to check if Contact Groups have been renamed or removed. + Set groupSet = new HashSet(); + jids = addresses.iterator(); + while (jids.hasNext()) { + jid = (String)jids.next(); + rosterEntry = roster.getEntry(jid); + Iterator groups = rosterEntry.getGroups(); + while (groups.hasNext()) { + RosterGroup g = (RosterGroup)groups.next(); + groupSet.add(g.getName()); + } + + for (ContactGroup group : new ArrayList(getContactGroups())) { + if (group.getContactItemByJID(jid) != null && group != unfiledGroup && group != offlineGroup) { + if (!groupSet.contains(group.getGroupName())) { + removeContactGroup(group); + } + } + + } + } + + + if (!isUnfiled) { + return; + } + + ContactItem unfiledItem = unfiledGroup.getContactItemByJID(jid); + if (unfiledItem != null) { + + } + else { + ContactItem offlineItem = offlineGroup.getContactItemByJID(jid); + if (offlineItem != null) { + if ((rosterEntry.getType() == RosterPacket.ItemType.NONE || rosterEntry.getType() == RosterPacket.ItemType.FROM) + && RosterPacket.ItemStatus.SUBSCRIPTION_PENDING == rosterEntry.getStatus()) { + // Remove from offlineItem and add to unfiledItem. + offlineGroup.removeContactItem(offlineItem); + unfiledGroup.addContactItem(offlineItem); + unfiledGroup.fireContactGroupUpdated(); + unfiledGroup.setVisible(true); + } + } + } + } + } + } + }); + + } + + /** + * Called when users are removed from the roster. + * + * @param addresses the addresses removed from the roster. + */ + public void entriesDeleted(final Collection addresses) { + SwingUtilities.invokeLater(new Runnable() { + public void run() { + Iterator jids = addresses.iterator(); + while (jids.hasNext()) { + String jid = (String)jids.next(); + removeContactItem(jid); + } + } + }); + + } + + public void presenceChanged(final String user) { + + } + + /** + * Retrieve the ContactItem by it's jid. + * + * @param jid the JID of the user. + * @return the "first" contact item found. + */ + public ContactItem getContactItemByJID(String jid) { + Iterator contactGroups = getContactGroups().iterator(); + while (contactGroups.hasNext()) { + ContactGroup contactGroup = (ContactGroup)contactGroups.next(); + ContactItem item = contactGroup.getContactItemByJID(StringUtils.parseBareAddress(jid)); + if (item != null) { + return item; + } + } + return null; + } + + /** + * Retrieve the ContactItem by their nickname. + * + * @param nickname the users nickname in the contact list. + * @return the "first" contact item found. + */ + public ContactItem getContactItemByNickname(String nickname) { + Iterator contactGroups = getContactGroups().iterator(); + while (contactGroups.hasNext()) { + ContactGroup contactGroup = (ContactGroup)contactGroups.next(); + ContactItem item = contactGroup.getContactItemByNickname(nickname); + if (item != null) { + return item; + } + } + return null; + } + + + private void addContactGroup(ContactGroup contactGroup) { + String groupName = contactGroup.getGroupName(); + StringTokenizer tkn = new StringTokenizer(groupName, "::"); + + ContactGroup rootGroup = null; + ContactGroup lastGroup = null; + StringBuffer buf = new StringBuffer(); + + boolean groupAdded = false; + while (tkn.hasMoreTokens()) { + String group = tkn.nextToken(); + buf.append(group); + if (tkn.hasMoreTokens()) { + buf.append("::"); + } + + String name = buf.toString(); + if (name.endsWith("::")) { + name = name.substring(0, name.length() - 2); + } + + ContactGroup newContactGroup = null; + if (contactGroup == offlineGroup) { + newContactGroup = offlineGroup; + } + else if (contactGroup == unfiledGroup) { + newContactGroup = unfiledGroup; + } + else { + newContactGroup = getContactGroup(name); + } + + if (newContactGroup == null) { + newContactGroup = new ContactGroup(group); + + String realGroupName = buf.toString(); + if (realGroupName.endsWith("::")) { + realGroupName = realGroupName.substring(0, realGroupName.length() - 2); + } + + newContactGroup.setGroupName(realGroupName); + } + else { + if (newContactGroup != offlineGroup && newContactGroup != unfiledGroup) { + rootGroup = newContactGroup; + continue; + } + } + + + if (lastGroup != null) { + lastGroup.addContactGroup(newContactGroup); + groupList.add(newContactGroup); + } + else if (rootGroup != null) { + rootGroup.addContactGroup(newContactGroup); + groupList.add(newContactGroup); + } + else { + rootGroup = newContactGroup; + } + + lastGroup = newContactGroup; + + + newContactGroup.addContactGroupListener(this); + + if (sharedGroups != null) { + boolean isSharedGroup = sharedGroups.contains(newContactGroup.getGroupName()); + newContactGroup.setSharedGroup(isSharedGroup); + } + + fireContactGroupAdded(newContactGroup); + + // Check state + + String prop = props.getProperty(newContactGroup.getGroupName()); + if (prop != null) { + boolean isCollapsed = Boolean.valueOf(prop).booleanValue(); + newContactGroup.setCollapsed(isCollapsed); + } + + + groupAdded = true; + } + + if (!groupAdded) { + return; + } + + + final List tempList = new ArrayList(); + final Component[] comps = mainPanel.getComponents(); + for (int i = 0; i < comps.length; i++) { + Component c = comps[i]; + if (comps[i] instanceof ContactGroup && c != offlineGroup) { + tempList.add(c); + } + } + tempList.add(rootGroup); + + + groupList.add(rootGroup); + + Collections.sort(tempList, groupComparator); + + int loc = tempList.indexOf(rootGroup); + + + try { + if (rootGroup == offlineGroup) { + mainPanel.add(rootGroup, tempList.size() - 1); + } + else { + mainPanel.add(rootGroup, loc); + } + } + catch (Exception e) { + System.out.println("Unable to add Contact Group" + rootGroup.getGroupName()); + Log.error(e); + } + + } + + private void removeContactGroup(ContactGroup contactGroup) { + contactGroup.removeContactGroupListener(this); + groupList.remove(contactGroup); + mainPanel.remove(contactGroup); + + ContactGroup parent = getParentGroup(contactGroup.getGroupName()); + if (parent != null) { + parent.removeContactGroup(contactGroup); + } + + treeScroller.validate(); + mainPanel.invalidate(); + mainPanel.repaint(); + + fireContactGroupRemoved(contactGroup); + } + + + public ContactGroup getContactGroup(String groupName) { + ContactGroup grp = null; + + for (ContactGroup contactGroup : groupList) { + if (contactGroup.getGroupName().equals(groupName)) { + grp = contactGroup; + break; + } + else { + grp = getSubContactGroup(contactGroup, groupName); + if (grp != null) { + break; + } + } + } + + return grp; + } + + public ContactGroup getParentGroup(String groupName) { + // Check if there is even a parent group + if (!groupName.contains("::")) { + return null; + } + + final ContactGroup group = getContactGroup(groupName); + if (group == null) { + return null; + } + + // Otherwise, find parent + int index = groupName.lastIndexOf("::"); + String parentGroupName = groupName.substring(0, index); + return getContactGroup(parentGroupName); + } + + private ContactGroup getSubContactGroup(ContactGroup group, String groupName) { + final Iterator contactGroups = group.getContactGroups().iterator(); + ContactGroup grp = null; + + while (contactGroups.hasNext()) { + ContactGroup contactGroup = (ContactGroup)contactGroups.next(); + if (contactGroup.getGroupName().equals(groupName)) { + grp = contactGroup; + break; + } + else if (contactGroup.getContactGroups().size() > 0) { + grp = getSubContactGroup(contactGroup, groupName); + if (grp != null) { + break; + } + } + + } + return grp; + } + + public void toggleGroupVisibility(String groupName, boolean visible) { + StringTokenizer tkn = new StringTokenizer(groupName, "::"); + while (tkn.hasMoreTokens()) { + String group = tkn.nextToken(); + ContactGroup contactGroup = getContactGroup(group); + if (contactGroup != null) { + contactGroup.setVisible(visible); + } + } + + ContactGroup group = getContactGroup(groupName); + if (group != null) { + group.setVisible(true); + } + } + + public void actionPerformed(ActionEvent e) { + if (e.getSource() == addingGroupButton) { + new RosterDialog().showRosterDialog(); + } + else if (e.getSource() == chatMenu) { + if (activeItem != null) { + activateChat(activeItem.getContactJID(), activeItem.getNickname()); + } + } + else if (e.getSource() == addContactMenu) { + RosterDialog rosterDialog = new RosterDialog(); + if (activeGroup != null) { + rosterDialog.setDefaultGroup(activeGroup); + } + rosterDialog.showRosterDialog(); + } + else if (e.getSource() == removeContactFromGroupMenu) { + if (activeItem != null) { + removeContactFromGroup(activeItem); + } + } + else if (e.getSource() == renameMenu) { + if (activeItem == null) { + return; + } + + String oldNickname = activeItem.getNickname(); + String newNickname = JOptionPane.showInputDialog(this, "Rename to:", oldNickname); + if (ModelUtil.hasLength(newNickname)) { + String address = activeItem.getFullJID(); + ContactGroup contactGroup = getContactGroup(activeItem.getGroupName()); + ContactItem contactItem = contactGroup.getContactItemByNickname(activeItem.getNickname()); + contactItem.setNickname(newNickname); + + final Roster roster = SparkManager.getConnection().getRoster(); + RosterEntry entry = roster.getEntry(address); + entry.setName(newNickname); + + + final Iterator contactGroups = groupList.iterator(); + String user = StringUtils.parseBareAddress(address); + while (contactGroups.hasNext()) { + ContactGroup cg = (ContactGroup)contactGroups.next(); + ContactItem ci = cg.getContactItemByJID(user); + if (ci != null) { + ci.setNickname(newNickname); + } + } + + } + } + } + + + /** + * Removes a contact item from the group. + * + * @param item the ContactItem to remove. + */ + private void removeContactFromGroup(ContactItem item) { + String groupName = item.getGroupName(); + ContactGroup contactGroup = getContactGroup(groupName); + Roster roster = SparkManager.getConnection().getRoster(); + RosterEntry entry = roster.getEntry(item.getFullJID()); + if (entry != null && contactGroup != offlineGroup) { + try { + RosterGroup rosterGroup = roster.getGroup(groupName); + if (rosterGroup != null) { + RosterEntry rosterEntry = rosterGroup.getEntry(entry.getUser()); + if (rosterEntry != null) { + rosterGroup.removeEntry(rosterEntry); + } + } + contactGroup.removeContactItem(contactGroup.getContactItemByJID(item.getFullJID())); + checkGroup(contactGroup); + } + catch (Exception e) { + Log.error("Error removing user from contact list.", e); + } + } + } + + private void removeContactFromRoster(ContactItem item) { + Roster roster = SparkManager.getConnection().getRoster(); + RosterEntry entry = roster.getEntry(item.getFullJID()); + if (entry != null) { + try { + roster.removeEntry(entry); + } + catch (XMPPException e) { + Log.warning("Unable to remove roster entry.", e); + } + } + } + + private void removeContactItem(String jid) { + Iterator groups = new ArrayList(getContactGroups()).iterator(); + while (groups.hasNext()) { + ContactGroup group = (ContactGroup)groups.next(); + ContactItem item = group.getContactItemByJID(jid); + if (item != null) { + group.removeContactItem(item); + checkGroup(group); + } + } + } + + /** + * Activate a chat room with the selected user. + */ + private void activateChat(final String userJID, final String nickname) { + if (!ModelUtil.hasLength(userJID)) { + return; + } + + SwingWorker worker = new SwingWorker() { + final ChatManager chatManager = SparkManager.getChatManager(); + ChatRoom chatRoom; + + public Object construct() { + try { + Thread.sleep(10); + } + catch (InterruptedException e) { + Log.error("Error in activate chat.", e); + } + + ChatContainer chatRooms = chatManager.getChatContainer(); + + try { + chatRoom = chatRooms.getChatRoom(userJID); + } + catch (ChatRoomNotFoundException e) { + + } + return chatRoom; + } + + public void finished() { + if (chatRoom == null) { + chatRoom = new ChatRoomImpl(userJID, nickname, nickname); + chatManager.getChatContainer().addChatRoom(chatRoom); + } + chatManager.getChatContainer().activateChatRoom(chatRoom); + } + }; + + worker.start(); + + } + + + public void contactItemClicked(ContactItem item) { + activeItem = item; + + clearSelectionList(item); + } + + public void contactItemDoubleClicked(ContactItem item) { + activeItem = item; + + boolean handled = false; + if (item.getHandler() != null) { + handled = item.getHandler().handleDoubleClick(); + } + + if (!handled) { + activateChat(item.getContactJID(), item.getNickname()); + } + + clearSelectionList(item); + } + + public void contactGroupPopup(MouseEvent e, final ContactGroup group) { + // Do nothing with offline group + if (group == offlineGroup || group == unfiledGroup) { + return; + } + + + final JPopupMenu popup = new JPopupMenu(); + if (!group.isSharedGroup()) { + popup.add(addContactMenu); + } + popup.add(addContactGroupMenu); + popup.addSeparator(); + + fireContextMenuListenerPopup(popup, group); + + JMenuItem delete = new JMenuItem("Delete"); + JMenuItem rename = new JMenuItem("Rename"); + if (!group.isSharedGroup()) { + popup.addSeparator(); + popup.add(delete); + popup.add(rename); + } + + delete.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + int ok = JOptionPane.showConfirmDialog(group, "Are you sure you want to delete \"" + group.getGroupName() + "\"", "Delete Confirmation", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE); + if (ok == JOptionPane.YES_OPTION) { + String groupName = group.getGroupName(); + Roster roster = SparkManager.getConnection().getRoster(); + + RosterGroup rosterGroup = roster.getGroup(groupName); + if (rosterGroup != null) { + Iterator entries = rosterGroup.getEntries(); + while (entries.hasNext()) { + RosterEntry entry = (RosterEntry)entries.next(); + try { + rosterGroup.removeEntry(entry); + } + catch (XMPPException e1) { + Log.error("Error removing entry", e1); + } + } + } + + // Remove from UI + removeContactGroup(group); + invalidate(); + repaint(); + } + + } + }); + + + rename.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + String newName = JOptionPane.showInputDialog(group, "Rename Roster Group", "Rename to:", JOptionPane.QUESTION_MESSAGE); + if (!ModelUtil.hasLength(newName)) { + return; + } + String groupName = group.getGroupName(); + Roster roster = SparkManager.getConnection().getRoster(); + + RosterGroup rosterGroup = roster.getGroup(groupName); + if (rosterGroup != null) { + removeContactGroup(group); + rosterGroup.setName(newName); + } + + } + }); + + // popup.add(inviteFirstAcceptor); + + popup.show(group, e.getX(), e.getY()); + activeGroup = group; + } + + /** + * Shows popup for right-clicking of ContactItem. + * + * @param e the MouseEvent + * @param item the ContactItem + */ + public void showPopup(MouseEvent e, final ContactItem item) { + if (item.getFullJID() == null) { + return; + } + + activeItem = item; + + final JPopupMenu popup = new JPopupMenu(); + + // Add Start Chat Menu + popup.add(chatMenu); + + // Add Send File Action + Action sendAction = new AbstractAction() { + public void actionPerformed(ActionEvent actionEvent) { + SparkManager.getTransferManager().sendFileTo(item); + } + }; + + sendAction.putValue(Action.SMALL_ICON, SparkRes.getImageIcon(SparkRes.DOCUMENT_16x16)); + sendAction.putValue(Action.NAME, "Send a File"); + + if (item.getPresence() != null) { + popup.add(sendAction); + } + + popup.addSeparator(); + + + String groupName = item.getGroupName(); + ContactGroup contactGroup = getContactGroup(groupName); + + // Only show "Remove Contact From Group" if the user belongs to more than one group. + if (!contactGroup.isSharedGroup() && !contactGroup.isOfflineGroup() && contactGroup != unfiledGroup) { + Roster roster = SparkManager.getConnection().getRoster(); + RosterEntry entry = roster.getEntry(item.getFullJID()); + if (entry != null) { + int groupCount = 0; + Iterator groups = entry.getGroups(); + while (groups.hasNext()) { + groups.next(); + groupCount++; + } + + if (groupCount > 1) { + popup.add(removeContactFromGroupMenu); + } + + } + } + + // Define remove entry action + Action removeAction = new AbstractAction() { + public void actionPerformed(ActionEvent e) { + removeContactFromRoster(item); + } + }; + + removeAction.putValue(Action.NAME, "Remove from Roster"); + removeAction.putValue(Action.SMALL_ICON, SparkRes.getImageIcon(SparkRes.SMALL_CIRCLE_DELETE)); + + // Check if user is in shared group. + boolean isInSharedGroup = false; + Iterator contactGroups = new ArrayList(getContactGroups()).iterator(); + while (contactGroups.hasNext()) { + ContactGroup cGroup = (ContactGroup)contactGroups.next(); + if (cGroup.isSharedGroup()) { + ContactItem it = cGroup.getContactItemByJID(item.getFullJID()); + if (it != null) { + isInSharedGroup = true; + } + } + } + + + if (!contactGroup.isSharedGroup() && !isInSharedGroup) { + popup.add(removeAction); + } + + popup.add(renameMenu); + + + Action viewProfile = new AbstractAction() { + public void actionPerformed(ActionEvent e) { + VCardManager vcardSupport = SparkManager.getVCardManager(); + String jid = item.getFullJID(); + vcardSupport.viewProfile(jid, SparkManager.getWorkspace()); + } + }; + viewProfile.putValue(Action.SMALL_ICON, SparkRes.getImageIcon(SparkRes.SMALL_PROFILE_IMAGE)); + viewProfile.putValue(Action.NAME, "View Profile"); + + popup.add(viewProfile); + + + popup.addSeparator(); + + Action viewPresenceAction = new AbstractAction() { + public void actionPerformed(ActionEvent e) { + JEditorPane pane = new JEditorPane(); + StringBuffer buf = new StringBuffer(); + Collection col = item.getPresenceHistory(); + Iterator iter = col.iterator(); + while (iter.hasNext()) { + String history = (String)iter.next(); + buf.append(history + "\n"); + } + pane.setText(buf.toString()); + MessageDialog.showComponent("Presence History", "History of user activity while online.", + SparkRes.getImageIcon(SparkRes.INFORMATION_IMAGE), new JScrollPane(pane), + getGUI(), 400, 400, false); + + } + }; + + /* + viewPresenceAction.putValue(Action.NAME, "View Presence History"); + viewPresenceAction.putValue(Action.SMALL_ICON, LaRes.getImageIcon(LaRes.VIEW_IMAGE)); + popup.add(viewPresenceAction); + */ + + Action lastActivityAction = new AbstractAction() { + public void actionPerformed(ActionEvent actionEvent) { + try { + LastActivity activity = LastActivity.getLastActivity(SparkManager.getConnection(), item.getFullJID()); + long idleTime = (activity.getIdleTime() * 1000); + String time = ModelUtil.getTimeFromLong(idleTime); + JOptionPane.showMessageDialog(getGUI(), "Idle for " + time, "Last Activity", JOptionPane.INFORMATION_MESSAGE); + } + catch (XMPPException e1) { + } + + } + }; + + lastActivityAction.putValue(Action.NAME, "View Last Activity"); + lastActivityAction.putValue(Action.SMALL_ICON, SparkRes.getImageIcon(SparkRes.SMALL_USER1_STOPWATCH)); + + if (contactGroup == offlineGroup) { + popup.add(lastActivityAction); + } + + Action subscribeAction = new AbstractAction() { + public void actionPerformed(ActionEvent e) { + String jid = item.getFullJID(); + Presence response = new Presence(Presence.Type.SUBSCRIBE); + response.setTo(jid); + + SparkManager.getConnection().sendPacket(response); + } + }; + + subscribeAction.putValue(Action.SMALL_ICON, SparkRes.getImageIcon(SparkRes.SMALL_USER1_INFORMATION)); + subscribeAction.putValue(Action.NAME, "Subscribe To"); + + Roster roster = SparkManager.getConnection().getRoster(); + RosterEntry entry = roster.getEntry(item.getFullJID()); + if (entry != null && entry.getType() == RosterPacket.ItemType.FROM) { + popup.add(subscribeAction); + } + + // Fire Context Menu Listener + fireContextMenuListenerPopup(popup, item); + + ContactGroup group = getContactGroup(item.getGroupName()); + popup.show(group.getList(), e.getX(), e.getY()); + } + + public void showPopup(MouseEvent e, final Collection items) { + ContactGroup group = null; + Iterator contactItems = items.iterator(); + while (contactItems.hasNext()) { + ContactItem item = (ContactItem)contactItems.next(); + group = getContactGroup(item.getGroupName()); + break; + } + + + final JPopupMenu popup = new JPopupMenu(); + final JMenuItem sendMessagesMenu = new JMenuItem("Send a Message...", SparkRes.getImageIcon(SparkRes.SMALL_MESSAGE_IMAGE)); + + + fireContextMenuListenerPopup(popup, items); + + popup.add(sendMessagesMenu); + + sendMessagesMenu.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + sendMessages(items); + } + }); + + popup.show(group.getList(), e.getX(), e.getY()); + } + + private void clearSelectionList(ContactItem item) { + // Check for null. In certain cases the event triggering the model might + // not find the selected object. + if (item == null) { + return; + } + final ContactGroup owner = getContactGroup(item.getGroupName()); + Iterator groups = new ArrayList(groupList).iterator(); + while (groups.hasNext()) { + ContactGroup contactGroup = (ContactGroup)groups.next(); + if (owner != contactGroup) { + contactGroup.clearSelection(); + } + } + } + + + private void sendMessages(Collection items) { + InputDialog dialog = new InputDialog(); + final String messageText = dialog.getInput("Broadcast Message", "Enter message to broadcast to selected users.", SparkRes.getImageIcon(SparkRes.BLANK_IMAGE), SparkManager.getMainWindow()); + if (ModelUtil.hasLength(messageText)) { + Iterator contacts = items.iterator(); + while (contacts.hasNext()) { + ContactItem item = (ContactItem)contacts.next(); + final ContactGroup contactGroup = getContactGroup(item.getGroupName()); + contactGroup.clearSelection(); + if (item.isAvailable()) { + Message mess = new Message(); + mess.setTo(item.getFullJID()); + mess.setBody(messageText); + + SparkManager.getConnection().sendPacket(mess); + } + } + } + + } + + // For plugin use only + + public void initialize() { + setBorder(BorderFactory.createLineBorder(Color.LIGHT_GRAY)); + + // Add Contact List + addContactListToWorkspace(); + + // Hide top toolbar + SparkManager.getMainWindow().getTopToolBar().setVisible(false); + + SwingWorker worker = new SwingWorker() { + public Object construct() { + + // Retrieve shared group list. + try { + sharedGroups = SharedGroupManager.getSharedGroups(SparkManager.getConnection()); + } + catch (XMPPException e) { + Log.error("Unable to contact shared group info.", e); + } + + return "ok"; + } + + public void finished() { + // Build the initial contact list. + buildContactList(); + + boolean show = localPreferences.isEmptyGroupsShown(); + + // Hide all groups initially + showEmptyGroups(show); + + // Add a subscription listener. + addSubscriptionListener(); + + // Load all plugins + SparkManager.getWorkspace().loadPlugins(); + } + }; + + worker.start(); + + + } + + public void addSubscriptionListener() { + // Add subscription listener + PacketFilter packetFilter = new PacketTypeFilter(Presence.class); + PacketListener subscribeListener = new PacketListener() { + public void processPacket(Packet packet) { + final Presence presence = (Presence)packet; + if (presence != null && presence.getType() == Presence.Type.SUBSCRIBE) { + SwingUtilities.invokeLater(new Runnable() { + public void run() { + subscriptionRequest(presence.getFrom()); + } + }); + } + else if (presence != null && presence.getType() == Presence.Type.UNSUBSCRIBE) { + SwingUtilities.invokeLater(new Runnable() { + public void run() { + Roster roster = SparkManager.getConnection().getRoster(); + RosterEntry entry = roster.getEntry(presence.getFrom()); + if (entry != null) { + try { + removeContactItem(presence.getFrom()); + roster.removeEntry(entry); + } + catch (XMPPException e) { + Presence unsub = new Presence(Presence.Type.UNSUBSCRIBED); + unsub.setTo(presence.getFrom()); + SparkManager.getConnection().sendPacket(unsub); + Log.error(e); + } + } + } + }); + + + } + else if (presence != null && presence.getType() == Presence.Type.SUBSCRIBED) { + // Find Contact in Contact List + String jid = StringUtils.parseBareAddress(presence.getFrom()); + ContactItem item = getContactItemByJID(jid); + + // If item is not in the Contact List, add them. + if (item == null) { + final Roster roster = SparkManager.getConnection().getRoster(); + RosterEntry entry = roster.getEntry(jid); + if (entry != null) { + String nickname = entry.getName(); + if (nickname == null) { + nickname = entry.getUser(); + } + item = new ContactItem(nickname, jid); + offlineGroup.addContactItem(item); + offlineGroup.fireContactGroupUpdated(); + } + } + } + else if (presence != null && presence.getType() == Presence.Type.UNSUBSCRIBED) { + SwingUtilities.invokeLater(new Runnable() { + public void run() { + Roster roster = SparkManager.getConnection().getRoster(); + RosterEntry entry = roster.getEntry(presence.getFrom()); + if (entry != null) { + try { + removeContactItem(presence.getFrom()); + roster.removeEntry(entry); + } + catch (XMPPException e) { + Log.error(e); + } + } + String jid = StringUtils.parseBareAddress(presence.getFrom()); + removeContactItem(jid); + } + }); + } + else { + initialPresences.add(presence); + + int numberOfMillisecondsInTheFuture = 500; + + presenceTimer.schedule(new TimerTask() { + public void run() { + SwingUtilities.invokeLater(new Runnable() { + public void run() { + final Iterator users = new ArrayList(initialPresences).iterator(); + while (users.hasNext()) { + Presence userToUpdate = (Presence)users.next(); + initialPresences.remove(userToUpdate); + updateUserPresence(userToUpdate); + + } + } + }); + } + }, numberOfMillisecondsInTheFuture); + } + } + }; + + SparkManager.getConnection().addPacketListener(subscribeListener, packetFilter); + } + + + public void shutdown() { + saveState(); + } + + public boolean canShutDown() { + return true; + } + + private void addContactListToWorkspace() { + Workspace workspace = SparkManager.getWorkspace(); + workspace.getWorkspacePane().addTab("Contacts", SparkRes.getImageIcon(SparkRes.SMALL_ALL_CHATS_IMAGE), this); //NOTRANS + + // Add To Contacts Menu + final JMenu contactsMenu = SparkManager.getMainWindow().getMenuByName("Contacts"); + JMenuItem addContactsMenu = new JMenuItem("", SparkRes.getImageIcon(SparkRes.SMALL_ADD_IMAGE)); + ResourceUtils.resButton(addContactsMenu, "&Add Contact"); + ResourceUtils.resButton(addContactGroupMenu, "Add Contact &Group"); + + contactsMenu.add(addContactsMenu); + contactsMenu.add(addContactGroupMenu); + addContactsMenu.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + new RosterDialog().showRosterDialog(); + } + }); + + addContactGroupMenu.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + + String groupName = JOptionPane.showInputDialog(getGUI(), "Name of Group:", "Add New Group", JOptionPane.QUESTION_MESSAGE); + if (ModelUtil.hasLength(groupName)) { + ContactGroup contactGroup = getContactGroup(groupName); + if (contactGroup == null) { + contactGroup = new ContactGroup(groupName); + addContactGroup(contactGroup); + } + } + } + }); + + // Add Toggle Contacts Menu + ResourceUtils.resButton(showHideMenu, "&Show Empty Groups"); + contactsMenu.add(showHideMenu); + + showHideMenu.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + showEmptyGroups(showHideMenu.isSelected()); + } + }); + + // Initialize vcard support + SparkManager.getVCardManager(); + } + + private void showEmptyGroups(boolean show) { + Iterator contactGroups = getContactGroups().iterator(); + while (contactGroups.hasNext()) { + ContactGroup group = (ContactGroup)contactGroups.next(); + if (show) { + group.setVisible(true); + } + else { + // Never hide offline group. + if (group != offlineGroup) { + group.setVisible(group.hasAvailableContacts()); + } + } + } + + localPreferences.setEmptyGroupsShown(show); + showHideMenu.setSelected(show); + viewOnline.setSelected(show); + + if (showHideMenu.isSelected()) { + viewOnline.setToolTipText("Hide Empty Groups"); + } + else { + viewOnline.setToolTipText("Show Empty Groups"); + } + + } + + /** + * Sorts ContactGroups + */ + final Comparator groupComparator = new Comparator() { + public int compare(Object contactGroupOne, Object contactGroup2) { + final ContactGroup group1 = (ContactGroup)contactGroupOne; + final ContactGroup group2 = (ContactGroup)contactGroup2; + return group1.getGroupName().toLowerCase().compareTo(group2.getGroupName().toLowerCase()); + + } + }; + + public JPanel getMainPanel() { + return mainPanel; + } + + public List getContactGroups() { + return groupList; + } + + private void subscriptionRequest(final String jid) { + final Roster roster = SparkManager.getConnection().getRoster(); + RosterEntry entry = roster.getEntry(jid); + if (entry != null && entry.getType() == RosterPacket.ItemType.TO) { + Presence response = new Presence(Presence.Type.SUBSCRIBED); + response.setTo(jid); + + SparkManager.getConnection().sendPacket(response); + return; + } + + + String message = jid + " would like to see your online presence and add you to their roster. Do you accept?"; + + final JFrame dialog = new JFrame("Subscription Request"); + dialog.setIconImage(SparkManager.getMainWindow().getIconImage()); + JPanel layoutPanel = new JPanel(); + layoutPanel.setLayout(new GridBagLayout()); + + WrappedLabel messageLabel = new WrappedLabel(); + messageLabel.setText(message); + layoutPanel.add(messageLabel, new GridBagConstraints(0, 0, 5, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); + + JButton acceptButton = new JButton("Accept"); + JButton viewInfoButton = new JButton("Profile"); + JButton denyButton = new JButton("Deny"); + layoutPanel.add(acceptButton, new GridBagConstraints(2, 1, 1, 1, 1.0, 0.0, GridBagConstraints.NORTHEAST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + layoutPanel.add(viewInfoButton, new GridBagConstraints(3, 1, 1, 1, 0.0, 0.0, GridBagConstraints.NORTHEAST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + layoutPanel.add(denyButton, new GridBagConstraints(4, 1, 1, 1, 0.0, 1.0, GridBagConstraints.NORTHEAST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + + acceptButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + dialog.dispose(); + + Presence response = new Presence(Presence.Type.SUBSCRIBED); + response.setTo(jid); + + SparkManager.getConnection().sendPacket(response); + + int ok = JOptionPane.showConfirmDialog(getGUI(), "Would you like to add the user to your roster?", "Add To Roster", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE); + if (ok == JOptionPane.OK_OPTION) { + RosterDialog rosterDialog = new RosterDialog(); + rosterDialog.setDefaultJID(jid); + rosterDialog.showRosterDialog(); + } + } + }); + + denyButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + // Send subscribed + Presence response = new Presence(Presence.Type.UNSUBSCRIBE); + response.setTo(jid); + SparkManager.getConnection().sendPacket(response); + dialog.dispose(); + } + }); + + viewInfoButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + SparkManager.getVCardManager().viewProfile(jid, getGUI()); + } + }); + + // show dialog + dialog.getContentPane().add(layoutPanel); + dialog.pack(); + dialog.setSize(450, 125); + dialog.setLocationRelativeTo(SparkManager.getMainWindow()); + SparkManager.getChatManager().getChatContainer().blinkFrameIfNecessary(dialog); + + } + + private void searchContacts(String contact) { + final JTextField contactField = new JTextField(); + + final Map contactMap = new HashMap(); + final Set contacts = new HashSet(); + + Iterator groups = getContactGroups().iterator(); + while (groups.hasNext()) { + ContactGroup group = (ContactGroup)groups.next(); + Iterator contactItems = group.getContactItems().iterator(); + while (contactItems.hasNext()) { + ContactItem item = (ContactItem)contactItems.next(); + contacts.add(item.getNickname()); + contactMap.put(item.getNickname(), item); + } + } + + //ListDataIntelliHints hints = new ListDataIntelliHints(contactField, new ArrayList(contacts)); + // hints.setCaseSensitive(false); + + final JDialog frame = new JDialog(SparkManager.getMainWindow(), "Find Contacts", false); + + JPanel layoutPanel = new JPanel(); + layoutPanel.setLayout(new GridBagLayout()); + frame.getContentPane().setLayout(new BorderLayout()); + JLabel enterLabel = new JLabel("Contact To Find?"); + enterLabel.setFont(new Font("Verdana", Font.BOLD, 10)); + layoutPanel.add(enterLabel, new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 0, 5), 0, 0)); + layoutPanel.add(contactField, new GridBagConstraints(0, 1, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 200, 0)); + layoutPanel.setBorder(BorderFactory.createBevelBorder(0)); + frame.getContentPane().add(layoutPanel); + + frame.pack(); + frame.setLocationRelativeTo(this); + frame.setVisible(true); + + frame.addKeyListener(new KeyAdapter() { + public void keyPressed(KeyEvent keyEvent) { + if (keyEvent.getKeyChar() == KeyEvent.VK_ESCAPE) { + frame.dispose(); + } + } + }); + + contactField.addKeyListener(new KeyAdapter() { + public void keyReleased(KeyEvent keyEvent) { + if (keyEvent.getKeyChar() == KeyEvent.VK_ENTER) { + if (ModelUtil.hasLength(contactField.getText())) { + ContactItem item = (ContactItem)contactMap.get(contactField.getText()); + if (item != null) { + activateChat(item.getFullJID(), item.getNickname()); + frame.dispose(); + } + } + + } + else if (keyEvent.getKeyChar() == KeyEvent.VK_ESCAPE) { + frame.dispose(); + } + } + }); + contactField.setText(contact); + } + + public void addContextMenuListener(ContextMenuListener listener) { + contextListeners.add(listener); + } + + public void removeContextMenuListener(ContextMenuListener listener) { + contextListeners.remove(listener); + } + + public void fireContextMenuListenerPopup(JPopupMenu popup, Object object) { + Iterator listeners = new ArrayList(contextListeners).iterator(); + while (listeners.hasNext()) { + ContextMenuListener listener = (ContextMenuListener)listeners.next(); + listener.poppingUp(object, popup); + } + } + + public JComponent getGUI() { + return this; + } + + public ContactGroup getActiveGroup() { + return activeGroup; + } + + public Collection getSelectedUsers() { + final List list = new ArrayList(); + + Iterator contactGroups = getContactGroups().iterator(); + while (contactGroups.hasNext()) { + ContactGroup group = (ContactGroup)contactGroups.next(); + List items = group.getSelectedContacts(); + Iterator itemIterator = items.iterator(); + while (itemIterator.hasNext()) { + ContactItem item = (ContactItem)itemIterator.next(); + list.add(item); + } + } + return list; + } + + private void checkGroup(ContactGroup group) { + if (!group.hasAvailableContacts() && group != offlineGroup && group != unfiledGroup && !showHideMenu.isSelected()) { + group.setVisible(false); + } + } + + public void addFileDropListener(FileDropListener listener) { + dndListeners.add(listener); + } + + public void removeFileDropListener(FileDropListener listener) { + dndListeners.remove(listener); + } + + public void fireFilesDropped(Collection files, ContactItem item) { + final Iterator listeners = new ArrayList(dndListeners).iterator(); + while (listeners.hasNext()) { + ((FileDropListener)listeners.next()).filesDropped(files, item); + } + } + + public void contactItemAdded(ContactItem item) { + fireContactItemAdded(item); + } + + public void contactItemRemoved(ContactItem item) { + fireContactItemRemoved(item); + } + + /* + Adding ContactListListener support. + */ + + public void addContactListListener(ContactListListener listener) { + contactListListeners.add(listener); + } + + public void removeContactListListener(ContactListListener listener) { + contactListListeners.remove(listener); + } + + public void fireContactItemAdded(ContactItem item) { + final Iterator listeners = new ArrayList(contactListListeners).iterator(); + while (listeners.hasNext()) { + ((ContactListListener)listeners.next()).contactItemAdded(item); + } + } + + public void fireContactItemRemoved(ContactItem item) { + final Iterator listeners = new ArrayList(contactListListeners).iterator(); + while (listeners.hasNext()) { + ((ContactListListener)listeners.next()).contactItemRemoved(item); + } + } + + public void fireContactGroupAdded(ContactGroup group) { + final Iterator listeners = new ArrayList(contactListListeners).iterator(); + while (listeners.hasNext()) { + ((ContactListListener)listeners.next()).contactGroupAdded(group); + } + } + + public void fireContactGroupRemoved(ContactGroup group) { + final Iterator listeners = new ArrayList(contactListListeners).iterator(); + while (listeners.hasNext()) { + ((ContactListListener)listeners.next()).contactGroupRemoved(group); + } + } + + public void uninstall() { + // Do nothing. + } + + private void saveState() { + if (props == null) { + return; + } + final Iterator contactGroups = getContactGroups().iterator(); + while (contactGroups.hasNext()) { + ContactGroup group = (ContactGroup)contactGroups.next(); + props.put(group.getGroupName(), Boolean.toString(group.isCollapsed())); + } + + try { + props.store(new FileOutputStream(propertiesFile), "Tracks the state of groups."); + } + catch (IOException e) { + Log.error("Unable to save group properties.", e); + } + + } + + + public void connectionClosed() { + reconnect("The connection was closed.", false); + } + + private void reconnect(final String message, final boolean conflict) { + // Show MainWindow + SparkManager.getMainWindow().setVisible(true); + + // Flash That Window. + SparkManager.getAlertManager().flashWindowStopOnFocus(SparkManager.getMainWindow()); + + if (reconnectListener == null) { + reconnectListener = new RetryPanel.ReconnectListener() { + public void reconnected() { + clientReconnected(); + } + + public void cancelled() { + removeAllUsers(); + } + + }; + + retryPanel.addReconnectionListener(reconnectListener); + } + + // Show reconnect panel + CardLayout cl = (CardLayout)getLayout(); + cl.show(this, RETRY_PANEL); + + retryPanel.setDisconnectReason(message); + + if (!conflict) { + retryPanel.startTimer(); + } + else { + retryPanel.showConflict(); + } + + + } + + private void removeAllUsers() { + // Show reconnect panel + CardLayout cardLayout = (CardLayout)getLayout(); + cardLayout.show(this, ROSTER_PANEL); + + // Behind the scenes, move everyone to the offline group. + Iterator contactGroups = new ArrayList(getContactGroups()).iterator(); + while (contactGroups.hasNext()) { + ContactGroup contactGroup = (ContactGroup)contactGroups.next(); + Iterator contactItems = new ArrayList(contactGroup.getContactItems()).iterator(); + while (contactItems.hasNext()) { + ContactItem item = (ContactItem)contactItems.next(); + contactGroup.removeContactItem(item); + } + } + + } + + public void clientReconnected() { + XMPPConnection con = SparkManager.getConnection(); + if (con.isConnected()) { + // Send Available status + final Presence presence = SparkManager.getWorkspace().getStatusBar().getPresence(); + SparkManager.getSessionManager().changePresence(presence); + final Roster roster = con.getRoster(); + + final Iterator rosterEntries = roster.getEntries(); + while (rosterEntries.hasNext()) { + RosterEntry entry = (RosterEntry)rosterEntries.next(); + updateUserPresence(roster.getPresence(entry.getUser())); + } + } + + CardLayout cl = (CardLayout)getLayout(); + cl.show(this, ROSTER_PANEL); + } + + public void connectionClosedOnError(final Exception ex) { + String errorMessage = ""; + + boolean conflictError = false; + if (ex instanceof XMPPException) { + XMPPException xmppEx = (XMPPException)ex; + StreamError error = xmppEx.getStreamError(); + String reason = error.getCode(); + if ("conflict".equals(reason)) { + errorMessage = "A user with the same account has logged in from another location."; + conflictError = true; + } + else { + errorMessage = "You have lost your connection to the server.\n\nReason: " + reason; + } + } + + reconnect(errorMessage, conflictError); + } + +} + diff --git a/src/java/org/jivesoftware/spark/ui/ContactListListener.java b/src/java/org/jivesoftware/spark/ui/ContactListListener.java new file mode 100644 index 00000000..a6cc40b4 --- /dev/null +++ b/src/java/org/jivesoftware/spark/ui/ContactListListener.java @@ -0,0 +1,48 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.ui; + +/** + * The ContactListListener interface is used to listen for model changes within the Contact List. + *

        + * In general, you implement this interface in order to listen + * for adding and removal of both ContactItems and ContactGroups. + */ +public interface ContactListListener { + + /** + * Notified when a ContactItem has been added to the ContactList. + * + * @param item the ContactItem added. + */ + void contactItemAdded(ContactItem item); + + /** + * Notified when a ContactItem has been removed from the ContactList. + * + * @param item the ContactItem removed. + */ + void contactItemRemoved(ContactItem item); + + /** + * Called when a ContactGroup has been added to the ContactList. + * + * @param group the ContactGroup. + */ + void contactGroupAdded(ContactGroup group); + + /** + * Called when a ContactGroup has been removed from the ContactList. + * + * @param group the ContactGroup. + */ + void contactGroupRemoved(ContactGroup group); +} diff --git a/src/java/org/jivesoftware/spark/ui/CustomPainter.java b/src/java/org/jivesoftware/spark/ui/CustomPainter.java new file mode 100644 index 00000000..9ef45718 --- /dev/null +++ b/src/java/org/jivesoftware/spark/ui/CustomPainter.java @@ -0,0 +1,100 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.ui; + +public class CustomPainter { + /* + + public Color getCollapsiblePaneTitleForeground() { + String start = Default.getString(Default.TEXT_COLOR); + return getColor(start); + } + + public Color getCollapsiblePaneFocusTitleForeground() { + String start = Default.getString(Default.HOVER_TEXT_COLOR); + return getColor(start); + } + + public Color getBackgroundLt() { + String start = Default.getString(Default.TAB_START_COLOR); + return getColor(start); + } + + public Color getBackgroundDk() { + String start = Default.getString(Default.TAB_END_COLOR); + return getColor(start); + } + + public Color getColor(Object object) { + return Color.pink; + } + + public Color getCollapsiblePaneContentBackground() { + return Color.pink; + } + + + public Color getCollapsiblePaneTitleForegroundEmphasized() { + return Color.DARK_GRAY; + } + + public Color getCollapsiblePaneFocusTitleForegroundEmphasized() { + return Color.DARK_GRAY; + } + + + public void paintCollapsiblePaneTitlePaneBackground(JComponent jComponent, Graphics graphics, Rectangle rectangle, int i, int i1) { + String start = Default.getString(Default.CONTACT_GROUP_START_COLOR); + String end = Default.getString(Default.CONTACT_GROUP_END_COLOR); + Color startColor = getColor(start); + Color endColor = getColor(end); + + + Graphics2D g2d = (Graphics2D)graphics; + // A non-cyclic gradient + GradientPaint gradient = new GradientPaint(0, 0, startColor, (float)rectangle.getWidth(), (float)rectangle.getHeight(), endColor); + g2d.setPaint(gradient); + + // A cyclic gradient + g2d.setPaint(gradient); + g2d.fill(rectangle); + } + + public void paintCollapsiblePanesBackground(JComponent jComponent, Graphics graphics, Rectangle rectangle, int i, int i1) { + Color startColor = Color.gray; + Color endColor = Color.white; + + Graphics2D g2d = (Graphics2D)graphics; + // A non-cyclic gradient + GradientPaint gradient = new GradientPaint(0, 0, startColor, (float)rectangle.getWidth(), (float)rectangle.getHeight(), endColor); + g2d.setPaint(gradient); + + // A cyclic gradient + g2d.setPaint(gradient); + g2d.fill(rectangle); + } + + private static Color getColor(String commaColorString) { + Color color = null; + try { + color = null; + + StringTokenizer tkn = new StringTokenizer(commaColorString, ","); + color = new Color(Integer.parseInt(tkn.nextToken()), Integer.parseInt(tkn.nextToken()), Integer.parseInt(tkn.nextToken())); + } + catch (NumberFormatException e1) { + Log.error(e1); + return Color.white; + } + return color; + }*/ + +} diff --git a/src/java/org/jivesoftware/spark/ui/DataFormUI.java b/src/java/org/jivesoftware/spark/ui/DataFormUI.java new file mode 100644 index 00000000..3b7dc799 --- /dev/null +++ b/src/java/org/jivesoftware/spark/ui/DataFormUI.java @@ -0,0 +1,230 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.ui; + +import org.jivesoftware.smackx.Form; +import org.jivesoftware.smackx.FormField; +import org.jivesoftware.spark.component.CheckBoxList; +import org.jivesoftware.spark.util.ModelUtil; + +import javax.swing.JCheckBox; +import javax.swing.JComboBox; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JPasswordField; +import javax.swing.JScrollPane; +import javax.swing.JTextArea; +import javax.swing.JTextField; + +import java.awt.Component; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.StringTokenizer; + +/** + * Builds the UI for any DataForm (JEP-0004: Data Forms), and allow for creation + * of an answer form to send back the the server. + */ +public class DataFormUI extends JPanel { + private final Map valueMap = new HashMap(); + private int row = 5; + private Form searchForm; + + /** + * Creates a new DataFormUI + * + * @param form the DataForm to build a UI with. + */ + public DataFormUI(Form form) { + this.setLayout(new GridBagLayout()); + this.searchForm = form; + + buildUI(form); + + this.add(new JLabel(), new GridBagConstraints(0, row, 3, 1, 0.0, 1.0, GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0)); + } + + + private void buildUI(Form form) { + // Add default answers to the form to submit + Iterator fields = form.getFields(); + while (fields.hasNext()) { + FormField field = (FormField)fields.next(); + String variable = field.getVariable(); + String label = field.getLabel(); + String type = field.getType(); + + Iterator iter = field.getValues(); + List valueList = new ArrayList(); + while (iter.hasNext()) { + valueList.add(iter.next()); + } + + if (type.equals(FormField.TYPE_BOOLEAN)) { + String o = (String)valueList.get(0); + boolean isSelected = o.equals("1"); + JCheckBox box = new JCheckBox(label); + box.setSelected(isSelected); + addField(label, box, variable); + } + else if (type.equals(FormField.TYPE_TEXT_SINGLE) || type.equals(FormField.TYPE_JID_SINGLE)) { + String v = ""; + if (valueList.size() > 0) { + v = (String)valueList.get(0); + } + addField(label, new JTextField(v), variable); + } + else if (type.equals(FormField.TYPE_TEXT_MULTI) || + type.equals(FormField.TYPE_JID_MULTI)) { + StringBuffer buf = new StringBuffer(); + iter = field.getOptions(); + while (iter.hasNext()) { + buf.append((String)iter.next()); + } + addField(label, new JTextArea(buf.toString()), variable); + } + else if (type.equals(FormField.TYPE_TEXT_PRIVATE)) { + addField(label, new JPasswordField(), variable); + } + else if (type.equals(FormField.TYPE_LIST_SINGLE)) { + JComboBox box = new JComboBox(); + iter = field.getOptions(); + while (iter.hasNext()) { + FormField.Option option = (FormField.Option)iter.next(); + String value = option.getValue(); + box.addItem(option); + } + if (valueList.size() > 0) { + String defaultValue = (String)valueList.get(0); + box.setSelectedItem(defaultValue); + } + + addField(label, box, variable); + } + else if (type.equals(FormField.TYPE_LIST_MULTI)) { + CheckBoxList checkBoxList = new CheckBoxList(); + Iterator i = field.getValues(); + while (i.hasNext()) { + String value = (String)i.next(); + checkBoxList.addCheckBox(new JCheckBox(value), value); + } + addField(label, checkBoxList, variable); + } + } + } + + /** + * Returns the answered DataForm. + * + * @return the answered DataForm. + */ + public Form getFilledForm() { + // Now submit all information + Iterator valueIter = valueMap.keySet().iterator(); + Form answerForm = searchForm.createAnswerForm(); + while (valueIter.hasNext()) { + String answer = (String)valueIter.next(); + Object o = valueMap.get(answer); + if (o instanceof JCheckBox) { + boolean isSelected = ((JCheckBox)o).isSelected(); + answerForm.setAnswer(answer, isSelected); + } + else if (o instanceof JTextArea) { + List list = new ArrayList(); + String value = ((JTextArea)o).getText(); + StringTokenizer tokenizer = new StringTokenizer(value, ", ", false); + while (tokenizer.hasMoreTokens()) { + list.add(tokenizer.nextToken()); + } + if (list.size() > 0) { + answerForm.setAnswer(answer, list); + } + } + else if (o instanceof JTextField) { + String value = ((JTextField)o).getText(); + if (ModelUtil.hasLength(value)) { + answerForm.setAnswer(answer, value); + } + } + else if (o instanceof JComboBox) { + Object v = ((JComboBox)o).getSelectedItem(); + String value = ""; + if (v instanceof FormField.Option) { + value = ((FormField.Option)v).getValue(); + } + else { + value = (String)v; + } + List list = new ArrayList(); + list.add(value); + if (list.size() > 0) { + answerForm.setAnswer(answer, list); + } + } + else if (o instanceof CheckBoxList) { + List list = (List)((CheckBoxList)o).getSelectedValues(); + if (list.size() > 0) { + answerForm.setAnswer(answer, list); + } + } + } + + return answerForm; + } + + + private void addField(String label, JComponent comp, String variable) { + if (!(comp instanceof JCheckBox)) { + this.add(new JLabel(label), new GridBagConstraints(0, row, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); + } + if (comp instanceof JTextArea) { + this.add(new JScrollPane(comp), new GridBagConstraints(1, row, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 100, 50)); + } + else if (comp instanceof JCheckBox) { + this.add(comp, new GridBagConstraints(0, row, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); + } + else if (comp instanceof CheckBoxList) { + this.add(comp, new GridBagConstraints(1, row, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 50)); + } + else { + this.add(comp, new GridBagConstraints(1, row, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); + + } + valueMap.put(variable, comp); + row++; + } + + public Component getComponent(String label) { + Component comp = (Component)valueMap.get(label); + return comp; + } + + + private String getValue(String label) { + Component comp = (Component)valueMap.get(label); + if (comp instanceof JCheckBox) { + return "" + ((JCheckBox)comp).isSelected(); + } + + if (comp instanceof JTextField) { + return ((JTextField)comp).getText(); + } + return null; + } +} + diff --git a/src/java/org/jivesoftware/spark/ui/FileDropListener.java b/src/java/org/jivesoftware/spark/ui/FileDropListener.java new file mode 100644 index 00000000..9f041708 --- /dev/null +++ b/src/java/org/jivesoftware/spark/ui/FileDropListener.java @@ -0,0 +1,33 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.ui; + +import java.awt.Component; +import java.util.Collection; + +/** + * The FileDropListener interface is one of the interfaces extension + * writers use to add functionality to Spark. + *

        + * In general, you implement this interface in order to listen + * for file drops onto components. + */ +public interface FileDropListener { + + /** + * Called when a file(s) has been Drag and Dropped onto a component. + * + * @param files the Collection of Files. + * @param component the Component the files were dropped on. + */ + void filesDropped(Collection files, Component component); + +} diff --git a/src/java/org/jivesoftware/spark/ui/LinkInterceptor.java b/src/java/org/jivesoftware/spark/ui/LinkInterceptor.java new file mode 100644 index 00000000..cf409b05 --- /dev/null +++ b/src/java/org/jivesoftware/spark/ui/LinkInterceptor.java @@ -0,0 +1,26 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.ui; + +/** + * Implementors of this interface wish to interecept link clicked events within + * an active chat. + */ +public interface LinkInterceptor { + + /** + * Returns true if you wish to handle this link, otherwise, will default to Spark. + * + * @param link the link that was clicked. + * @return true if the user wishes to handle the link. + */ + public boolean handleLink(String link); +} diff --git a/src/java/org/jivesoftware/spark/ui/MessageEventListener.java b/src/java/org/jivesoftware/spark/ui/MessageEventListener.java new file mode 100644 index 00000000..4fcd2e5a --- /dev/null +++ b/src/java/org/jivesoftware/spark/ui/MessageEventListener.java @@ -0,0 +1,25 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.ui; + +import org.jivesoftware.smack.packet.Message; + +/** + * Listen for the sending and receiving of messages. + * + * @author Derek DeMoro + */ +public interface MessageEventListener { + + void sendingMessage(Message message); + + void receivingMessage(Message message); +} diff --git a/src/java/org/jivesoftware/spark/ui/MessageFilter.java b/src/java/org/jivesoftware/spark/ui/MessageFilter.java new file mode 100644 index 00000000..9d01f11e --- /dev/null +++ b/src/java/org/jivesoftware/spark/ui/MessageFilter.java @@ -0,0 +1,43 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.ui; + +import org.jivesoftware.smack.packet.Message; + +/** + * The MessageFilter interface is one of the interfaces extension + * writers use to add functionality to Spark. + *

        + * In general, you implement this interface in order to modify the body of the message. + *

        + *

        + * String currentBody = message.getBody();
        + * currentBody = removeAllBadWords(currentBody);
        + * message.setBody(currentBody);
        + */
        +public interface MessageFilter {
        +
        +    /**
        +     * Update the body of an outgoing message.
        +     *
        +     * @param message the message to update.
        +     */
        +    void filterOutgoing(Message message);
        +
        +    /**
        +     * Updates the body of an incoming message.
        +     *
        +     * @param message the message to update.
        +     */
        +    void filterIncoming(Message message);
        +
        +
        +}
        diff --git a/src/java/org/jivesoftware/spark/ui/MessageListener.java b/src/java/org/jivesoftware/spark/ui/MessageListener.java
        new file mode 100644
        index 00000000..caa3aca0
        --- /dev/null
        +++ b/src/java/org/jivesoftware/spark/ui/MessageListener.java
        @@ -0,0 +1,43 @@
        +/**
        + * $Revision: $
        + * $Date: $
        + *
        + * Copyright (C) 2006 Jive Software. All rights reserved.
        + *
        + * This software is published under the terms of the GNU Lesser Public License (LGPL),
        + * a copy of which is included in this distribution.
        + */
        +
        +package org.jivesoftware.spark.ui;
        +
        +import org.jivesoftware.smack.packet.Message;
        +
        +/**
        + * The MessageListener interface is one of the interfaces extension
        + * writers use to add functionality to Spark.
        + * 

        + * In general, you implement this interface in order to listen + * for incoming or outgoing messages to particular ChatRooms and to be notified + * about the message itself. + */ +public interface MessageListener { + + /** + * Invoked by the ChatRoom when it is receives a new message. + * + * @param room the ChatRoom the message was sent to. + * @param message the message received. + * @see ChatRoom + */ + void messageReceived(ChatRoom room, Message message); + + /** + * Invoked by the ChatRoom when a new message has + * been sent. + * + * @param room the ChatRoom that sent the message. + * @param message the message sent. + * @see ChatRoom + */ + void messageSent(ChatRoom room, Message message); +} \ No newline at end of file diff --git a/src/java/org/jivesoftware/spark/ui/NewRoster.java b/src/java/org/jivesoftware/spark/ui/NewRoster.java new file mode 100644 index 00000000..478fd5eb --- /dev/null +++ b/src/java/org/jivesoftware/spark/ui/NewRoster.java @@ -0,0 +1,159 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.ui; + +import org.jivesoftware.smack.Roster; +import org.jivesoftware.smack.RosterEntry; +import org.jivesoftware.smack.RosterGroup; +import org.jivesoftware.smack.RosterListener; +import org.jivesoftware.smack.XMPPConnection; +import org.jivesoftware.spark.util.GraphicUtils; + +import javax.swing.JFrame; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTree; +import javax.swing.tree.DefaultTreeModel; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Dimension; +import java.util.Collection; +import java.util.Iterator; + +public class NewRoster extends JPanel implements RosterListener { + + private final RosterNode rootNode; + private JTree tree; + private DefaultTreeModel model; + + private JPanel mainPanel; + private JScrollPane treeScroller; + + public static XMPPConnection con; + + public NewRoster() { + setLayout(new BorderLayout()); + + rootNode = new RosterNode(); + tree = new JTree(rootNode) { + /** + * Lets make sure that the panel doesn't stretch past the + * scrollpane view pane. + * + * @return the preferred dimension + */ + public Dimension getPreferredSize() { + final Dimension size = super.getPreferredSize(); + size.width = 0; + return size; + } + }; + + tree.setCellRenderer(new RosterTreeCellRenderer()); + model = (DefaultTreeModel)tree.getModel(); + + mainPanel = new JPanel(); + mainPanel.setLayout(new BorderLayout()); + mainPanel.setBackground(Color.white); + + treeScroller = new JScrollPane(tree); + mainPanel.add(treeScroller, BorderLayout.CENTER); + + this.add(mainPanel, BorderLayout.CENTER); + } + + private void buildContactList(XMPPConnection conn) { + con = conn; + final Roster roster = con.getRoster(); + + + roster.addRosterListener(this); + + final Iterator rosterGroups = roster.getGroups(); + while (rosterGroups.hasNext()) { + RosterGroup group = (RosterGroup)rosterGroups.next(); + + // Create Group node. + final RosterNode groupNode = new RosterNode(group.getName(), true); + if (group.getEntryCount() > 0) { + rootNode.add(groupNode); + } + + + Iterator entries = group.getEntries(); + while (entries.hasNext()) { + RosterEntry entry = (RosterEntry)entries.next(); + String nickname = entry.getName(); + if (nickname == null) { + nickname = entry.getUser(); + } + + RosterNode contact = new RosterNode(nickname, entry.getUser()); + + // Add to contact group. + groupNode.add(contact); + + } + + } + + RosterNode unfiledGroup = new RosterNode("Unfiled", true); + rootNode.add(unfiledGroup); + + // Add Unfiled Group + final Iterator unfiledEntries = roster.getUnfiledEntries(); + while (unfiledEntries.hasNext()) { + RosterEntry entry = (RosterEntry)unfiledEntries.next(); + String name = entry.getName(); + if (name == null) { + name = entry.getUser(); + } + + RosterNode contact = new RosterNode(name, entry.getUser()); + unfiledGroup.add(contact); + } + + for (int i = 0; i < tree.getRowCount(); i++) { + tree.expandRow(i); + } + + tree.setRootVisible(false); + + } + + public void entriesAdded(Collection addresses) { + } + + public void entriesUpdated(Collection addresses) { + } + + public void entriesDeleted(Collection addresses) { + } + + public void presenceChanged(String XMPPAddress) { + } + + public static void main(String args[]) throws Exception { + final XMPPConnection con = new XMPPConnection("jivesoftware.com", 5222); + con.login("agent", "agent"); + + final JFrame frame = new JFrame(); + frame.getContentPane().setLayout(new BorderLayout()); + NewRoster roster = new NewRoster(); + frame.getContentPane().add(roster); + frame.pack(); + frame.setSize(600, 400); + GraphicUtils.centerWindowOnScreen(frame); + frame.setVisible(true); + roster.buildContactList(con); + } +} \ No newline at end of file diff --git a/src/java/org/jivesoftware/spark/ui/PresenceListener.java b/src/java/org/jivesoftware/spark/ui/PresenceListener.java new file mode 100644 index 00000000..1680c75d --- /dev/null +++ b/src/java/org/jivesoftware/spark/ui/PresenceListener.java @@ -0,0 +1,29 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.ui; + +import org.jivesoftware.smack.packet.Presence; + +/** + * The PresenceListener is used to listen for Personal Presence changes within the system. + *

        + * Presence listeners can be registered using the {@link org.jivesoftware.spark.SessionManager} + */ +public interface PresenceListener { + + /** + * Called when the user of Sparks presence has changed. + * + * @param presence the presence. + */ + void presenceChanged(Presence presence); + +} diff --git a/src/java/org/jivesoftware/spark/ui/RetryPanel.java b/src/java/org/jivesoftware/spark/ui/RetryPanel.java new file mode 100644 index 00000000..617f67b5 --- /dev/null +++ b/src/java/org/jivesoftware/spark/ui/RetryPanel.java @@ -0,0 +1,233 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.ui; + +import org.jivesoftware.resource.SparkRes; +import org.jivesoftware.smack.XMPPConnection; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.spark.SparkManager; +import org.jivesoftware.spark.component.RolloverButton; +import org.jivesoftware.spark.component.WrappedLabel; +import org.jivesoftware.spark.util.ModelUtil; +import org.jivesoftware.spark.util.SwingWorker; +import org.jivesoftware.spark.util.log.Log; + +import javax.swing.JPanel; +import javax.swing.Timer; + +import java.awt.Color; +import java.awt.FlowLayout; +import java.awt.Font; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * RetryPanel is the UI/Function class to handle reconnection logic. This allows for a simple card layout to replace the current + * roster when the connection has been lost. + * + * @author Derek DeMoro + */ +public class RetryPanel extends JPanel { + private WrappedLabel descriptionLabel; + private RolloverButton retryButton; + private RolloverButton cancelButton; + + private Timer timer; + private int countdown; + + private List listeners = new ArrayList(); + + /** + * Construct the RetryPanel. + */ + public RetryPanel() { + setLayout(new GridBagLayout()); + + countdown = 45; + + // Init Components + descriptionLabel = new WrappedLabel(); + + retryButton = new RolloverButton(SparkRes.getImageIcon(SparkRes.SMALL_CHECK)); + cancelButton = new RolloverButton("Cancel", SparkRes.getImageIcon(SparkRes.SMALL_CIRCLE_DELETE)); + + layoutComponents(); + + retryButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent actionEvent) { + attemptReconnect(); + } + }); + + + cancelButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent actionEvent) { + logout(); + } + }); + + setBackground(Color.white); + + // Set Font + descriptionLabel.setFont(new Font("Dialog", Font.PLAIN, 13)); + } + + /** + * Sets the reason the user was disconnected from the server. + * + * @param reason the reason the user was disconnected from the server. + */ + public void setDisconnectReason(String reason) { + descriptionLabel.setText(reason); + } + + private void layoutComponents() { + add(descriptionLabel, new GridBagConstraints(0, 0, 1, 1, 1.0, 1.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(5, 5, 5, 5), 0, 0)); + + final JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT)); + buttonPanel.setOpaque(false); + buttonPanel.add(retryButton); + buttonPanel.add(cancelButton); + + add(buttonPanel, new GridBagConstraints(0, 1, 1, 1, 1.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); + } + + /** + * Starts the countdown to the next retry attempt. The retry attemp is set for every 45 seconds or what is set + * as the default in preferences. + */ + protected void startTimer() { + retryButton.setVisible(true); + + ActionListener updateTime = new ActionListener() { + public void actionPerformed(ActionEvent evt) { + if (countdown > 0) { + retryButton.setText("Retry in " + countdown); + countdown--; + } + else { + attemptReconnect(); + } + } + }; + + timer = new Timer(1000, updateTime); + timer.start(); + } + + /** + * Changes the UI to handle when a conflict occurs on the server. + */ + public void showConflict() { + retryButton.setVisible(false); + } + + private void attemptReconnect() { + retryButton.setText("Attempting...."); + timer.stop(); + + SwingWorker worker = new SwingWorker() { + public Object construct() { + try { + Thread.sleep(500); + } + catch (InterruptedException e) { + Log.error(e); + } + + return timer; + } + + public void finished() { + XMPPConnection con; + try { + con = SparkManager.getConnection().reconnect(); + SparkManager.getSessionManager().setConnection(con); + SparkManager.getMessageEventManager().setConnection(con); + fireReconnection(); + } + catch (XMPPException e) { + countdown = 45; + timer.start(); + } + } + }; + + worker.start(); + + + } + + /** + * Adds a ReconnectListener. + * + * @param listener the listener to add. + */ + public void addReconnectionListener(ReconnectListener listener) { + listeners.add(listener); + } + + /** + * Removes a ReconnectListener. + * + * @param listener the listener to remove. + */ + public void removeReconnectionListener(ReconnectListener listener) { + listeners.remove(listener); + } + + private void fireReconnection() { + final Iterator iter = ModelUtil.reverseListIterator(listeners.listIterator()); + while (iter.hasNext()) { + ReconnectListener listener = (ReconnectListener)iter.next(); + listener.reconnected(); + } + } + + private void fireCancelled() { + final Iterator iter = ModelUtil.reverseListIterator(listeners.listIterator()); + while (iter.hasNext()) { + ReconnectListener listener = (ReconnectListener)iter.next(); + listener.cancelled(); + } + } + + + private void logout() { + SparkManager.getMainWindow().setVisible(false); + SparkManager.getMainWindow().logout(); + } + + /** + * Implementation of this class if you wish to be notified of reconnection or cancelling events when Spark + * loses it's connection to the server. + */ + public interface ReconnectListener { + + /** + * Spark has successfully reconnected. + */ + void reconnected(); + + /** + * The user has decided to cancel the reconnection attempt. + */ + void cancelled(); + } + + +} diff --git a/src/java/org/jivesoftware/spark/ui/RosterDialog.java b/src/java/org/jivesoftware/spark/ui/RosterDialog.java new file mode 100644 index 00000000..c7475acd --- /dev/null +++ b/src/java/org/jivesoftware/spark/ui/RosterDialog.java @@ -0,0 +1,339 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.ui; + +import org.jivesoftware.resource.SparkRes; +import org.jivesoftware.smack.Roster; +import org.jivesoftware.smack.RosterEntry; +import org.jivesoftware.smack.RosterGroup; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.util.StringUtils; +import org.jivesoftware.smackx.packet.VCard; +import org.jivesoftware.spark.SparkManager; +import org.jivesoftware.spark.component.TitlePanel; +import org.jivesoftware.spark.util.ModelUtil; +import org.jivesoftware.spark.util.log.Log; + +import javax.swing.JButton; +import javax.swing.JComboBox; +import javax.swing.JDialog; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JTextField; + +import java.awt.BorderLayout; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.Iterator; +import java.util.Vector; + + +/** + * The RosterDialog is used to add new users to the users XMPP Roster. + */ +public class RosterDialog implements PropertyChangeListener, ActionListener { + private JPanel panel; + private JTextField jidField; + private JTextField nicknameField; + private final Vector groupModel = new Vector(); + private JComboBox groupBox; + private JOptionPane pane; + private JDialog dialog; + private ContactList contactList; + + /** + * Create a new instance of RosterDialog. + */ + public RosterDialog() { + contactList = SparkManager.getWorkspace().getContactList(); + + panel = new JPanel(); + JLabel contactIDLabel = new JLabel("Jabber ID:"); + jidField = new JTextField(); + JLabel nicknameLabel = new JLabel("Nickname:"); + nicknameField = new JTextField(); + JLabel groupLabel = new JLabel("Group:"); + groupBox = new JComboBox(groupModel); + JButton newGroupButton = new JButton("New..."); + pane = null; + dialog = null; + panel.setLayout(new GridBagLayout()); + panel.add(contactIDLabel, new GridBagConstraints(0, 0, 1, 1, 0.0D, 0.0D, 17, 2, new Insets(5, 5, 5, 5), 0, 0)); + panel.add(jidField, new GridBagConstraints(1, 0, 1, 1, 1.0D, 0.0D, 17, 2, new Insets(5, 5, 5, 5), 0, 0)); + panel.add(nicknameLabel, new GridBagConstraints(0, 1, 1, 1, 0.0D, 0.0D, 17, 2, new Insets(5, 5, 5, 5), 0, 0)); + panel.add(nicknameField, new GridBagConstraints(1, 1, 1, 1, 1.0D, 0.0D, 17, 2, new Insets(5, 5, 5, 5), 0, 0)); + panel.add(groupLabel, new GridBagConstraints(0, 2, 1, 1, 0.0D, 0.0D, 17, 2, new Insets(5, 5, 5, 5), 0, 0)); + panel.add(groupBox, new GridBagConstraints(1, 2, 1, 1, 1.0D, 0.0D, 17, 2, new Insets(5, 5, 5, 5), 0, 0)); + panel.add(newGroupButton, new GridBagConstraints(2, 2, 1, 1, 0.0D, 0.0D, 17, 2, new Insets(5, 5, 5, 5), 0, 0)); + newGroupButton.addActionListener(this); + + + Iterator groups = contactList.getContactGroups().iterator(); + while (groups.hasNext()) { + ContactGroup group = (ContactGroup)groups.next(); + if (!group.isOfflineGroup() && !"Unfiled".equalsIgnoreCase(group.getGroupName()) && !group.isSharedGroup()) { + groupModel.add(group.getGroupName()); + } + } + + groupBox.setEditable(true); + + if (groupModel.size() == 0) { + groupBox.addItem("Friends"); + } + + if (groupModel.size() > 0) { + groupBox.setSelectedIndex(0); + } + + jidField.addFocusListener(new FocusListener() { + public void focusGained(FocusEvent e) { + + } + + public void focusLost(FocusEvent e) { + String jid = jidField.getText(); + if (ModelUtil.hasLength(jid) && jid.indexOf('@') == -1) { + // Append server address + jidField.setText(jid + "@" + SparkManager.getConnection().getServiceName()); + } + + String nickname = nicknameField.getText(); + if (!ModelUtil.hasLength(nickname) && ModelUtil.hasLength(jid)) { + nicknameField.setText(StringUtils.parseName(jidField.getText())); + } + } + }); + } + + /** + * Sets the default ContactGroup to display in the combo box. + * + * @param contactGroup the default ContactGroup. + */ + public void setDefaultGroup(ContactGroup contactGroup) { + String groupName = contactGroup.getGroupName(); + if (groupModel.contains(groupName)) { + groupBox.setSelectedItem(groupName); + } + else if (groupModel.size() > 0) { + groupBox.addItem(groupName); + groupBox.setSelectedItem(groupName); + } + } + + /** + * Sets the default jid to show in the jid field. + * + * @param jid the jid. + */ + public void setDefaultJID(String jid) { + jidField.setText(jid); + } + + /** + * Sets the default nickname to show in the nickname field. + * + * @param nickname the nickname. + */ + public void setDefaultNickname(String nickname) { + nicknameField.setText(nickname); + } + + + public void actionPerformed(ActionEvent e) { + String group = JOptionPane.showInputDialog(dialog, "Enter new group name:", "New Roster Group", 3); + if (group != null && group.length() > 0 && !groupModel.contains(group)) { + SparkManager.getConnection().getRoster().createGroup(group); + groupModel.add(group); + int size = groupModel.size(); + groupBox.setSelectedIndex(size - 1); + } + } + + /** + * Display the RosterDialog using a parent container. + * + * @param parent the parent Frame. + */ + public void showRosterDialog(JFrame parent) { + TitlePanel titlePanel = new TitlePanel("Add Contact", "Add contact to contact list", SparkRes.getImageIcon(SparkRes.USER1_32x32), true); + JPanel mainPanel = new JPanel(); + mainPanel.setLayout(new BorderLayout()); + mainPanel.add(titlePanel, "North"); + Object[] options = { + "Add", "Cancel" + }; + pane = new JOptionPane(panel, -1, 2, null, options, options[0]); + mainPanel.add(pane, "Center"); + dialog = new JDialog(parent, "Add Contact", true); + dialog.pack(); + dialog.setContentPane(mainPanel); + dialog.setSize(350, 250); + + dialog.setLocationRelativeTo(parent); + pane.addPropertyChangeListener(this); + + + dialog.setVisible(true); + dialog.toFront(); + dialog.requestFocus(); + + jidField.requestFocus(); + } + + /** + * Display the RosterDialog using the MainWindow as the parent. + */ + public void showRosterDialog() { + showRosterDialog(SparkManager.getMainWindow()); + } + + public void propertyChange(PropertyChangeEvent e) { + if (pane != null && pane.getValue() instanceof Integer) { + pane.removePropertyChangeListener(this); + dialog.dispose(); + return; + } + + String value = (String)pane.getValue(); + String errorMessage = "General Error"; + if ("Cancel".equals(value)) { + dialog.setVisible(false); + } + else if ("Add".equals(value)) { + String contact = jidField.getText(); + String nickname = nicknameField.getText(); + String group = (String)groupBox.getSelectedItem(); + + if (!ModelUtil.hasLength(nickname) && ModelUtil.hasLength(contact)) { + // Try to load nickname from VCard + VCard vcard = new VCard(); + try { + vcard.load(SparkManager.getConnection(), contact); + nickname = vcard.getNickName(); + } + catch (XMPPException e1) { + Log.error(e1); + } + // If no nickname, use first name. + if (!ModelUtil.hasLength(nickname)) { + nickname = StringUtils.parseName(contact); + } + nicknameField.setText(nickname); + } + + ContactGroup contactGroup = contactList.getContactGroup(group); + boolean isSharedGroup = contactGroup != null && contactGroup.isSharedGroup(); + + + if (isSharedGroup) { + errorMessage = "You cannot add new contacts to a Shared Group."; + } + else if (!ModelUtil.hasLength(contact)) { + errorMessage = "Please specify the contact JID (ex. lisa@jivesoftware.org)"; + } + else if (StringUtils.parseBareAddress(contact).indexOf("@") == -1) { + errorMessage = "The JID you specified is invalid. (ex. lisa@jivesoftware.com)"; + } + else if (!ModelUtil.hasLength(group)) { + errorMessage = "You must specify a Group to add the user to."; + } + else if (ModelUtil.hasLength(contact) && ModelUtil.hasLength(group) && !isSharedGroup) { + addEntry(); + dialog.setVisible(false); + return; + } + + JOptionPane.showMessageDialog(dialog, errorMessage, "Invalid Contact Information", JOptionPane.ERROR_MESSAGE); + pane.setValue(JOptionPane.UNINITIALIZED_VALUE); + } + } + + private void addEntry() { + String jid = jidField.getText(); + if (jid.indexOf("@") == -1) { + jid = jid + "@" + SparkManager.getConnection().getHost(); + } + String nickname = nicknameField.getText(); + String group = (String)groupBox.getSelectedItem(); + + // Add as a new entry + addEntry(jid, nickname, group); + } + + /** + * Adds a new entry to the users Roster. + * + * @param jid the jid. + * @param nickname the nickname. + * @param group the contact group. + * @return the new RosterEntry. + */ + public RosterEntry addEntry(String jid, String nickname, String group) { + String[] groups = {group}; + + Roster roster = SparkManager.getConnection().getRoster(); + RosterEntry userEntry = roster.getEntry(jid); + + boolean isSubscribed = true; + if (userEntry != null) { + Iterator iter = userEntry.getGroups(); + if (iter.hasNext()) { + isSubscribed = false; + } + } + + if (isSubscribed) { + try { + roster.createEntry(jid, nickname, new String[]{group}); + } + catch (XMPPException e) { + Log.error("Unable to add new entry " + jid, e); + } + return roster.getEntry(jid); + } + + + try { + RosterGroup rosterGroup = roster.getGroup(group); + if (rosterGroup == null) { + rosterGroup = roster.createGroup(group); + } + + if (userEntry == null) { + roster.createEntry(jid, nickname, groups); + userEntry = roster.getEntry(jid); + } + else { + userEntry.setName(nickname); + rosterGroup.addEntry(userEntry); + } + + userEntry = roster.getEntry(jid); + } + catch (XMPPException ex) { + Log.error("Error adding new entry.", ex); + } + return userEntry; + } + + +} \ No newline at end of file diff --git a/src/java/org/jivesoftware/spark/ui/RosterNode.java b/src/java/org/jivesoftware/spark/ui/RosterNode.java new file mode 100644 index 00000000..4936411b --- /dev/null +++ b/src/java/org/jivesoftware/spark/ui/RosterNode.java @@ -0,0 +1,125 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.ui; + +import org.jivesoftware.resource.SparkRes; +import org.jivesoftware.smack.packet.Presence; + +import javax.swing.Icon; +import javax.swing.tree.DefaultMutableTreeNode; + +/** + * + */ +public class RosterNode extends DefaultMutableTreeNode { + private String name; + private boolean isGroup; + + private Icon openIcon; + private Icon closedIcon; + + private Presence presence; + private String fullJID; + + public RosterNode() { + super("root"); + } + + public RosterNode(String name, boolean isGroup) { + super(name, true); + + this.name = name; + + this.isGroup = isGroup; + if (isGroup) { + openIcon = SparkRes.getImageIcon(SparkRes.MINUS_SIGN); + closedIcon = SparkRes.getImageIcon(SparkRes.PLUS_SIGN); + } + } + + public Object getUserObject() { + return name + " " + getChildCount(); + } + + public RosterNode(String name, String fullJID) { + super(name, false); + this.name = name; + this.fullJID = fullJID; + } + + /** + * Returns the default image used. + * + * @return the default image used. + */ + public Icon getIcon() { + return closedIcon; + } + + /** + * Return the icon that is displayed when the node is expanded. + * + * @return the open icon. + */ + public Icon getOpenIcon() { + return openIcon; + } + + /** + * Returns the icon that is displayed when the node is collapsed. + * + * @return the closed icon. + */ + public Icon getClosedIcon() { + return closedIcon; + } + + /** + * Sets the default icon. + * + * @param icon the icon. + */ + public void setOpenIcon(Icon icon) { + openIcon = icon; + } + + public void setClosedIcon(Icon icon) { + closedIcon = icon; + } + + public boolean isContact() { + return !isGroup; + } + + public boolean isGroup() { + return isGroup; + } + + public Presence getPresence() { + return presence; + } + + public void setPresence(Presence presence) { + this.presence = presence; + } + + public String getFullJID() { + return fullJID; + } + + public void setFullJID(String fullJID) { + this.fullJID = fullJID; + } + + public String getName() { + return name; + } +} diff --git a/src/java/org/jivesoftware/spark/ui/RosterPickList.java b/src/java/org/jivesoftware/spark/ui/RosterPickList.java new file mode 100644 index 00000000..8f057c71 --- /dev/null +++ b/src/java/org/jivesoftware/spark/ui/RosterPickList.java @@ -0,0 +1,146 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.ui; + +import org.jivesoftware.resource.SparkRes; +import org.jivesoftware.smack.Roster; +import org.jivesoftware.smack.RosterEntry; +import org.jivesoftware.smack.packet.Presence; +import org.jivesoftware.spark.SparkManager; +import org.jivesoftware.spark.component.TitlePanel; +import org.jivesoftware.spark.component.renderer.JPanelRenderer; +import org.jivesoftware.spark.util.ResourceUtils; + +import javax.swing.DefaultListModel; +import javax.swing.JDialog; +import javax.swing.JLabel; +import javax.swing.JList; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JScrollPane; + +import java.awt.BorderLayout; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; + +/** + * The RosterPickList is used as a pick list of users within ones Roster. + */ +public class RosterPickList extends JPanel { + private DefaultListModel model = new DefaultListModel(); + private JList rosterList = new JList(model); + + /** + * Creates a new instance of the RosterBrowser. + */ + public RosterPickList() { + setLayout(new GridBagLayout()); + + rosterList.setCellRenderer(new JPanelRenderer()); + + JLabel rosterLabel = new JLabel(); + this.add(rosterLabel, new GridBagConstraints(0, 0, 1, 1, 1.0, 0.0, GridBagConstraints.NORTHWEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); + this.add(new JScrollPane(rosterList), new GridBagConstraints(0, 1, 1, 1, 1.0, 1.0, GridBagConstraints.NORTHWEST, GridBagConstraints.BOTH, new Insets(5, 5, 5, 5), 0, 0)); + + ResourceUtils.resLabel(rosterLabel, rosterList, "&Available users in Roster"); + } + + /** + * Displays a pick list of available users within their roster. + * + * @param parent the parent container. + * @return all items choosen in the pick list. + */ + public Collection showRoster(JDialog parent) { + // Populate Invite Panel with Available users. + Roster roster = SparkManager.getConnection().getRoster(); + Iterator iter = roster.getEntries(); + while (iter.hasNext()) { + RosterEntry entry = (RosterEntry)iter.next(); + Presence presence = roster.getPresence(entry.getUser()); + if (presence != null) { + ContactItem item = new ContactItem(entry.getName(), entry.getUser()); + model.addElement(item); + } + } + + final JOptionPane pane; + + + TitlePanel titlePanel; + + // Create the title panel for this dialog + titlePanel = new TitlePanel("Roster", "Select one ore more users in your Roster.", SparkRes.getImageIcon(SparkRes.BLANK_IMAGE), true); + + // Construct main panel w/ layout. + final JPanel mainPanel = new JPanel(); + mainPanel.setLayout(new BorderLayout()); + mainPanel.add(titlePanel, BorderLayout.NORTH); + + // The user should only be able to close this dialog. + Object[] options = {"Ok", "Cancel"}; + pane = new JOptionPane(this, JOptionPane.PLAIN_MESSAGE, JOptionPane.OK_CANCEL_OPTION, null, options, options[0]); + + mainPanel.add(pane, BorderLayout.CENTER); + + final JOptionPane p = new JOptionPane(); + + final JDialog dlg = p.createDialog(parent, "Roster"); + dlg.setModal(true); + + dlg.pack(); + dlg.setSize(350, 450); + dlg.setResizable(true); + dlg.setContentPane(mainPanel); + dlg.setLocationRelativeTo(parent); + + PropertyChangeListener changeListener = new PropertyChangeListener() { + public void propertyChange(PropertyChangeEvent e) { + String value = (String)pane.getValue(); + if ("Cancel".equals(value)) { + rosterList.clearSelection(); + pane.setValue(JOptionPane.UNINITIALIZED_VALUE); + dlg.dispose(); + } + else if ("Ok".equals(value)) { + pane.setValue(JOptionPane.UNINITIALIZED_VALUE); + dlg.dispose(); + } + } + }; + + pane.addPropertyChangeListener(changeListener); + + dlg.setVisible(true); + dlg.toFront(); + dlg.requestFocus(); + + List selectedContacts = new ArrayList(); + + Object[] values = rosterList.getSelectedValues(); + final int no = values != null ? values.length : 0; + for (int i = 0; i < no; i++) { + ContactItem item = (ContactItem)values[i]; + selectedContacts.add(item.getFullJID()); + } + + return selectedContacts; + } + + +} diff --git a/src/java/org/jivesoftware/spark/ui/RosterTreeCellRenderer.java b/src/java/org/jivesoftware/spark/ui/RosterTreeCellRenderer.java new file mode 100644 index 00000000..e16dede7 --- /dev/null +++ b/src/java/org/jivesoftware/spark/ui/RosterTreeCellRenderer.java @@ -0,0 +1,119 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.ui; + +import javax.swing.Icon; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTree; +import javax.swing.tree.DefaultTreeCellRenderer; + +import java.awt.Color; +import java.awt.Component; +import java.awt.FlowLayout; +import java.awt.Font; + +/** + * + */ +public class RosterTreeCellRenderer extends DefaultTreeCellRenderer { + private Object value; + private boolean isExpanded; + + /** + * Empty Constructor. + */ + public RosterTreeCellRenderer() { + } + + public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) { + this.value = value; + + final Component c = super.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus); + + isExpanded = expanded; + + setIcon(getCustomIcon()); + + // Root Nodes are always bold + RosterNode node = (RosterNode)value; + if (node.isGroup()) { + setFont(new Font("Dialog", Font.BOLD, 11)); + setText(node.getName() + " (" + node.getChildCount() + " online)"); + setForeground(new Color(64, 112, 196)); + setIcon(getCustomIcon()); + } + + if (node.isGroup()) { + return c; + } + + final JPanel panel = new JPanel(); + panel.setLayout(new FlowLayout(FlowLayout.LEFT)); + + final JLabel label = new JLabel(); + label.setFont(new Font("Arial", Font.PLAIN, 11)); + label.setText(node.getName()); + panel.add(label); + + final JLabel descriptionLabel = new JLabel(); + descriptionLabel.setFont(new Font("Dialog", Font.PLAIN, 11)); + descriptionLabel.setForeground(new Color(178, 181, 182)); + descriptionLabel.setText(" - I'm just chilling."); + + panel.add(descriptionLabel); + + if (selected) { + panel.setBackground(getBackgroundSelectionColor()); + } + else { + panel.setBackground(getBackgroundNonSelectionColor()); + } + return panel; + } + + private Icon getCustomIcon() { + if (value instanceof RosterNode) { + RosterNode node = (RosterNode)value; + if (isExpanded) { + return node.getOpenIcon(); + } + return node.getClosedIcon(); + } + return null; + } + + public Icon getClosedIcon() { + return getCustomIcon(); + } + + public Icon getDefaultClosedIcon() { + return getCustomIcon(); + } + + public Icon getDefaultLeafIcon() { + return getCustomIcon(); + } + + public Icon getDefaultOpenIcon() { + return getCustomIcon(); + } + + public Icon getLeafIcon() { + return getCustomIcon(); + } + + public Icon getOpenIcon() { + return getCustomIcon(); + } +} + + diff --git a/src/java/org/jivesoftware/spark/ui/Sparkler.java b/src/java/org/jivesoftware/spark/ui/Sparkler.java new file mode 100644 index 00000000..59224b93 --- /dev/null +++ b/src/java/org/jivesoftware/spark/ui/Sparkler.java @@ -0,0 +1,20 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.ui; + +public interface Sparkler { + + /** + * @param message + * @param decorator + */ + void decorateMessage(String message, SparklerDecorator decorator); +} diff --git a/src/java/org/jivesoftware/spark/ui/SparklerDecorator.java b/src/java/org/jivesoftware/spark/ui/SparklerDecorator.java new file mode 100644 index 00000000..cc22b353 --- /dev/null +++ b/src/java/org/jivesoftware/spark/ui/SparklerDecorator.java @@ -0,0 +1,40 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.ui; + +import javax.swing.JComponent; + +import java.util.HashMap; +import java.util.Map; + +public class SparklerDecorator { + + private Map urls = new HashMap(); + private Map popups = new HashMap(); + + + public void setURL(String matchedText, String url) { + urls.put(matchedText, url); + } + + public void setPopup(String matchedText, JComponent gui) { + popups.put(matchedText, gui); + } + + public Map getURLS() { + return urls; + } + + public Map getPopups() { + return popups; + } + +} diff --git a/src/java/org/jivesoftware/spark/ui/StringTransferHandler.java b/src/java/org/jivesoftware/spark/ui/StringTransferHandler.java new file mode 100644 index 00000000..eb3a366a --- /dev/null +++ b/src/java/org/jivesoftware/spark/ui/StringTransferHandler.java @@ -0,0 +1,68 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.ui; + +import javax.swing.JComponent; +import javax.swing.TransferHandler; + +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.StringSelection; +import java.awt.datatransfer.Transferable; +import java.awt.datatransfer.UnsupportedFlavorException; +import java.io.IOException; + +/** + * Used for String Drag and Drop functionality. + */ +public abstract class StringTransferHandler extends TransferHandler { + + protected abstract String exportString(JComponent c); + + protected abstract void importString(JComponent c, String str); + + protected abstract void cleanup(JComponent c, boolean remove); + + protected Transferable createTransferable(JComponent c) { + return new StringSelection(exportString(c)); + } + + public int getSourceActions(JComponent c) { + return COPY_OR_MOVE; + } + + public boolean importData(JComponent c, Transferable t) { + if (canImport(c, t.getTransferDataFlavors())) { + try { + String str = (String)t.getTransferData(DataFlavor.stringFlavor); + importString(c, str); + return true; + } + catch (UnsupportedFlavorException ufe) { + } + catch (IOException ioe) { + } + } + return false; + } + + protected void exportDone(JComponent c, Transferable data, int action) { + cleanup(c, action == MOVE); + } + + public boolean canImport(JComponent c, DataFlavor[] flavors) { + for (int i = 0; i < flavors.length; i++) { + if (DataFlavor.stringFlavor.equals(flavors[i])) { + return true; + } + } + return false; + } +} \ No newline at end of file diff --git a/src/java/org/jivesoftware/spark/ui/TranscriptAlert.java b/src/java/org/jivesoftware/spark/ui/TranscriptAlert.java new file mode 100644 index 00000000..c19af61c --- /dev/null +++ b/src/java/org/jivesoftware/spark/ui/TranscriptAlert.java @@ -0,0 +1,80 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.ui; + +import org.jivesoftware.spark.component.RolloverButton; + +import javax.swing.BorderFactory; +import javax.swing.Icon; +import javax.swing.JLabel; +import javax.swing.JPanel; + +import java.awt.Color; +import java.awt.Font; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; + +public class TranscriptAlert extends JPanel { + + private JLabel imageLabel = new JLabel(); + private JLabel titleLabel = new JLabel(); + private RolloverButton yesButton = new RolloverButton(); + private RolloverButton cancelButton = new RolloverButton(); + + public TranscriptAlert() { + setLayout(new GridBagLayout()); + + setBackground(new Color(250, 249, 242)); + add(imageLabel, new GridBagConstraints(0, 0, 1, 3, 0.0, 0.0, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + + add(titleLabel, new GridBagConstraints(1, 0, 2, 1, 1.0, 0.0, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + titleLabel.setFont(new Font("Verdana", Font.BOLD, 11)); + titleLabel.setForeground(new Color(211, 174, 102)); + + add(yesButton, new GridBagConstraints(1, 2, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, 5, 0, 5), 0, 0)); + add(cancelButton, new GridBagConstraints(2, 2, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, 5, 0, 5), 0, 0)); + + yesButton.setForeground(new Color(73, 113, 196)); + cancelButton.setForeground(new Color(73, 113, 196)); + + cancelButton.setFont(new Font("Verdana", Font.BOLD, 10)); + yesButton.setFont(new Font("Verdana", Font.BOLD, 10)); + + yesButton.setBorder(BorderFactory.createMatteBorder(0, 0, 1, 0, new Color(73, 113, 196))); + cancelButton.setBorder(BorderFactory.createMatteBorder(0, 0, 1, 0, new Color(73, 113, 196))); + + cancelButton.setVisible(false); + setBorder(BorderFactory.createMatteBorder(0, 0, 1, 0, Color.white)); + } + + public void setCancelButtonText(String cancelText) { + cancelButton.setText(cancelText); + } + + public void showCancelButton(boolean show) { + cancelButton.setVisible(show); + } + + public void setYesButtonText(String yesText) { + yesButton.setText(yesText); + } + + public void setTitle(String title) { + titleLabel.setText(title); + } + + public void setIcon(Icon icon) { + imageLabel.setIcon(icon); + } + + +} diff --git a/src/java/org/jivesoftware/spark/ui/TranscriptWindow.java b/src/java/org/jivesoftware/spark/ui/TranscriptWindow.java new file mode 100644 index 00000000..d1aa5123 --- /dev/null +++ b/src/java/org/jivesoftware/spark/ui/TranscriptWindow.java @@ -0,0 +1,536 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.ui; + +import org.jivesoftware.Spark; +import org.jivesoftware.resource.SparkRes; +import org.jivesoftware.smack.packet.Message; +import org.jivesoftware.smack.util.StringUtils; +import org.jivesoftware.spark.SparkManager; +import org.jivesoftware.spark.plugin.ContextMenuListener; +import org.jivesoftware.spark.preference.PreferenceManager; +import org.jivesoftware.spark.util.ModelUtil; +import org.jivesoftware.spark.util.log.Log; +import org.jivesoftware.sparkimpl.preference.chat.ChatPreference; +import org.jivesoftware.sparkimpl.preference.chat.ChatPreferences; + +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.JComponent; +import javax.swing.JFileChooser; +import javax.swing.JOptionPane; +import javax.swing.JPopupMenu; +import javax.swing.KeyStroke; +import javax.swing.UIManager; +import javax.swing.text.BadLocationException; +import javax.swing.text.Document; +import javax.swing.text.SimpleAttributeSet; +import javax.swing.text.Style; +import javax.swing.text.StyleConstants; +import javax.swing.text.StyledDocument; + +import java.awt.Color; +import java.awt.Component; +import java.awt.Font; +import java.awt.Toolkit; +import java.awt.datatransfer.StringSelection; +import java.awt.event.ActionEvent; +import java.awt.event.MouseEvent; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Iterator; +import java.util.List; + +/** + * The TranscriptWindow class. Provides a default implementation + * of a Chat Window. In general, extensions could override this class + * to offer more support within the chat, but should not be necessary. + */ +public class TranscriptWindow extends ChatArea { + + + private ChatPreferences chatPref; + private Date lastUpdated; + + /** + * The default font used in the chat window for all messages. + */ + private Font font = new Font("Dialog", Font.PLAIN, 12); + + + /** + * Creates a default instance of TranscriptWindow. + */ + public TranscriptWindow() { + setEditable(false); + + /* Load Preferences for this instance */ + PreferenceManager preferenceManager = SparkManager.getPreferenceManager(); + chatPref = (ChatPreferences)preferenceManager.getPreferenceData(ChatPreference.NAMESPACE); + + addMouseListener(this); + addMouseMotionListener(this); + setDragEnabled(true); + + final TranscriptWindow window = this; + addContextMenuListener(new ContextMenuListener() { + public void poppingUp(Object component, JPopupMenu popup) { + Action printAction = new AbstractAction() { + public void actionPerformed(ActionEvent actionEvent) { + SparkManager.printChatTranscript(window); + } + }; + + + Action clearAction = new AbstractAction() { + public void actionPerformed(ActionEvent actionEvent) { + clear(); + } + }; + + + printAction.putValue(Action.NAME, "Print"); + printAction.putValue(Action.SMALL_ICON, SparkRes.getImageIcon(SparkRes.PRINTER_IMAGE_16x16)); + + clearAction.putValue(Action.NAME, "Clear"); + clearAction.putValue(Action.SMALL_ICON, SparkRes.getImageIcon(SparkRes.ERASER_IMAGE)); + popup.addSeparator(); + popup.add(printAction); + + popup.add(clearAction); + } + + public void poppingDown(JPopupMenu popup) { + + } + + public boolean handleDefaultAction(MouseEvent e) { + return false; + } + }); + + // Make sure ctrl-c works + getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("control c"), "copy"); + + getActionMap().put("copy", new AbstractAction("copy") { + public void actionPerformed(ActionEvent evt) { + StringSelection ss = new StringSelection(getSelectedText()); + Toolkit.getDefaultToolkit().getSystemClipboard().setContents(ss, null); + } + }); + } + + public void addComponent(Component component) { + StyledDocument doc = (StyledDocument)getDocument(); + + // The image must first be wrapped in a style + Style style = doc.addStyle("StyleName", null); + + + StyleConstants.setComponent(style, component); + + // Insert the image at the end of the text + try { + doc.insertString(doc.getLength(), "ignored text", style); + doc.insertString(doc.getLength(), "\n", null); + } + catch (BadLocationException e) { + Log.error(e); + } + } + + /** + * Create and insert a message from the current user. + * + * @param userid the userid of the current agent. + * @param message the agents message to insert. + */ + public void insertMessage(String userid, String message) { + + try { + String date = getDate(null); + + // Agent color is always blue + StyleConstants.setBold(styles, false); + StyleConstants.setForeground(styles, (Color)UIManager.get("User.foreground")); + final Document doc = getDocument(); + styles.removeAttribute("link"); + + StyleConstants.setFontSize(styles, font.getSize()); + doc.insertString(doc.getLength(), date + userid + ": ", styles); + + // Reset Styles for message + StyleConstants.setBold(styles, false); + setText(message); + insertText("\n"); + } + catch (BadLocationException e) { + Log.error("Error message.", e); + } + } + + /** + * Create and insert a message from the current user. + * + * @param userid the userid of the current agent. + * @param message the agents message to insert. + * @param datePosted the date the message was posted. + */ + public void insertMessage(String userid, String message, Date datePosted) { + + try { + String date = getDate(datePosted); + + // Agent color is always blue + StyleConstants.setBold(styles, false); + StyleConstants.setForeground(styles, (Color)UIManager.get("User.foreground")); + final Document doc = getDocument(); + styles.removeAttribute("link"); + + StyleConstants.setFontSize(styles, font.getSize()); + doc.insertString(doc.getLength(), date + userid + ": ", styles); + + // Reset Styles for message + StyleConstants.setBold(styles, false); + setText(message); + insertText("\n"); + } + catch (BadLocationException e) { + Log.error("Error message.", e); + } + } + + public void insertCustomMessage(String prefix, String message) { + try { + // Agent color is always blue + StyleConstants.setBold(styles, false); + StyleConstants.setForeground(styles, (Color)UIManager.get("User.foreground")); + final Document doc = getDocument(); + styles.removeAttribute("link"); + + StyleConstants.setFontSize(styles, font.getSize()); + if (prefix != null) { + doc.insertString(doc.getLength(), prefix + ": ", styles); + } + + // Reset Styles for message + StyleConstants.setBold(styles, false); + setText(message); + insertText("\n"); + } + catch (BadLocationException e) { + Log.error("Error message.", e); + } + } + + public void insertCustomOtherMessage(String prefix, String message) { + try { + StyleConstants.setBold(styles, false); + StyleConstants.setForeground(styles, (Color)UIManager.get("OtherUser.foreground")); + final Document doc = getDocument(); + styles.removeAttribute("link"); + + StyleConstants.setFontSize(styles, font.getSize()); + doc.insertString(doc.getLength(), prefix + ": ", styles); + + StyleConstants.setBold(styles, false); + setText(message); + insertText("\n"); + } + catch (BadLocationException ex) { + Log.error("Error message.", ex); + } + } + + /** + * Create and insert a message from a customer. + * + * @param userid the userid of the customer. + * @param message the message from the customer. + * @param date the date the message was posted, can be null. + */ + public void insertOthersMessage(String userid, String message, Date date) { + try { + String theDate = getDate(date); + + StyleConstants.setBold(styles, false); + StyleConstants.setForeground(styles, (Color)UIManager.get("OtherUser.foreground")); + final Document doc = getDocument(); + styles.removeAttribute("link"); + + StyleConstants.setFontSize(styles, font.getSize()); + doc.insertString(doc.getLength(), theDate + userid + ": ", styles); + + StyleConstants.setBold(styles, false); + setText(message); + insertText("\n"); + } + catch (BadLocationException ex) { + Log.error("Error message.", ex); + } + } + + + /** + * Create and insert a notification message. A notification message generally is a + * presence update, but can be used for most anything related to the room. + * + * @param message the information message to insert. + */ + public synchronized void insertNotificationMessage(String message) { + try { + + Color notificationColor = (Color)UIManager.get("Notification.foreground"); + + // Agent color is always blue + StyleConstants.setBold(styles, false); + StyleConstants.setForeground(styles, notificationColor); + final Document doc = getDocument(); + styles.removeAttribute("link"); + + StyleConstants.setFontSize(styles, font.getSize()); + doc.insertString(doc.getLength(), "", styles); + + // Reset Styles for message + StyleConstants.setBold(styles, false); + setForeground(notificationColor); + setText(message); + insertText("\n"); + setForeground(Color.black); + } + catch (BadLocationException ex) { + Log.error("Error message.", ex); + } + } + + /** + * Creates and inserts an error message. + * + * @param message the information message to insert. + */ + public void insertErrorMessage(String message) { + try { + // Agent color is always blue + StyleConstants.setBold(styles, false); + StyleConstants.setForeground(styles, (Color)UIManager.get("Error.foreground")); + final Document doc = getDocument(); + styles.removeAttribute("link"); + + StyleConstants.setFontSize(styles, font.getSize()); + doc.insertString(doc.getLength(), "", styles); + + // Reset Styles for message + StyleConstants.setBold(styles, false); + setForeground(Color.red); + setText(message); + insertText("\n"); + setForeground(Color.black); + } + catch (BadLocationException ex) { + Log.error("Error message.", ex); + } + } + + /** + * Create and insert a question message. A question message is specified by the + * end customer during the initial request. + * + * @param question the question asked by the customer. + */ + public void insertQuestionMessage(String question) { + + try { + StyleConstants.setBold(styles, false); + StyleConstants.setForeground(styles, (Color)UIManager.get("Question.foreground")); + final Document doc = getDocument(); + styles.removeAttribute("link"); + + StyleConstants.setFontSize(styles, font.getSize()); + StyleConstants.setFontFamily(styles, font.getFamily()); + doc.insertString(doc.getLength(), "Question - ", styles); + + StyleConstants.setBold(styles, false); + setText(question); + insertText("\n"); + } + catch (BadLocationException e) { + Log.error("Error message.", e); + } + + } + + + /** + * Returns the formatted date. + * + * @param insertDate the date to format. + * @return the formatted date. + */ + private String getDate(Date insertDate) { + chatPref = (ChatPreferences)SparkManager.getPreferenceManager().getPreferenceData(ChatPreference.NAMESPACE); + + if (insertDate == null) { + insertDate = new Date(); + } + + StyleConstants.setFontFamily(styles, font.getFontName()); + StyleConstants.setFontSize(styles, font.getSize()); + + if (chatPref.showDatesInChat()) { + final SimpleDateFormat formatter = new SimpleDateFormat("h:mm a"); + final String date = formatter.format(insertDate); + + return "[" + date + "] "; + } + lastUpdated = insertDate; + return ""; + } + + + /** + * Return the last time the TranscriptWindow was updated. + * + * @return the last time the TranscriptWindow was updated. + */ + public Date getLastUpdated() { + return lastUpdated; + } + + /** + * Inserts a history message. + * + * @param userid the userid of the sender. + * @param message the message to insert. + * @param date the Date object created when the message was delivered. + */ + public void insertHistoryMessage(String userid, String message, Date date) { + try { + String value = ""; + + final SimpleDateFormat formatter = new SimpleDateFormat("MM/dd/yyyy h:mm a"); + value = "[" + formatter.format(date) + "] "; + value = value + userid + ": "; + + // Agent color is always blue + StyleConstants.setBold(styles, false); + StyleConstants.setForeground(styles, Color.LIGHT_GRAY); + final Document doc = getDocument(); + styles.removeAttribute("link"); + + StyleConstants.setFontSize(styles, font.getSize()); + doc.insertString(doc.getLength(), value, styles); + + // Reset Styles for message + StyleConstants.setBold(styles, false); + setForeground(Color.LIGHT_GRAY); + setText(message); + setForeground(Color.BLACK); + insertText("\n"); + } + catch (BadLocationException ex) { + Log.error("Error message.", ex); + } + } + + /** + * Disable the entire TranscriptWindow and visually represent + * it as disabled. + */ + public void showDisabledWindowUI() { + final Document document = getDocument(); + final SimpleAttributeSet attrs = new SimpleAttributeSet(); + StyleConstants.setForeground(attrs, Color.LIGHT_GRAY); + + final int length = document.getLength(); + StyledDocument styledDocument = getStyledDocument(); + styledDocument.setCharacterAttributes(0, length, attrs, false); + } + + /** + * Persist a current transcript. + * + * @param fileName the name of the file to save the transcript as. Note: This can be modified by the user. + * @param transcript the collection of transcript. + * @param headerData the string to prepend to the transcript. + * @see ChatRoom#getTranscripts() + */ + public void saveTranscript(String fileName, List transcript, String headerData) { + try { + SimpleDateFormat formatter; + + File defaultSaveFile = new File(new File(Spark.getUserHome()), fileName); + final JFileChooser fileChooser = new JFileChooser(defaultSaveFile); + fileChooser.setSelectedFile(defaultSaveFile); + + // Show save dialog; this method does not return until the dialog is closed + int result = fileChooser.showSaveDialog(this); + final File selFile = fileChooser.getSelectedFile(); + + if (selFile != null && result == JFileChooser.APPROVE_OPTION) { + final StringBuffer buf = new StringBuffer(); + final Iterator transcripts = transcript.iterator(); + buf.append(""); + if (headerData != null) { + buf.append(headerData); + } + + buf.append(""); + while (transcripts.hasNext()) { + final Message message = (Message)transcripts.next(); + String from = message.getFrom(); + if (from == null) { + from = chatPref.getNickname(); + } + + if (Message.Type.GROUP_CHAT == message.getType()) { + if (ModelUtil.hasLength(StringUtils.parseResource(from))) { + from = StringUtils.parseResource(from); + } + } + + final String body = message.getBody(); + final Date insertionDate = (Date)message.getProperty("insertionDate"); + formatter = new SimpleDateFormat("hh:mm:ss"); + + String value = ""; + if (insertionDate != null) { + value = "[" + formatter.format(insertionDate) + "] "; + } + buf.append(""); + + } + buf.append("
        ").append(value).append("").append(from).append(": ").append(body).append("
        "); + final BufferedWriter writer = new BufferedWriter(new FileWriter(selFile)); + writer.write(buf.toString()); + writer.close(); + JOptionPane.showMessageDialog(SparkManager.getMainWindow(), "Chat transcript has been saved.", + "Chat Transcript Saved", JOptionPane.INFORMATION_MESSAGE); + } + } + catch (Exception ex) { + Log.error("Unable to save chat transcript.", ex); + JOptionPane.showMessageDialog(SparkManager.getMainWindow(), "Could not save transcript.", "Error", JOptionPane.ERROR_MESSAGE); + } + + } + + public void setFont(Font font) { + this.font = font; + } + + public Font getFont() { + return font; + } + +} \ No newline at end of file diff --git a/src/java/org/jivesoftware/spark/ui/VCardPanel.java b/src/java/org/jivesoftware/spark/ui/VCardPanel.java new file mode 100644 index 00000000..33fea087 --- /dev/null +++ b/src/java/org/jivesoftware/spark/ui/VCardPanel.java @@ -0,0 +1,170 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.ui; + +import org.jivesoftware.smack.PacketCollector; +import org.jivesoftware.smack.filter.PacketIDFilter; +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smackx.packet.Time; +import org.jivesoftware.smackx.packet.VCard; +import org.jivesoftware.spark.SparkManager; +import org.jivesoftware.spark.component.borders.PartialLineBorder; +import org.jivesoftware.spark.ui.rooms.ChatRoomImpl; +import org.jivesoftware.spark.util.ModelUtil; +import org.jivesoftware.spark.util.SwingWorker; +import org.jivesoftware.sparkimpl.profile.VCardManager; + +import javax.swing.ImageIcon; +import javax.swing.JLabel; +import javax.swing.JPanel; + +import java.awt.Color; +import java.awt.Font; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; + +public class VCardPanel extends JPanel { + + private ChatRoomImpl chatRoom; + private JLabel avatarImage; + + public VCardPanel(final ChatRoomImpl chatRoom) { + setLayout(new GridBagLayout()); + setOpaque(false); + + this.chatRoom = chatRoom; + avatarImage = new JLabel(); + + SwingWorker worker = new SwingWorker() { + VCard vcard = null; + + public Object construct() { + vcard = SparkManager.getVCardManager().getVCard(chatRoom.getParticipantJID()); + return vcard; + } + + public void finished() { + if (vcard == null) { + // Do nothing. + return; + } + + byte[] bytes = vcard.getAvatar(); + if (bytes != null) { + try { + ImageIcon icon = new ImageIcon(bytes); + icon = VCardManager.scale(icon); + if (icon.getIconWidth() > 0) { + avatarImage.setIcon(icon); + avatarImage.setBorder(new PartialLineBorder(Color.LIGHT_GRAY, 1)); + } + setupUI(vcard); + } + catch (Exception e) { + } + } + } + }; + + worker.start(); + } + + private void setupUI(VCard vcard) { + add(avatarImage, new GridBagConstraints(0, 0, 1, 4, 0.0, 0.0, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + + String city = vcard.getField("CITY"); + String state = vcard.getField("STATE"); + String country = vcard.getField("COUNTRY"); + String firstName = vcard.getFirstName(); + if (firstName == null) { + firstName = ""; + } + + String lastName = vcard.getLastName(); + if (lastName == null) { + lastName = ""; + } + + String title = vcard.getField("TITLE"); + + final JLabel usernameLabel = new JLabel(); + usernameLabel.setFont(new Font("Dialog", Font.BOLD, 12)); + + usernameLabel.setForeground(Color.DARK_GRAY); + if (ModelUtil.hasLength(firstName) && ModelUtil.hasLength(lastName)) { + usernameLabel.setText(firstName + " " + lastName); + } + else { + usernameLabel.setText(chatRoom.getTabTitle()); + } + + + add(usernameLabel, new GridBagConstraints(1, 0, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(2, 5, 2, 5), 0, 0)); + + final JLabel locationLabel = new JLabel(); + + if (ModelUtil.hasLength(city) && ModelUtil.hasLength(state) && ModelUtil.hasLength(country)) { + locationLabel.setText(" - " + city + ", " + state + " " + country); + } + add(locationLabel, new GridBagConstraints(2, 0, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(2, 5, 2, 5), 0, 0)); + + + final JLabel titleLabel = new JLabel(title); + if (ModelUtil.hasLength(title)) { + add(titleLabel, new GridBagConstraints(1, 1, 2, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(2, 5, 2, 5), 0, 0)); + } + + String phone = vcard.getPhoneWork("VOICE"); + if (ModelUtil.hasLength(phone)) { + final JLabel phoneNumber = new JLabel("Work: " + phone); + add(phoneNumber, new GridBagConstraints(1, 2, 2, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(2, 5, 2, 5), 0, 0)); + } + + final JLabel localTime = new JLabel(); + add(localTime, new GridBagConstraints(1, 3, 2, 1, 0.0, 0.0, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, new Insets(2, 5, 2, 5), 0, 0)); + + + final Time time = new Time(); + time.setType(IQ.Type.GET); + + String fullJID = SparkManager.getUserManager().getFullJID(chatRoom.getParticipantJID()); + if (fullJID == null) { + return; + } + time.setTo(fullJID); + + final PacketCollector packetCollector = SparkManager.getConnection().createPacketCollector(new PacketIDFilter(time.getPacketID())); + + SwingWorker timeThread = new SwingWorker() { + IQ timeResult = null; + + public Object construct() { + SparkManager.getConnection().sendPacket(time); + timeResult = (IQ)packetCollector.nextResult(); + return timeResult; + } + + public void finished() { + // Wait up to 5 seconds for a result. + + if (timeResult != null && timeResult.getType() == IQ.Type.RESULT) { + Time t = (Time)timeResult; + localTime.setText("Local Time: " + t.getDisplay()); + } + } + }; + + timeThread.start(); + } + + +} diff --git a/src/java/org/jivesoftware/spark/ui/conferences/BannedUsers.java b/src/java/org/jivesoftware/spark/ui/conferences/BannedUsers.java new file mode 100644 index 00000000..6a265e3c --- /dev/null +++ b/src/java/org/jivesoftware/spark/ui/conferences/BannedUsers.java @@ -0,0 +1,139 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.ui.conferences; + +import org.jivesoftware.resource.SparkRes; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smackx.muc.Affiliate; +import org.jivesoftware.smackx.muc.MultiUserChat; +import org.jivesoftware.spark.component.renderer.ListIconRenderer; +import org.jivesoftware.spark.ui.ChatRoom; +import org.jivesoftware.spark.ui.rooms.GroupChatRoom; +import org.jivesoftware.spark.util.log.Log; + +import javax.swing.DefaultListModel; +import javax.swing.ImageIcon; +import javax.swing.JList; +import javax.swing.JMenuItem; +import javax.swing.JPanel; +import javax.swing.JPopupMenu; + +import java.awt.BorderLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.util.Iterator; + + +/** + * Handles unbanning banned users in Chat Rooms. + */ +public class BannedUsers extends JPanel { + private GroupChatRoom chatRoom; + private MultiUserChat chat; + + private DefaultListModel listModel = new DefaultListModel(); + private JList list = new JList(listModel); + private JMenuItem unBanMenuItem = new JMenuItem("Unban"); + + /** + * Construct UI + */ + public BannedUsers() { + setLayout(new BorderLayout()); + list.setCellRenderer(new ListIconRenderer()); + add(list, BorderLayout.CENTER); + // Respond to Double-Click in Agent List to start a chat + list.addMouseListener(new MouseAdapter() { + public void mousePressed(MouseEvent evt) { + if (evt.isPopupTrigger()) { + int index = list.locationToIndex(evt.getPoint()); + list.setSelectedIndex(index); + ImageIcon icon = (ImageIcon)list.getModel().getElementAt(index); + String jid = icon.getDescription(); + showPopup(evt, jid); + } + } + + public void mouseReleased(MouseEvent evt) { + if (evt.isPopupTrigger()) { + int index = list.locationToIndex(evt.getPoint()); + list.setSelectedIndex(index); + ImageIcon icon = (ImageIcon)list.getModel().getElementAt(index); + String jid = icon.getDescription(); + showPopup(evt, jid); + } + } + }); + + unBanMenuItem.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + int index = list.getSelectedIndex(); + ImageIcon icon = (ImageIcon)list.getModel().getElementAt(index); + String jid = icon.getDescription(); + try { + chat.grantMembership(jid); + } + catch (XMPPException memEx) { + Log.error("Error granting membership", memEx); + } + listModel.removeElementAt(index); + + } + }); + } + + /** + * Binds a ChatRoom to listen to. + * + * @param cRoom the group chat room. + */ + public void setChatRoom(ChatRoom cRoom) { + this.chatRoom = (GroupChatRoom)cRoom; + chat = chatRoom.getMultiUserChat(); + } + + /** + * Loads all banned users in a ChatRoom. + */ + public void loadAllBannedUsers() { + // Clear all elements from model + listModel.clear(); + + Iterator bannedUsers = null; + try { + bannedUsers = chat.getOutcasts().iterator(); + } + catch (XMPPException e) { + Log.error("Error loading all banned users", e); + } + + while (bannedUsers != null && bannedUsers.hasNext()) { + Affiliate bannedUser = (Affiliate)bannedUsers.next(); + ImageIcon icon = SparkRes.getImageIcon(SparkRes.STAR_RED_IMAGE); + icon.setDescription(bannedUser.getJid()); + listModel.addElement(icon); + } + } + + /** + * Responsible for popping up the menu items. + * + * @param e the MouseEvent that triggered it. + * @param jid the JID to handle. + */ + private void showPopup(MouseEvent e, String jid) { + final JPopupMenu popup = new JPopupMenu(); + popup.add(unBanMenuItem); + popup.show(this, e.getX(), e.getY()); + } +} diff --git a/src/java/org/jivesoftware/spark/ui/conferences/Bookmark.java b/src/java/org/jivesoftware/spark/ui/conferences/Bookmark.java new file mode 100644 index 00000000..96ba2ee6 --- /dev/null +++ b/src/java/org/jivesoftware/spark/ui/conferences/Bookmark.java @@ -0,0 +1,42 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.ui.conferences; + +public class Bookmark { + + private String serviceName; + private String roomJID; + private String roomName; + + public String getServiceName() { + return serviceName; + } + + public void setServiceName(String serviceName) { + this.serviceName = serviceName; + } + + public String getRoomJID() { + return roomJID; + } + + public void setRoomJID(String roomJID) { + this.roomJID = roomJID; + } + + public String getRoomName() { + return roomName; + } + + public void setRoomName(String roomName) { + this.roomName = roomName; + } +} diff --git a/src/java/org/jivesoftware/spark/ui/conferences/BookmarkedConferences.java b/src/java/org/jivesoftware/spark/ui/conferences/BookmarkedConferences.java new file mode 100644 index 00000000..8e0fdf48 --- /dev/null +++ b/src/java/org/jivesoftware/spark/ui/conferences/BookmarkedConferences.java @@ -0,0 +1,627 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.ui.conferences; + +import org.dom4j.Document; +import org.dom4j.DocumentException; +import org.dom4j.DocumentHelper; +import org.dom4j.Element; +import org.dom4j.io.SAXReader; +import org.dom4j.io.XMLWriter; +import org.jivesoftware.MainWindowListener; +import org.jivesoftware.Spark; +import org.jivesoftware.resource.SparkRes; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smackx.ServiceDiscoveryManager; +import org.jivesoftware.smackx.muc.MultiUserChat; +import org.jivesoftware.smackx.packet.DiscoverInfo; +import org.jivesoftware.smackx.packet.DiscoverItems; +import org.jivesoftware.spark.SparkManager; +import org.jivesoftware.spark.component.JiveTreeCellRenderer; +import org.jivesoftware.spark.component.JiveTreeNode; +import org.jivesoftware.spark.component.RolloverButton; +import org.jivesoftware.spark.component.Tree; +import org.jivesoftware.spark.plugin.ContextMenuListener; +import org.jivesoftware.spark.util.GraphicUtils; +import org.jivesoftware.spark.util.ResourceUtils; +import org.jivesoftware.spark.util.SwingWorker; +import org.jivesoftware.spark.util.log.Log; + +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.BorderFactory; +import javax.swing.JCheckBoxMenuItem; +import javax.swing.JLabel; +import javax.swing.JMenuItem; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JPopupMenu; +import javax.swing.JScrollPane; +import javax.swing.JTextField; +import javax.swing.tree.DefaultTreeModel; +import javax.swing.tree.TreePath; + +import java.awt.Color; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.event.ActionEvent; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +/** + * BookmarkedConferences is used to display the UI for all bookmarked conference rooms. + */ +public class BookmarkedConferences extends JPanel { + private Tree tree; + + private JiveTreeNode rootNode; + + private Collection mucServices; + + private Set autoJoinRooms = new HashSet(); + + private List listeners = new ArrayList(); + + /** + * Initialize Conference UI. + */ + public BookmarkedConferences() { + setBorder(BorderFactory.createLineBorder(Color.LIGHT_GRAY)); + setLayout(new GridBagLayout()); + + add(getServicePanel(), new GridBagConstraints(0, 0, 2, 1, 1.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); + + rootNode = new JiveTreeNode("Conference Services"); + tree = new Tree(rootNode) { + protected void setExpandedState(TreePath path, boolean state) { + // Ignore all collapse requests; collapse events will not be fired + if (state) { + super.setExpandedState(path, state); + } + } + }; + + tree.addMouseListener(new MouseAdapter() { + public void mouseEntered(MouseEvent mouseEvent) { + tree.setCursor(GraphicUtils.HAND_CURSOR); + } + + public void mouseExited(MouseEvent mouseEvent) { + tree.setCursor(GraphicUtils.DEFAULT_CURSOR); + } + }); + + + JScrollPane scrollPane = new JScrollPane(tree); + add(scrollPane, new GridBagConstraints(0, 2, 2, 1, 1.0, 1.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0)); + + // Add all registered services. + addRegisteredServices(); + + + tree.setCellRenderer(new JiveTreeCellRenderer()); + tree.putClientProperty("JTree.lineStyle", "None"); + tree.setRootVisible(false); + + tree.addMouseListener(new MouseAdapter() { + public void mouseClicked(MouseEvent mouseEvent) { + if (mouseEvent.getClickCount() == 2) { + TreePath path = tree.getPathForLocation(mouseEvent.getX(), mouseEvent.getY()); + if (path == null) { + return; + } + JiveTreeNode node = (JiveTreeNode)path.getLastPathComponent(); + if (node != null && node.getAllowsChildren()) { + browseRooms((String)node.getUserObject()); + } + else if (node != null) { + String roomJID = node.getAssociatedObject().toString(); + + ConferenceUtils.autoJoinConferenceRoom(node.getUserObject().toString(), roomJID, null); + } + } + } + + public void mouseReleased(MouseEvent mouseEvent) { + checkPopup(mouseEvent); + } + + public void mousePressed(MouseEvent mouseEvent) { + checkPopup(mouseEvent); + } + } + ); + // Set background + Color menuBarColor = new Color(235, 233, 237); + setBackground(menuBarColor); + + // Add closing listener + SparkManager.getMainWindow().addMainWindowListener(new MainWindowListener() { + public void shutdown() { + // Save autoJoins + File sparkHom = new File(Spark.getUserHome(), "Spark"); + File conferenceXML = new File(sparkHom, "conferences.xml"); + Element element = DocumentHelper.createElement("conferences"); + Iterator iter = autoJoinRooms.iterator(); + while (iter.hasNext()) { + element.addElement("auto-join").setText((String)iter.next()); + } + try { + XMLWriter saxWriter = new XMLWriter(new FileOutputStream(conferenceXML)); + saxWriter.write(element); + } + catch (IOException e) { + Log.error(e); + } + } + + public void mainWindowActivated() { + + } + + public void mainWindowDeactivated() { + + } + }); + } + + private void checkPopup(MouseEvent mouseEvent) { + // Handle no path for x y coordinates + if (tree.getPathForLocation(mouseEvent.getX(), mouseEvent.getY()) == null) { + return; + } + + final JiveTreeNode node = (JiveTreeNode)tree.getPathForLocation(mouseEvent.getX(), mouseEvent.getY()).getLastPathComponent(); + + if (mouseEvent.isPopupTrigger() && node != null) { + JPopupMenu popupMenu = new JPopupMenu(); + + // Define service actions + Action browseAction = new AbstractAction() { + public void actionPerformed(ActionEvent actionEvent) { + browseRooms(node.toString()); + } + }; + browseAction.putValue(Action.NAME, "Browse Service"); + browseAction.putValue(Action.SMALL_ICON, SparkRes.getImageIcon(SparkRes.SMALL_DATA_FIND_IMAGE)); + + Action removeServiceAction = new AbstractAction() { + public void actionPerformed(ActionEvent actionEvent) { + DefaultTreeModel treeModel = (DefaultTreeModel)tree.getModel(); + treeModel.removeNodeFromParent(node); + } + }; + removeServiceAction.putValue(Action.NAME, "Remove Service"); + removeServiceAction.putValue(Action.SMALL_ICON, SparkRes.getImageIcon(SparkRes.SMALL_DELETE)); + + JMenuItem browseServiceMenu = new JMenuItem(browseAction); + JMenuItem removeServiceMenu = new JMenuItem(removeServiceAction); + + // Define room actions + Action joinRoomAction = new AbstractAction() { + public void actionPerformed(ActionEvent actionEvent) { + String roomName = node.getUserObject().toString(); + String roomJID = node.getAssociatedObject().toString(); + ConferenceUtils.autoJoinConferenceRoom(roomName, roomJID, null); + } + }; + + joinRoomAction.putValue(Action.NAME, "Join Room"); + joinRoomAction.putValue(Action.SMALL_ICON, SparkRes.getImageIcon(SparkRes.SMALL_USER_ENTER)); + + Action removeRoomAction = new AbstractAction() { + public void actionPerformed(ActionEvent actionEvent) { + DefaultTreeModel treeModel = (DefaultTreeModel)tree.getModel(); + treeModel.removeNodeFromParent(node); + String roomJID = node.getAssociatedObject().toString(); + autoJoinRooms.remove(roomJID); + } + }; + removeRoomAction.putValue(Action.NAME, "Remove Bookmark"); + removeRoomAction.putValue(Action.SMALL_ICON, SparkRes.getImageIcon(SparkRes.DELETE_BOOKMARK_ICON)); + + + JMenuItem joinRoomMenu = new JMenuItem(joinRoomAction); + JMenuItem removeRoomMenu = new JMenuItem(removeRoomAction); + + + if (node != null && node.getAllowsChildren()) { + popupMenu.add(browseServiceMenu); + popupMenu.add(removeServiceMenu); + } + else if (node != null) { + popupMenu.add(joinRoomMenu); + popupMenu.add(removeRoomMenu); + popupMenu.addSeparator(); + + Action autoJoin = new AbstractAction() { + public void actionPerformed(ActionEvent e) { + String roomJID = node.getAssociatedObject().toString(); + if (autoJoinRooms.contains(roomJID)) { + autoJoinRooms.remove(roomJID); + } + else { + autoJoinRooms.add(roomJID); + } + } + }; + + autoJoin.putValue(Action.NAME, "Join on startup"); + + JCheckBoxMenuItem item = new JCheckBoxMenuItem(autoJoin); + String roomJID = node.getAssociatedObject().toString(); + item.setSelected(autoJoinRooms.contains(roomJID)); + popupMenu.add(item); + + // Define service actions + Action roomInfoAction = new AbstractAction() { + public void actionPerformed(ActionEvent actionEvent) { + String roomJID = node.getAssociatedObject().toString(); + RoomBrowser roomBrowser = new RoomBrowser(); + roomBrowser.displayRoomInformation(roomJID); + } + }; + + roomInfoAction.putValue(Action.NAME, "View Room Info"); + roomInfoAction.putValue(Action.SMALL_ICON, SparkRes.getImageIcon(SparkRes.SMALL_DATA_FIND_IMAGE)); + popupMenu.add(roomInfoAction); + } + + // Fire menu listeners + fireContextMenuListeners(popupMenu, node); + + // Display popup menu. + popupMenu.show(tree, mouseEvent.getX(), mouseEvent.getY()); + } + } + + public void browseRooms(String serviceName) { + ConferenceRooms rooms = new ConferenceRooms(tree, serviceName); + rooms.invoke(); + } + + private void addRegisteredServices() { + SwingWorker worker = new SwingWorker() { + + public Object construct() { + try { + + mucServices = MultiUserChat.getServiceNames(SparkManager.getConnection()); + + } + catch (XMPPException e) { + Log.error("Unable to load MUC Service Names.", e); + } + return mucServices; + } + + public void finished() { + if (mucServices == null) { + return; + } + + Iterator services = mucServices.iterator(); + while (services.hasNext()) { + String service = (String)services.next(); + if (!hasService(service)) { + addServiceToList(service); + } + } + } + }; + + worker.start(); + } + + /** + * Adds a new service (ex. conferences@jabber.org) to the services list. + * + * @param service the new service. + * @return the new service node created. + */ + public JiveTreeNode addServiceToList(String service) { + final JiveTreeNode serviceNode = new JiveTreeNode(service, true, SparkRes.getImageIcon(SparkRes.SERVER_ICON)); + rootNode.add(serviceNode); + final DefaultTreeModel model = (DefaultTreeModel)tree.getModel(); + model.nodeStructureChanged(rootNode); + // expand the tree for displaying + for (int i = 0; i <= tree.getRowCount(); i++) { + tree.expandPath(tree.getPathForRow(i)); + } + return serviceNode; + } + + /** + * Adds a new bookmark to a particular service node. + * + * @param serviceNode the service node. + * @param roomName the name of the room to bookmark. + * @param roomJID the jid of the room. + * @return the new bookmark created. + */ + public JiveTreeNode addBookmark(JiveTreeNode serviceNode, String roomName, String roomJID) { + JiveTreeNode roomNode = new JiveTreeNode(roomName, false, SparkRes.getImageIcon(SparkRes.BOOKMARK_ICON)); + roomNode.setAssociatedObject(roomJID); + serviceNode.add(roomNode); + final DefaultTreeModel model = (DefaultTreeModel)tree.getModel(); + model.nodeStructureChanged(serviceNode); + return roomNode; + } + + + private JPanel getServicePanel() { + final JPanel servicePanel = new JPanel(); + servicePanel.setOpaque(false); + servicePanel.setLayout(new GridBagLayout()); + + final JLabel serviceLabel = new JLabel(); + final RolloverButton addButton = new RolloverButton(SparkRes.getImageIcon(SparkRes.SMALL_ADD_IMAGE)); + addButton.setToolTipText("Add conference service."); + + final JTextField serviceField = new JTextField(); + servicePanel.add(serviceLabel, new GridBagConstraints(0, 0, 2, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(0, 5, 2, 5), 0, 0)); + servicePanel.add(serviceField, new GridBagConstraints(0, 1, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(0, 5, 2, 5), 0, 0)); + servicePanel.add(addButton, new GridBagConstraints(1, 1, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, 5, 2, 5), 0, 0)); + + // Add resource utils + ResourceUtils.resLabel(serviceLabel, serviceField, "&Add Conference Service"); + + final Action conferenceAction = new AbstractAction() { + public void actionPerformed(ActionEvent e) { + final String conferenceService = serviceField.getText(); + if (hasService(conferenceService)) { + JOptionPane.showMessageDialog(null, "Service is already in your service list.", "Service Exists", JOptionPane.ERROR_MESSAGE); + serviceField.setText(""); + } + else { + final List serviceList = new ArrayList(); + serviceField.setText("Searching. Please wait..."); + serviceField.setEnabled(false); + addButton.setEnabled(false); + SwingWorker worker = new SwingWorker() { + DiscoverInfo discoInfo; + + public Object construct() { + ServiceDiscoveryManager discoManager = ServiceDiscoveryManager.getInstanceFor(SparkManager.getConnection()); + + try { + discoInfo = discoManager.discoverInfo(conferenceService); + Iterator iter = discoInfo.getIdentities(); + while (iter.hasNext()) { + DiscoverInfo.Identity identity = (DiscoverInfo.Identity)iter.next(); + if ("conference".equals(identity.getCategory())) { + serviceList.add(conferenceService); + break; + } + else if ("server".equals(identity.getCategory())) { + try { + Collection services = getConferenceServices(conferenceService); + Iterator serviceIterator = services.iterator(); + while (serviceIterator.hasNext()) { + serviceList.add((String)serviceIterator.next()); + } + } + catch (Exception e1) { + Log.error("Unable to load conference services in server.", e1); + } + + } + } + } + catch (XMPPException e1) { + Log.error("Error in disco discovery.", e1); + } + return "ok"; + } + + public void finished() { + if (discoInfo != null) { + Iterator services = serviceList.iterator(); + while (services.hasNext()) { + addServiceToList((String)services.next()); + } + serviceField.setText(""); + serviceField.setEnabled(true); + addButton.setEnabled(true); + } + else { + JOptionPane.showMessageDialog(null, "Service could not be located.", "Unavailable Service", JOptionPane.ERROR_MESSAGE); + serviceField.setText(""); + serviceField.setEnabled(true); + addButton.setEnabled(true); + } + } + }; + worker.start(); + } + } + }; + + addButton.addActionListener(conferenceAction); + + + serviceField.addKeyListener(new KeyAdapter() { + public void keyPressed(KeyEvent e) { + if (e.getKeyChar() == KeyEvent.VK_ENTER) { + conferenceAction.actionPerformed(null); + } + } + }); + + return servicePanel; + } + + private Collection getConferenceServices(String server) throws Exception { + List answer = new ArrayList(); + ServiceDiscoveryManager discoManager = ServiceDiscoveryManager.getInstanceFor(SparkManager.getConnection()); + DiscoverItems items = discoManager.discoverItems(server); + for (Iterator it = items.getItems(); it.hasNext();) { + DiscoverItems.Item item = (DiscoverItems.Item)it.next(); + if (item.getEntityID().startsWith("conference") || item.getEntityID().startsWith("private")) { + answer.add(item.getEntityID()); + } + else { + try { + DiscoverInfo info = discoManager.discoverInfo(item.getEntityID()); + if (info.containsFeature("http://jabber.org/protocol/muc")) { + answer.add(item.getEntityID()); + } + } + catch (XMPPException e) { + Log.error("Problem when loading conference service.", e); + } + } + } + return answer; + } + + private boolean hasService(String service) { + TreePath path = tree.findByName(tree, new String[]{rootNode.getUserObject().toString(), service}); + return path != null; + } + + /** + * Returns the Tree used to display bookmarks. + * + * @return Tree used to display bookmarks. + */ + public Tree getTree() { + return tree; + } + + /** + * Sets the current bookmarks used with this account. + * + * @param bookmarks the current bookmarks used with this account. + */ + public void setBookmarks(Collection bookmarks) { + Iterator iter = bookmarks.iterator(); + while (iter.hasNext()) { + Bookmark bookmark = (Bookmark)iter.next(); + String serviceName = bookmark.getServiceName(); + String roomJID = bookmark.getRoomJID(); + String roomName = bookmark.getRoomName(); + + // Get Service Node + TreePath path = tree.findByName(tree, new String[]{rootNode.getUserObject().toString(), serviceName}); + JiveTreeNode serviceNode = null; + if (path == null) { + serviceNode = addServiceToList(serviceName); + path = tree.findByName(tree, new String[]{rootNode.getUserObject().toString(), serviceName}); + } + else { + serviceNode = (JiveTreeNode)path.getLastPathComponent(); + } + + addBookmark(serviceNode, roomName, roomJID); + + tree.expandPath(path); + } + + SwingWorker worker = new SwingWorker() { + public Object construct() { + try { + Thread.sleep(2000); + } + catch (InterruptedException e) { + Log.error(e); + } + return getAutoJoins(); + } + + public void finished() { + Collection col = (Collection)get(); + if (col != null) { + joinTaggedRooms(col); + } + } + }; + worker.start(); + } + + private Collection getAutoJoins() { + // Load auto-joined rooms + // Save autoJoins + File sparkHome = new File(Spark.getUserHome(), "Spark"); + File conferenceXML = new File(sparkHome, "conferences.xml"); + if (!conferenceXML.exists()) { + return null; + } + SAXReader saxReader = new SAXReader(); + Document conferenceDocument = null; + List autoJoins = null; + try { + conferenceDocument = saxReader.read(conferenceXML); + autoJoins = conferenceDocument.selectNodes("/conferences/auto-join"); + } + catch (DocumentException e) { + } + + return autoJoins; + } + + private void joinTaggedRooms(Collection autoJoins) { + Iterator joins = autoJoins.iterator(); + while (joins.hasNext()) { + Element autoJoin = (Element)joins.next(); + String conferenceJID = autoJoin.getText(); + autoJoinRooms.add(conferenceJID); + ConferenceUtils.autoJoinConferenceRoom(conferenceJID, conferenceJID, null); + } + } + + /** + * Returns all MUC services available. + * + * @return a collection of MUC services. + */ + public Collection getMucServices() { + return mucServices; + } + + /** + * Adds a new ContextMenuListener. + * + * @param listener the listener. + */ + public void addContextMenuListener(ContextMenuListener listener) { + listeners.add(listener); + } + + /** + * Removes a ContextMenuListener. + * + * @param listener the listener. + */ + public void removeContextMenuListener(ContextMenuListener listener) { + listeners.remove(listener); + } + + private void fireContextMenuListeners(JPopupMenu popup, JiveTreeNode node) { + final Iterator iter = new ArrayList(listeners).iterator(); + while (iter.hasNext()) { + ContextMenuListener listener = (ContextMenuListener)iter.next(); + listener.poppingUp(node, popup); + } + } +} diff --git a/src/java/org/jivesoftware/spark/ui/conferences/ConferenceCreator.java b/src/java/org/jivesoftware/spark/ui/conferences/ConferenceCreator.java new file mode 100644 index 00000000..81258d21 --- /dev/null +++ b/src/java/org/jivesoftware/spark/ui/conferences/ConferenceCreator.java @@ -0,0 +1,249 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.ui.conferences; + +import org.jivesoftware.resource.SparkRes; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smackx.muc.MultiUserChat; +import org.jivesoftware.spark.SparkManager; +import org.jivesoftware.spark.component.TitlePanel; +import org.jivesoftware.spark.util.ModelUtil; +import org.jivesoftware.spark.util.ResourceUtils; +import org.jivesoftware.spark.util.log.Log; + +import javax.swing.JCheckBox; +import javax.swing.JDialog; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JPasswordField; +import javax.swing.JTextField; + +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; + +public class ConferenceCreator extends JPanel { + private JLabel nameLabel = new JLabel(); + private JLabel topicLabel = new JLabel(); + private JLabel passwordLabel = new JLabel(); + private JLabel confirmPasswordLabel = new JLabel(); + private JCheckBox permanentCheckBox = new JCheckBox(); + private JCheckBox privateCheckbox = new JCheckBox(); + private JTextField nameField = new JTextField(); + private JTextField topicField = new JTextField(); + private JPasswordField passwordField = new JPasswordField(); + private JPasswordField confirmPasswordField = new JPasswordField(); + private GridBagLayout gridBagLayout1 = new GridBagLayout(); + private MultiUserChat groupChat = null; + + public ConferenceCreator() { + try { + jbInit(); + } + catch (Exception e) { + Log.error(e); + } + } + + private void jbInit() throws Exception { + this.setLayout(gridBagLayout1); + nameLabel.setText("Name:"); + topicLabel.setText("Topic:"); + passwordLabel.setText("Password:"); + confirmPasswordLabel.setText("Confirm Password:"); + permanentCheckBox.setText("Permanent"); + privateCheckbox.setText("Private Room"); + this.add(confirmPasswordField, new GridBagConstraints(1, 4, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); + this.add(passwordField, new GridBagConstraints(1, 3, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); + this.add(topicField, new GridBagConstraints(1, 1, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); + this.add(nameField, new GridBagConstraints(1, 0, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); + this.add(privateCheckbox, new GridBagConstraints(0, 2, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + this.add(permanentCheckBox, new GridBagConstraints(0, 5, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + this.add(confirmPasswordLabel, new GridBagConstraints(0, 4, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + this.add(passwordLabel, new GridBagConstraints(0, 3, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + this.add(topicLabel, new GridBagConstraints(0, 1, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 5, 0)); + this.add(nameLabel, new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + + ResourceUtils.resLabel(nameLabel, nameField, "Room &Name:"); + ResourceUtils.resLabel(topicLabel, topicField, "Room &Topic:"); + ResourceUtils.resLabel(passwordLabel, passwordField, "&Password:"); + ResourceUtils.resLabel(confirmPasswordLabel, confirmPasswordField, "&Confirm Password:"); + ResourceUtils.resButton(permanentCheckBox, "&Room is permanent"); + ResourceUtils.resButton(privateCheckbox, "Room &is private"); + } + + public MultiUserChat createGroupChat(Component parent, final String serviceName) { + final JOptionPane pane; + final JDialog dlg; + + TitlePanel titlePanel; + + // Create the title panel for this dialog + titlePanel = new TitlePanel("Create/Join Room", "Create or join a conference chat room", SparkRes.getImageIcon(SparkRes.BLANK_24x24), true); + + // Construct main panel w/ layout. + final JPanel mainPanel = new JPanel(); + mainPanel.setLayout(new BorderLayout()); + mainPanel.add(titlePanel, BorderLayout.NORTH); + + // The user should only be able to close this dialog. + Object[] options = {"Create", "Close"}; + pane = new JOptionPane(this, JOptionPane.PLAIN_MESSAGE, JOptionPane.OK_CANCEL_OPTION, null, options, options[0]); + + mainPanel.add(pane, BorderLayout.CENTER); + + JOptionPane p = new JOptionPane(); + dlg = p.createDialog(parent, "Conference Rooms"); + dlg.pack(); + dlg.setSize(400, 350); + dlg.setContentPane(mainPanel); + dlg.setLocationRelativeTo(parent); + + + PropertyChangeListener changeListener = new PropertyChangeListener() { + public void propertyChange(PropertyChangeEvent e) { + Object o = pane.getValue(); + if (o instanceof Integer) { + dlg.setVisible(false); + return; + } + + String value = (String)pane.getValue(); + if ("Close".equals(value)) { + dlg.setVisible(false); + return; + } + else if ("Create".equals(value)) { + boolean isValid = validatePanel(); + if (isValid) { + String room = nameField.getText().replaceAll(" ", "_") + "@" + serviceName; + try { + MultiUserChat.getRoomInfo(SparkManager.getConnection(), room); + //JOptionPane.showMessageDialog(dlg, "Room already exists. Please specify a unique room name.", "Room Exists", JOptionPane.ERROR_MESSAGE); + //pane.setValue(JOptionPane.UNINITIALIZED_VALUE); + pane.removePropertyChangeListener(this); + dlg.setVisible(false); + ConferenceUtils.joinConferenceRoom(room, room); + return; + } + catch (XMPPException e1) { + + } + + groupChat = createGroupChat(nameField.getText(), serviceName); + if (groupChat == null) { + showError("Could not join chat " + nameField.getText()); + pane.setValue(JOptionPane.UNINITIALIZED_VALUE); + + } + else { + pane.removePropertyChangeListener(this); + dlg.setVisible(false); + } + } + else { + pane.setValue(JOptionPane.UNINITIALIZED_VALUE); + } + } + } + }; + + pane.addPropertyChangeListener(changeListener); + nameField.requestFocusInWindow(); + + dlg.setVisible(true); + dlg.toFront(); + dlg.requestFocus(); + + return groupChat; + } + + private boolean validatePanel() { + String roomName = nameField.getText(); + String topic = topicField.getText(); + String password = new String(passwordField.getPassword()); + String confirmPassword = new String(confirmPasswordField.getPassword()); + boolean isPrivate = privateCheckbox.isSelected(); + boolean isPermanent = permanentCheckBox.isSelected(); + + // Check for valid information + if (!ModelUtil.hasLength(roomName)) { + showError("You must specify a valid name."); + nameField.requestFocus(); + return false; + } + + if (isPrivate) { + if (!ModelUtil.hasLength(password)) { + showError("Please specify a password for the private room."); + passwordField.requestFocus(); + return false; + } + + if (!ModelUtil.hasLength(confirmPassword)) { + showError("Please confirm the private room password."); + confirmPasswordField.requestFocus(); + return false; + } + + if (!ModelUtil.areEqual(password, confirmPassword)) { + showError("Passwords to not match. Please verify passwords."); + passwordField.requestFocus(); + return false; + } + } + + return true; + } + + private MultiUserChat createGroupChat(String roomName, String serviceName) { + String room = roomName.replaceAll(" ", "_") + "@" + serviceName; + + // Create a group chat with valid information + return new MultiUserChat(SparkManager.getConnection(), room.toLowerCase()); + } + + + private void showError(String errorMessage) { + JOptionPane.showMessageDialog(this, errorMessage, "Room Creation Problem", JOptionPane.ERROR_MESSAGE); + } + + public boolean isPrivate() { + return privateCheckbox.isSelected(); + } + + public boolean isPermanent() { + return permanentCheckBox.isSelected(); + } + + public boolean isPasswordProtected() { + String password = new String(passwordField.getPassword()); + if (password != null) { + return true; + } + + return false; + } + + public String getPassword() { + return new String(confirmPasswordField.getPassword()); + } + + public String getRoomName() { + return nameField.getText(); + } + +} \ No newline at end of file diff --git a/src/java/org/jivesoftware/spark/ui/conferences/ConferenceData.java b/src/java/org/jivesoftware/spark/ui/conferences/ConferenceData.java new file mode 100644 index 00000000..c7e52e7a --- /dev/null +++ b/src/java/org/jivesoftware/spark/ui/conferences/ConferenceData.java @@ -0,0 +1,157 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.ui.conferences; + +import org.jivesoftware.smackx.packet.PrivateData; +import org.jivesoftware.smackx.provider.PrivateDataProvider; +import org.xmlpull.v1.XmlPullParser; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +public class ConferenceData implements PrivateData { + + private final Map serviceMap = new HashMap(); + + public static final String ELEMENT = "conference-data"; + public static final String NAMESPACE = "http://www.jivesoftware.com/communicator"; + + public void addService(String service) { + serviceMap.put(service, ""); + } + + public void addBookmark(Bookmark bookmark) { + List bookmarks = (List)serviceMap.get(bookmark.getServiceName()); + if (bookmarks == null) { + bookmarks = new ArrayList(); + } + + bookmarks.add(bookmark); + serviceMap.put(bookmark.getServiceName(), bookmarks); + } + + public Collection getBookmarks() { + List list = new ArrayList(); + + Iterator iter = serviceMap.values().iterator(); + while (iter.hasNext()) { + List l = (List)iter.next(); + Iterator bookmarks = l.iterator(); + while (bookmarks.hasNext()) { + Bookmark bookmark = (Bookmark)bookmarks.next(); + list.add(bookmark); + } + } + return list; + } + + + public String getElementName() { + return ELEMENT; + } + + public String getNamespace() { + return NAMESPACE; + } + + public String toXML() { + StringBuffer buf = new StringBuffer(); + buf.append("<").append(getElementName()).append(" xmlns=\"").append(getNamespace()).append("\">"); + Iterator iter = serviceMap.keySet().iterator(); + buf.append(""); + + while (iter.hasNext()) { + String serviceName = (String)iter.next(); + List bookmarks = (List)serviceMap.get(serviceName); + + if (bookmarks != null) { + Iterator rooms = bookmarks.iterator(); + while (rooms.hasNext()) { + Bookmark bookmark = (Bookmark)rooms.next(); + buf.append(""); + buf.append("").append(serviceName).append(""); + buf.append("").append(bookmark.getRoomJID()).append(""); + buf.append("").append(bookmark.getRoomName()).append(""); + buf.append(""); + } + } + else { + buf.append("").append(serviceName).append(""); + } + + + } + buf.append(""); + + + buf.append(""); + return buf.toString(); + } + + public static class ConferencePrivateDataProvider implements PrivateDataProvider { + + public ConferencePrivateDataProvider() { + } + + public PrivateData parsePrivateData(XmlPullParser parser) throws Exception { + ConferenceData conference = new ConferenceData(); + + boolean done = false; + + boolean isInstalled = false; + while (!done) { + int eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG && parser.getName().equals("services")) { + isInstalled = true; + } + + if (eventType == XmlPullParser.START_TAG && parser.getName().equals("service")) { + Bookmark bookmark = getBookmark(parser); + conference.addBookmark(bookmark); + } + + else if (eventType == XmlPullParser.END_TAG && parser.getName().equals("services")) { + done = true; + } + else if (!isInstalled) { + done = true; + } + } + return conference; + } + } + + private static Bookmark getBookmark(XmlPullParser parser) throws Exception { + final Bookmark bookmark = new Bookmark(); + boolean done = false; + while (!done) { + int eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG && parser.getName().equals("name")) { + bookmark.setServiceName(parser.nextText()); + } + else if (eventType == XmlPullParser.START_TAG && parser.getName().equals("roomJID")) { + bookmark.setRoomJID(parser.nextText()); + } + else if (eventType == XmlPullParser.START_TAG && parser.getName().equals("roomName")) { + bookmark.setRoomName(parser.nextText()); + } + + else if (eventType == XmlPullParser.END_TAG && parser.getName().equals("service")) { + done = true; + } + } + return bookmark; + } +} diff --git a/src/java/org/jivesoftware/spark/ui/conferences/ConferenceInviteDialog.java b/src/java/org/jivesoftware/spark/ui/conferences/ConferenceInviteDialog.java new file mode 100644 index 00000000..37e994e6 --- /dev/null +++ b/src/java/org/jivesoftware/spark/ui/conferences/ConferenceInviteDialog.java @@ -0,0 +1,325 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.ui.conferences; + +import org.jivesoftware.resource.SparkRes; +import org.jivesoftware.smack.util.StringUtils; +import org.jivesoftware.spark.ChatManager; +import org.jivesoftware.spark.ChatNotFoundException; +import org.jivesoftware.spark.SparkManager; +import org.jivesoftware.spark.component.TitlePanel; +import org.jivesoftware.spark.ui.ChatRoom; +import org.jivesoftware.spark.ui.RosterPickList; +import org.jivesoftware.spark.ui.rooms.GroupChatRoom; +import org.jivesoftware.spark.util.ModelUtil; +import org.jivesoftware.spark.util.ResourceUtils; +import org.jivesoftware.spark.util.SwingWorker; +import org.jivesoftware.spark.util.log.Log; + +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.DefaultListModel; +import javax.swing.JButton; +import javax.swing.JDialog; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JList; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JPopupMenu; +import javax.swing.JScrollPane; +import javax.swing.JTextField; + +import java.awt.BorderLayout; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; + +final class ConferenceInviteDialog extends JPanel { + private JLabel roomsLabel = new JLabel(); + private JTextField roomsField = new JTextField(); + + private JLabel messageLabel = new JLabel(); + private JTextField messageField = new JTextField(); + + private JLabel inviteLabel = new JLabel(); + + + private DefaultListModel invitedUsers = new DefaultListModel(); + private JList invitedUserList = new JList(invitedUsers); + + private JDialog dlg; + + private GridBagLayout gridBagLayout1 = new GridBagLayout(); + + public ConferenceInviteDialog() { + setLayout(gridBagLayout1); + + add(roomsLabel, new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + add(roomsField, new GridBagConstraints(1, 0, 3, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); + + + add(messageLabel, new GridBagConstraints(0, 1, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + add(messageField, new GridBagConstraints(1, 1, 3, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); + + JLabel jidLabel = new JLabel(); + final JTextField jidField = new JTextField(); + JButton addJIDButton = new JButton(); + JButton browseButton = new JButton(); + ResourceUtils.resButton(addJIDButton, "&Add"); + ResourceUtils.resButton(browseButton, "&Roster..."); + ResourceUtils.resLabel(jidLabel, jidField, "&Add JID"); + + add(jidLabel, new GridBagConstraints(0, 2, 1, 1, 0.0, 0.0, GridBagConstraints.NORTHWEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); + add(jidField, new GridBagConstraints(1, 2, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); + add(addJIDButton, new GridBagConstraints(2, 2, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + add(browseButton, new GridBagConstraints(3, 2, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + + addJIDButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent actionEvent) { + String jid = jidField.getText(); + String server = StringUtils.parseBareAddress(jid); + if (server == null || server.indexOf("@") == -1) { + JOptionPane.showMessageDialog(dlg, "Please enter a valid Jabber ID", "Invalid JID", JOptionPane.ERROR_MESSAGE); + jidField.setText(""); + jidField.requestFocus(); + return; + } + else { + if (!invitedUsers.contains(jid)) { + invitedUsers.addElement(jid); + } + jidField.setText(""); + } + } + }); + + browseButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent actionEvent) { + RosterPickList browser = new RosterPickList(); + Collection col = browser.showRoster(dlg); + + Iterator iter = col.iterator(); + while (iter.hasNext()) { + String jid = (String)iter.next(); + if (!invitedUsers.contains(jid)) { + invitedUsers.addElement(jid); + } + + } + } + }); + + + add(inviteLabel, new GridBagConstraints(0, 3, 1, 1, 0.0, 0.0, GridBagConstraints.NORTHWEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); + add(new JScrollPane(invitedUserList), new GridBagConstraints(1, 3, 3, 1, 1.0, 1.0, GridBagConstraints.WEST, GridBagConstraints.BOTH, new Insets(5, 5, 5, 5), 0, 0)); + + // Add Resource Utils + ResourceUtils.resLabel(messageLabel, messageField, "&Message:"); + ResourceUtils.resLabel(roomsLabel, roomsField, "&Room:"); + inviteLabel.setText("Invited Users"); + + messageField.setText("Please join me in a conference."); + + // Add Listener to list + invitedUserList.addMouseListener(new MouseAdapter() { + public void mouseReleased(MouseEvent mouseEvent) { + if (mouseEvent.isPopupTrigger()) { + showPopup(mouseEvent); + } + } + + public void mousePressed(MouseEvent mouseEvent) { + if (mouseEvent.isPopupTrigger()) { + showPopup(mouseEvent); + } + } + }); + } + + private void showPopup(MouseEvent e) { + final JPopupMenu popup = new JPopupMenu(); + final int index = invitedUserList.locationToIndex(e.getPoint()); + + Action removeAction = new AbstractAction() { + public void actionPerformed(ActionEvent e) { + invitedUsers.remove(index); + } + }; + + removeAction.putValue(Action.NAME, "Remove"); + removeAction.putValue(Action.SMALL_ICON, SparkRes.getImageIcon(SparkRes.SMALL_DELETE)); + + popup.add(removeAction); + + popup.show(invitedUserList, e.getX(), e.getY()); + + } + + public void inviteUsersToRoom(final String serviceName, String roomName, Collection jids) { + roomsField.setText(roomName); + + + JFrame parent = SparkManager.getChatManager().getChatContainer().getChatFrame(); + if (parent == null || !parent.isVisible()) { + parent = SparkManager.getMainWindow(); + } + + // Add jids to user list + if (jids != null) { + Iterator iter = jids.iterator(); + while (iter.hasNext()) { + invitedUsers.addElement(iter.next()); + } + } + + final JOptionPane pane; + + + TitlePanel titlePanel; + + // Create the title panel for this dialog + titlePanel = new TitlePanel("Invite To Conference", "Invite users to a conference room.", SparkRes.getImageIcon(SparkRes.BLANK_IMAGE), true); + + // Construct main panel w/ layout. + final JPanel mainPanel = new JPanel(); + mainPanel.setLayout(new BorderLayout()); + mainPanel.add(titlePanel, BorderLayout.NORTH); + + // The user should only be able to close this dialog. + Object[] options = {"Invite", "Cancel"}; + pane = new JOptionPane(this, JOptionPane.PLAIN_MESSAGE, JOptionPane.OK_CANCEL_OPTION, null, options, options[0]); + + mainPanel.add(pane, BorderLayout.CENTER); + + final JOptionPane p = new JOptionPane(); + + dlg = p.createDialog(parent, "Conference Rooms"); + dlg.setModal(false); + + dlg.pack(); + dlg.setSize(500, 450); + dlg.setResizable(true); + dlg.setContentPane(mainPanel); + dlg.setLocationRelativeTo(parent); + + + PropertyChangeListener changeListener = new PropertyChangeListener() { + public void propertyChange(PropertyChangeEvent e) { + String value = (String)pane.getValue(); + if ("Cancel".equals(value)) { + pane.setValue(JOptionPane.UNINITIALIZED_VALUE); + dlg.dispose(); + } + else if ("Invite".equals(value)) { + final String roomTitle = roomsField.getText(); + int size = invitedUserList.getModel().getSize(); + + if (size == 0) { + JOptionPane.showMessageDialog(dlg, "Please specify users to join this conference room.", "No Invitees Specified", JOptionPane.ERROR_MESSAGE); + pane.setValue(JOptionPane.UNINITIALIZED_VALUE); + return; + } + + if (!ModelUtil.hasLength(roomTitle)) { + JOptionPane.showMessageDialog(dlg, "No room to join.", "Invalid Room Name", JOptionPane.ERROR_MESSAGE); + pane.setValue(JOptionPane.UNINITIALIZED_VALUE); + return; + } + String roomName = ""; + + // Add all rooms the user is in to list. + ChatManager chatManager = SparkManager.getChatManager(); + Iterator rooms = chatManager.getChatContainer().getChatRooms().iterator(); + while (rooms.hasNext()) { + ChatRoom chatRoom = (ChatRoom)rooms.next(); + if (chatRoom instanceof GroupChatRoom) { + GroupChatRoom groupRoom = (GroupChatRoom)chatRoom; + if (groupRoom.getRoomname().equals(roomTitle)) { + roomName = groupRoom.getMultiUserChat().getRoom(); + break; + } + } + } + String message = messageField.getText(); + final String messageText = message != null ? message : "Please join me in a conference"; + + if (invitedUsers.getSize() > 0) { + invitedUserList.setSelectionInterval(0, invitedUsers.getSize() - 1); + } + + + GroupChatRoom chatRoom = null; + try { + chatRoom = SparkManager.getChatManager().getGroupChat(roomName); + } + catch (ChatNotFoundException e1) { + dlg.setVisible(false); + final List jidList = new ArrayList(); + Object[] jids = invitedUserList.getSelectedValues(); + final int no = jids != null ? jids.length : 0; + for (int i = 0; i < no; i++) { + jidList.add(jids[i]); + } + + SwingWorker worker = new SwingWorker() { + public Object construct() { + try { + Thread.sleep(15); + } + catch (InterruptedException e2) { + Log.error(e2); + } + return "ok"; + } + + public void finished() { + ConferenceUtils.createPrivateConference(serviceName, messageText, roomTitle, jidList); + return; + } + }; + + worker.start(); + pane.setValue(JOptionPane.UNINITIALIZED_VALUE); + return; + } + + pane.setValue(JOptionPane.UNINITIALIZED_VALUE); + dlg.dispose(); + + Object[] values = invitedUserList.getSelectedValues(); + final int no = values != null ? values.length : 0; + for (int i = 0; i < no; i++) { + String jid = (String)values[i]; + chatRoom.getMultiUserChat().invite(jid, message != null ? message : "Please join me in a conference"); + } + + } + } + }; + + pane.addPropertyChangeListener(changeListener); + + dlg.setVisible(true); + dlg.toFront(); + dlg.requestFocus(); + } +} diff --git a/src/java/org/jivesoftware/spark/ui/conferences/ConferenceRoomInfo.java b/src/java/org/jivesoftware/spark/ui/conferences/ConferenceRoomInfo.java new file mode 100644 index 00000000..09a92578 --- /dev/null +++ b/src/java/org/jivesoftware/spark/ui/conferences/ConferenceRoomInfo.java @@ -0,0 +1,688 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.ui.conferences; + +import org.jivesoftware.resource.SparkRes; +import org.jivesoftware.smack.PacketListener; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.packet.Packet; +import org.jivesoftware.smack.packet.Presence; +import org.jivesoftware.smack.util.StringUtils; +import org.jivesoftware.smackx.muc.Affiliate; +import org.jivesoftware.smackx.muc.MultiUserChat; +import org.jivesoftware.smackx.muc.Occupant; +import org.jivesoftware.smackx.muc.UserStatusListener; +import org.jivesoftware.spark.ChatManager; +import org.jivesoftware.spark.SparkManager; +import org.jivesoftware.spark.UserManager; +import org.jivesoftware.spark.component.ImageTitlePanel; +import org.jivesoftware.spark.component.renderer.ListIconRenderer; +import org.jivesoftware.spark.ui.ChatRoom; +import org.jivesoftware.spark.ui.ChatRoomListener; +import org.jivesoftware.spark.ui.ChatRoomNotFoundException; +import org.jivesoftware.spark.ui.rooms.ChatRoomImpl; +import org.jivesoftware.spark.ui.rooms.GroupChatRoom; +import org.jivesoftware.spark.ui.status.StatusBar; +import org.jivesoftware.spark.ui.status.StatusItem; +import org.jivesoftware.spark.util.ModelUtil; +import org.jivesoftware.spark.util.log.Log; + +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.DefaultListModel; +import javax.swing.Icon; +import javax.swing.ImageIcon; +import javax.swing.JComponent; +import javax.swing.JList; +import javax.swing.JMenu; +import javax.swing.JMenuItem; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JPopupMenu; +import javax.swing.JScrollPane; +import javax.swing.SwingUtilities; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.event.ActionEvent; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +/** + * The RoomInfo class is used to display all room information, such as agents and room information. + */ +public final class ConferenceRoomInfo extends JPanel implements ChatRoomListener { + private GroupChatRoom groupChatRoom; + private final ImageTitlePanel agentInfoPanel = new ImageTitlePanel("Participants in Room"); + private ChatManager chatManager; + private MultiUserChat chat; + + private final Map userMap = new HashMap(); + + private UserManager userManager = SparkManager.getUserManager(); + private DefaultListModel model = new DefaultListModel(); + private JList list = new JList(model); + private PacketListener listener = null; + + + /** + * Creates a new RoomInfo instance using the specified ChatRoom. The RoomInfo + * component is responsible for monitoring all activity in the ChatRoom. + */ + public ConferenceRoomInfo() { + chatManager = SparkManager.getChatManager(); + list.setCellRenderer(new ListIconRenderer()); + + // Set the room to track + this.setOpaque(true); + + this.setLayout(new BorderLayout()); + this.setOpaque(true); + this.setBackground(Color.white); + + // Respond to Double-Click in Agent List to start a chat + list.addMouseListener(new MouseAdapter() { + public void mouseClicked(MouseEvent evt) { + if (evt.getClickCount() == 2) { + String selectedUser = getSelectedUser(); + startChat(groupChatRoom, (String)userMap.get(selectedUser)); + } + } + + public void mouseReleased(final MouseEvent evt) { + if (evt.isPopupTrigger()) { + checkPopup(evt); + } + } + + public void mousePressed(final MouseEvent evt) { + if (evt.isPopupTrigger()) { + checkPopup(evt); + } + } + }); + + + JScrollPane scroller = new JScrollPane(list); + + // Speed up scrolling. It was way too slow. + scroller.getVerticalScrollBar().setBlockIncrement(50); + scroller.getVerticalScrollBar().setUnitIncrement(20); + scroller.setBackground(Color.white); + scroller.getViewport().setBackground(Color.white); + + + this.add(scroller, BorderLayout.CENTER); + } + + public void setChatRoom(final ChatRoom chatRoom) { + this.groupChatRoom = (GroupChatRoom)chatRoom; + + chatManager.addChatRoomListener(this); + + chat = groupChatRoom.getMultiUserChat(); + + + listener = new PacketListener() { + public void processPacket(final Packet packet) { + SwingUtilities.invokeLater(new Runnable() { + public void run() { + Presence p = (Presence)packet; + final String userid = p.getFrom(); + + String nickname = StringUtils.parseResource(userid); + userMap.put(nickname, userid); + + if (p.getType() == Presence.Type.AVAILABLE) { + addParticipant(userid, p); + agentInfoPanel.setVisible(true); + } + else { + model.removeElement(nickname); + } + } + }); + + } + }; + + chat.addParticipantListener(listener); + } + + public void chatRoomOpened(ChatRoom room) { + if (room != groupChatRoom) { + return; + } + + + chat.addUserStatusListener(new UserStatusListener() { + public void kicked(String actor, String reason) { + + } + + public void voiceGranted() { + + } + + public void voiceRevoked() { + + } + + public void banned(String actor, String reason) { + + } + + public void membershipGranted() { + + } + + public void membershipRevoked() { + + } + + public void moderatorGranted() { + + } + + public void moderatorRevoked() { + + } + + public void ownershipGranted() { + } + + public void ownershipRevoked() { + + } + + public void adminGranted() { + + } + + public void adminRevoked() { + + } + }); + } + + public void chatRoomLeft(ChatRoom room) { + if (this.groupChatRoom == room) { + chatManager.removeChatRoomListener(this); + agentInfoPanel.setVisible(false); + } + } + + public void chatRoomClosed(ChatRoom room) { + if (this.groupChatRoom == room) { + chatManager.removeChatRoomListener(this); + chat.removeParticipantListener(listener); + } + } + + public void chatRoomActivated(ChatRoom room) { + } + + + public void userHasJoined(ChatRoom room, String userid) { + } + + private ImageIcon getImageIcon(String participantJID) { + String nickname = StringUtils.parseResource(participantJID); + Occupant occupant = SparkManager.getUserManager().getOccupant(groupChatRoom, nickname); + boolean isOwnerOrAdmin = SparkManager.getUserManager().isOwnerOrAdmin(occupant); + boolean isModerator = SparkManager.getUserManager().isModerator(occupant); + + + ImageIcon icon = SparkRes.getImageIcon(SparkRes.MODERATOR_IMAGE); + if (!isOwnerOrAdmin) { + if (isModerator) { + icon = SparkRes.getImageIcon(SparkRes.MODERATOR_IMAGE); + } + else { + icon = SparkRes.getImageIcon(SparkRes.GREEN_BALL); + } + } + + icon.setDescription(nickname); + return icon; + } + + private void addParticipant(String participantJID, Presence presence) { + String nickname = StringUtils.parseResource(participantJID); + Occupant occupant = SparkManager.getUserManager().getOccupant(groupChatRoom, nickname); + boolean isOwnerOrAdmin = SparkManager.getUserManager().isOwnerOrAdmin(occupant); + boolean isModerator = SparkManager.getUserManager().isModerator(occupant); + + if (!exists(nickname)) { + ImageIcon icon = SparkRes.getImageIcon(SparkRes.MODERATOR_IMAGE); + + if (!isOwnerOrAdmin) { + if (isModerator) { + icon = SparkRes.getImageIcon(SparkRes.MODERATOR_IMAGE); + } + else { + StatusBar statusBar = SparkManager.getWorkspace().getStatusBar(); + StatusItem item = statusBar.getItemFromPresence(presence); + if (item != null) { + icon = new ImageIcon(item.getImageIcon().getImage()); + } + else { + icon = SparkRes.getImageIcon(SparkRes.GREEN_BALL); + } + } + } + + icon.setDescription(nickname); + model.addElement(icon); + } + else { + ImageIcon icon = SparkRes.getImageIcon(SparkRes.MODERATOR_IMAGE); + if (!isOwnerOrAdmin) { + if (isModerator) { + icon = SparkRes.getImageIcon(SparkRes.MODERATOR_IMAGE); + } + else { + StatusBar statusBar = SparkManager.getWorkspace().getStatusBar(); + StatusItem item = statusBar.getItemFromPresence(presence); + if (item != null) { + icon = new ImageIcon(item.getImageIcon().getImage()); + } + else { + icon = SparkRes.getImageIcon(SparkRes.GREEN_BALL); + } + } + } + icon.setDescription(nickname); + + int index = getIndex(nickname); + if (index != -1) { + model.removeElementAt(index); + model.insertElementAt(icon, index); + } + } + } + + public void userHasLeft(ChatRoom room, String userid) { + int index = getIndex(userid); + + if (index != -1) { + model.removeElementAt(index); + } + } + + private boolean exists(String user) { + for (int i = 0; i < model.getSize(); i++) { + final ImageIcon icon = (ImageIcon)model.get(i); + if (icon.getDescription().equals(user)) { + return true; + } + } + return false; + } + + private int getIndex(String nickname) { + for (int i = 0; i < model.getSize(); i++) { + final ImageIcon icon = (ImageIcon)model.get(i); + if (icon.getDescription().equals(nickname)) { + return i; + } + } + return -1; + } + + private String getSelectedUser() { + ImageIcon icon = (ImageIcon)list.getSelectedValue(); + if (icon == null) { + return null; + } + return icon.getDescription(); + } + + + private void startChat(ChatRoom groupChat, String groupJID) { + String groupJIDNickname = StringUtils.parseResource(groupJID); + String nickname = groupChat.getNickname(); + + if (groupJIDNickname.equals(nickname)) { + return; + } + + ChatRoom chatRoom = null; + try { + chatRoom = chatManager.getChatContainer().getChatRoom(groupJID); + } + catch (ChatRoomNotFoundException e) { + Log.error("Could not find chat room - " + groupJID); + + // Create new room + chatRoom = new ChatRoomImpl(groupJID, groupJIDNickname, groupJID); + chatManager.getChatContainer().addChatRoom(chatRoom); + } + + chatManager.getChatContainer().activateChatRoom(chatRoom); + } + + public void tabSelected() { + //To change body of implemented methods use File | Settings | File Templates. + } + + public String getTabTitle() { + return "Room Information"; + } + + public Icon getTabIcon() { + return SparkRes.getImageIcon(SparkRes.SMALL_BUSINESS_MAN_VIEW); + } + + public String getTabToolTip() { + return "Room Information"; + } + + public JComponent getGUI() { + return this; + } + + + /** + * **************************************************************** + */ + /* MUC Functions */ + private void kickUser(String nickname) { + try { + chat.kickParticipant(nickname, "You have been kicked"); + } + catch (XMPPException e) { + groupChatRoom.insertText("You are unable to kick " + nickname + "."); + } + } + + private void banUser(String nickname) { + try { + Occupant occupant = chat.getOccupant((String)userMap.get(nickname)); + if (occupant != null) { + String bareJID = StringUtils.parseBareAddress(occupant.getJid()); + chat.banUser(bareJID, "You have been banned"); + } + } + catch (XMPPException e) { + Log.error(e); + } + } + + private void unbanUser(String jid) { + try { + chat.grantMembership(jid); + } + catch (XMPPException e) { + Log.error(e); + } + } + + private void grantVoice(String nickname) { + try { + chat.grantVoice(nickname); + } + catch (XMPPException e) { + Log.error(e); + } + } + + private void revokeVoice(String nickname) { + try { + chat.revokeVoice(nickname); + } + catch (XMPPException e) { + Log.error(e); + } + } + + + private void grantModerator(String nickname) { + try { + chat.grantModerator(nickname); + } + catch (XMPPException e) { + Log.error(e); + } + } + + private void revokeModerator(String nickname) { + try { + chat.revokeModerator(nickname); + } + catch (XMPPException e) { + Log.error(e); + } + } + + + /** + * Let's make sure that the panel doesn't strech past the + * scrollpane view pane. + * + * @return the preferred dimension + */ + public Dimension getPreferredSize() { + final Dimension size = super.getPreferredSize(); + size.width = 150; + return size; + } + + private void checkPopup(MouseEvent evt) { + final int index = list.locationToIndex(evt.getPoint()); + list.setSelectedIndex(index); + + final String selectedUser = getSelectedUser(); + final String groupJID = (String)userMap.get(selectedUser); + String groupJIDNickname = StringUtils.parseResource(groupJID); + + final String nickname = groupChatRoom.getNickname(); + final Occupant occupant = userManager.getOccupant(groupChatRoom, selectedUser); + final boolean admin = SparkManager.getUserManager().isOwnerOrAdmin(groupChatRoom, chat.getNickname()); + final boolean moderator = SparkManager.getUserManager().isModerator(groupChatRoom, chat.getNickname()); + + final boolean userIsAdmin = userManager.isOwnerOrAdmin(occupant); + final boolean userIsModerator = userManager.isModerator(occupant); + boolean isMe = groupJIDNickname.equals(nickname); + + JPopupMenu popup = new JPopupMenu(); + if (isMe) { + Action changeNicknameAction = new AbstractAction() { + public void actionPerformed(ActionEvent actionEvent) { + String newNickname = JOptionPane.showInputDialog(groupChatRoom, "New Nickname:", "Change Nickname", JOptionPane.QUESTION_MESSAGE); + if (ModelUtil.hasLength(newNickname)) { + while (true) { + newNickname = newNickname.trim(); + try { + chat.changeNickname(newNickname); + break; + } + catch (XMPPException e1) { + newNickname = JOptionPane.showInputDialog(groupChatRoom, "Nickname in use. Please specify another Nickname:", "Change Nickname", JOptionPane.QUESTION_MESSAGE); + if (!ModelUtil.hasLength(newNickname)) { + break; + } + } + } + } + } + }; + + changeNicknameAction.putValue(Action.NAME, "Change Nickname"); + changeNicknameAction.putValue(Action.SMALL_ICON, SparkRes.getImageIcon(SparkRes.DESKTOP_IMAGE)); + popup.add(changeNicknameAction); + } + + Action chatAction = new AbstractAction() { + public void actionPerformed(ActionEvent actionEvent) { + String selectedUser = getSelectedUser(); + startChat(groupChatRoom, (String)userMap.get(selectedUser)); + } + }; + + chatAction.putValue(Action.NAME, "Start Chat"); + chatAction.putValue(Action.SMALL_ICON, SparkRes.getImageIcon(SparkRes.SMALL_MESSAGE_IMAGE)); + if (!isMe) { + popup.add(chatAction); + } + + Action blockAction = new AbstractAction() { + public void actionPerformed(ActionEvent e) { + ImageIcon icon = (ImageIcon)list.getSelectedValue(); + String description = icon.getDescription(); + if (groupChatRoom.isBlocked(groupJID)) { + groupChatRoom.removeBlockedUser(groupJID); + icon = getImageIcon(groupJID); + model.setElementAt(icon, index); + } + else { + groupChatRoom.addBlockedUser(groupJID); + icon = SparkRes.getImageIcon(SparkRes.BRICKWALL_IMAGE); + icon.setDescription(description); + model.setElementAt(icon, index); + } + } + }; + + blockAction.putValue(Action.NAME, "Block User"); + blockAction.putValue(Action.SMALL_ICON, SparkRes.getImageIcon(SparkRes.BRICKWALL_IMAGE)); + if (!isMe) { + if (groupChatRoom.isBlocked(groupJID)) { + blockAction.putValue(Action.NAME, "Unblock User"); + } + popup.add(blockAction); + } + + + Action kickAction = new AbstractAction() { + public void actionPerformed(ActionEvent actionEvent) { + kickUser(selectedUser); + } + }; + + kickAction.putValue(Action.NAME, "Kick User"); + kickAction.putValue(Action.SMALL_ICON, SparkRes.getImageIcon(SparkRes.SMALL_DELETE)); + if (moderator && !userIsAdmin && !isMe) { + popup.add(kickAction); + } + + // Handle Voice Operations + Action voiceAction = new AbstractAction() { + public void actionPerformed(ActionEvent actionEvent) { + if (userManager.hasVoice(groupChatRoom, selectedUser)) { + revokeVoice(selectedUser); + } + else { + grantVoice(selectedUser); + } + + } + }; + + voiceAction.putValue(Action.NAME, "Voice"); + voiceAction.putValue(Action.SMALL_ICON, SparkRes.getImageIcon(SparkRes.MEGAPHONE_16x16)); + if (moderator && !userIsModerator && !isMe) { + if (userManager.hasVoice(groupChatRoom, selectedUser)) { + voiceAction.putValue(Action.NAME, "Revoke Voice"); + } + else { + voiceAction.putValue(Action.NAME, "Grant Voice"); + } + popup.add(voiceAction); + } + + + Action banAction = new AbstractAction() { + public void actionPerformed(ActionEvent actionEvent) { + banUser(selectedUser); + } + }; + banAction.putValue(Action.NAME, "Ban User"); + banAction.putValue(Action.SMALL_ICON, SparkRes.getImageIcon(SparkRes.RED_FLAG_16x16)); + if (admin && !userIsModerator && !isMe) { + popup.add(banAction); + } + + + Action moderatorAction = new AbstractAction() { + public void actionPerformed(ActionEvent actionEvent) { + if (!userIsModerator) { + grantModerator(selectedUser); + } + else { + revokeModerator(selectedUser); + } + } + }; + + moderatorAction.putValue(Action.SMALL_ICON, SparkRes.getImageIcon(SparkRes.MODERATOR_IMAGE)); + if (admin && !userIsModerator) { + moderatorAction.putValue(Action.NAME, "Grant Moderator"); + popup.add(moderatorAction); + } + else if (admin && userIsModerator && !isMe) { + moderatorAction.putValue(Action.NAME, "Revoke Moderator"); + popup.add(moderatorAction); + } + + // Handle Unbanning of users. + Action unbanAction = new AbstractAction() { + public void actionPerformed(ActionEvent actionEvent) { + String jid = ((JMenuItem)actionEvent.getSource()).getText(); + unbanUser(jid); + } + }; + + if (admin) { + JMenu unbanMenu = new JMenu("Unban"); + Iterator bannedUsers = null; + try { + bannedUsers = chat.getOutcasts().iterator(); + } + catch (XMPPException e) { + Log.error("Error loading all banned users", e); + } + + while (bannedUsers != null && bannedUsers.hasNext()) { + Affiliate bannedUser = (Affiliate)bannedUsers.next(); + ImageIcon icon = SparkRes.getImageIcon(SparkRes.RED_BALL); + JMenuItem bannedItem = new JMenuItem(bannedUser.getJid(), icon); + unbanMenu.add(bannedItem); + bannedItem.addActionListener(unbanAction); + } + + if (unbanMenu.getMenuComponentCount() > 0) { + popup.add(unbanMenu); + } + } + + + Action inviteAction = new AbstractAction() { + public void actionPerformed(ActionEvent actionEvent) { + ConferenceUtils.inviteUsersToRoom(groupChatRoom.getConferenceService(), groupChatRoom.getRoomname(), null); + } + }; + + inviteAction.putValue(Action.NAME, "Invite Users"); + inviteAction.putValue(Action.SMALL_ICON, SparkRes.getImageIcon(SparkRes.CONFERENCE_IMAGE_16x16)); + popup.addSeparator(); + popup.add(inviteAction); + + + popup.show(list, evt.getX(), evt.getY()); + } +} + + diff --git a/src/java/org/jivesoftware/spark/ui/conferences/ConferenceRooms.java b/src/java/org/jivesoftware/spark/ui/conferences/ConferenceRooms.java new file mode 100644 index 00000000..7f28a4c8 --- /dev/null +++ b/src/java/org/jivesoftware/spark/ui/conferences/ConferenceRooms.java @@ -0,0 +1,725 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.ui.conferences; + +import org.jivesoftware.resource.SparkRes; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.util.StringUtils; +import org.jivesoftware.smackx.Form; +import org.jivesoftware.smackx.muc.HostedRoom; +import org.jivesoftware.smackx.muc.MultiUserChat; +import org.jivesoftware.smackx.muc.RoomInfo; +import org.jivesoftware.spark.ChatManager; +import org.jivesoftware.spark.SparkManager; +import org.jivesoftware.spark.component.JiveSortableTable; +import org.jivesoftware.spark.component.JiveTreeNode; +import org.jivesoftware.spark.component.RolloverButton; +import org.jivesoftware.spark.component.Table; +import org.jivesoftware.spark.component.TitlePanel; +import org.jivesoftware.spark.component.Tree; +import org.jivesoftware.spark.ui.ChatRoomNotFoundException; +import org.jivesoftware.spark.ui.rooms.GroupChatRoom; +import org.jivesoftware.spark.util.ResourceUtils; +import org.jivesoftware.spark.util.SwingWorker; +import org.jivesoftware.spark.util.log.Log; +import org.jivesoftware.sparkimpl.preference.chat.ChatPreference; +import org.jivesoftware.sparkimpl.preference.chat.ChatPreferences; + +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.JDialog; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JPopupMenu; +import javax.swing.JScrollPane; +import javax.swing.JTable; +import javax.swing.ListSelectionModel; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; +import javax.swing.table.DefaultTableCellRenderer; +import javax.swing.table.TableCellRenderer; +import javax.swing.tree.DefaultTreeModel; +import javax.swing.tree.TreePath; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Component; +import java.awt.FlowLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; + +/** + * A UI that handles all Group Rooms contained in an XMPP Messenger server. This handles + * creation and joining of rooms for group chat discussions as well as the listing + * of the creation times, number of occupants in a room, and the room name itself. + */ +public class ConferenceRooms extends JPanel implements ActionListener { + private final RoomList roomsTable; + private final RolloverButton createButton = new RolloverButton("Create Room", SparkRes.getImageIcon(SparkRes.SMALL_USER1_NEW)); + private final RolloverButton joinRoomButton = new RolloverButton("Join Room", SparkRes.getImageIcon(SparkRes.DOOR_IMAGE)); + private final RolloverButton refreshButton = new RolloverButton("Refresh", SparkRes.getImageIcon(SparkRes.REFRESH_IMAGE)); + private final RolloverButton addRoomButton = new RolloverButton("Bookmark Room", SparkRes.getImageIcon(SparkRes.ADD_BOOKMARK_ICON)); + + private ChatManager chatManager; + + private JDialog dlg; + + private Tree serviceTree; + private String serviceName; + + private boolean partialDiscovery = false; + + /** + * Creates a new instance of ConferenceRooms. + * + * @param serviceTree the service tree. + * @param serviceName the name of the conference service. + * //TODO This needs to be refactored. + */ + public ConferenceRooms(final Tree serviceTree, final String serviceName) { + + this.setLayout(new BorderLayout()); + + this.serviceTree = serviceTree; + this.serviceName = serviceName; + + // Add Toolbar + final JPanel toolbar = new JPanel(new FlowLayout(FlowLayout.LEFT)); + toolbar.add(joinRoomButton); + toolbar.add(addRoomButton); + toolbar.add(createButton); + toolbar.add(refreshButton); + + this.add(toolbar, BorderLayout.NORTH); + createButton.addActionListener(this); + joinRoomButton.addActionListener(this); + refreshButton.addActionListener(this); + + ResourceUtils.resButton(createButton, "&Create or Join Room"); + ResourceUtils.resButton(joinRoomButton, "&Join Selected Room"); + ResourceUtils.resButton(refreshButton, "&Refresh"); + ResourceUtils.resButton(addRoomButton, "&Bookmark Room"); + + refreshButton.setToolTipText("Update Room List"); + joinRoomButton.setToolTipText("Join Conference Room"); + createButton.setToolTipText("Create or Join a conference room"); + + // Add Group Chat Table + roomsTable = new RoomList(); + + final JScrollPane pane = new JScrollPane(roomsTable); + pane.setBackground(Color.white); + pane.setForeground(Color.white); + this.setBackground(Color.white); + this.setForeground(Color.white); + pane.getViewport().setBackground(Color.white); + this.add(pane, BorderLayout.CENTER); + + chatManager = SparkManager.getChatManager(); + + joinRoomButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent actionEvent) { + joinSelectedRoom(); + } + }); + + addRoomButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent actionEvent) { + bookmarkRoom(serviceName, serviceTree); + } + }); + + refreshButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent actionEvent) { + refreshRoomList(serviceName); + } + }); + + joinRoomButton.setEnabled(false); + addRoomButton.setEnabled(false); + + addTableListener(); + } + + private void refreshRoomList(final String serviceName) { + roomsTable.clearTable(); + SwingWorker worker = new SwingWorker() { + Collection result; + + public Object construct() { + result = getRoomsAndInfo(serviceName); + return result; + } + + public void finished() { + try { + + + Iterator rooms = result.iterator(); + while (rooms.hasNext()) { + RoomObject obj = (RoomObject)rooms.next(); + addRoomToTable(obj.getRoomJID(), obj.getRoomName(), obj.getNumberOfOccupants()); + } + } + catch (Exception e) { + Log.error("Unable to retrieve room list and info.", e); + } + } + }; + + worker.start(); + + + } + + private Collection getRoomsAndInfo(final String serviceName) { + List roomList = new ArrayList(); + boolean stillSearchForOccupants = true; + try { + Collection result = getRoomList(serviceName); + try { + Iterator rooms = result.iterator(); + while (rooms.hasNext()) { + HostedRoom hostedRoom = (HostedRoom)rooms.next(); + String roomName = hostedRoom.getName(); + String roomJID = hostedRoom.getJid(); + int numberOfOccupants = -1; + if (stillSearchForOccupants) { + RoomInfo roomInfo = null; + try { + roomInfo = MultiUserChat.getRoomInfo(SparkManager.getConnection(), roomJID); + } + catch (Exception e) { + } + + if (roomInfo != null) { + numberOfOccupants = roomInfo.getOccupantsCount(); + if (numberOfOccupants == -1) { + stillSearchForOccupants = false; + } + } + else { + stillSearchForOccupants = false; + } + } + + RoomObject obj = new RoomObject(); + obj.setRoomJID(roomJID); + obj.setRoomName(roomName); + obj.setNumberOfOccupants(numberOfOccupants); + roomList.add(obj); + } + } + catch (Exception e) { + Log.error("Error setting up GroupChatTable", e); + } + } + catch (Exception e) { + System.err.println(e); + } + return roomList; + } + + private void bookmarkRoom(String serviceName, Tree serviceTree) { + int selectedRow = roomsTable.getSelectedRow(); + if (-1 == selectedRow) { + JOptionPane.showMessageDialog(dlg, "Please select a room to add to your service list.", "Group Chat", JOptionPane.INFORMATION_MESSAGE); + return; + } + + final String roomJID = (String)roomsTable.getValueAt(selectedRow, 2) + "@" + serviceName; + final String roomName = (String)roomsTable.getValueAt(selectedRow, 1); + + // Check to see what type of room this is. + try { + final RoomInfo roomInfo = MultiUserChat.getRoomInfo(SparkManager.getConnection(), roomJID); + if (!roomInfo.isPersistent()) { + JOptionPane.showMessageDialog(dlg, "You cannot bookmark temporary rooms.", "Group Chat Error", JOptionPane.ERROR_MESSAGE); + return; + } + } + catch (XMPPException e) { + return; + } + + JiveTreeNode rootNode = (JiveTreeNode)serviceTree.getModel().getRoot(); + + TreePath rootPath = serviceTree.findByName(serviceTree, new String[]{rootNode.toString(), serviceName}); + + + boolean isBookmarked = isBookmarked(roomJID); + + + if (!isBookmarked) { + JiveTreeNode node = (JiveTreeNode)serviceTree.getLastSelectedPathComponent(); + if (node == null) { + TreePath path = serviceTree.findByName(serviceTree, new String[]{rootNode.toString(), Conferences.getDefaultServiceName()}); + node = (JiveTreeNode)path.getLastPathComponent(); + } + JiveTreeNode roomNode = new JiveTreeNode(roomName, false, SparkRes.getImageIcon(SparkRes.BOOKMARK_ICON)); + roomNode.setAssociatedObject(roomJID); + node.add(roomNode); + final DefaultTreeModel model = (DefaultTreeModel)serviceTree.getModel(); + model.nodeStructureChanged(node); + serviceTree.expandPath(rootPath); + roomsTable.setValueAt(new JLabel(SparkRes.getImageIcon(SparkRes.BOOKMARK_ICON)), selectedRow, 0); + addBookmarkUI(false); + } + else { + // Remove bookmark + TreePath path = serviceTree.findByName(serviceTree, new String[]{rootNode.toString(), serviceName, roomName}); + JiveTreeNode node = (JiveTreeNode)path.getLastPathComponent(); + final DefaultTreeModel model = (DefaultTreeModel)serviceTree.getModel(); + model.removeNodeFromParent(node); + roomsTable.setValueAt(new JLabel(SparkRes.getImageIcon(SparkRes.BLANK_IMAGE)), selectedRow, 0); + addBookmarkUI(true); + } + } + + private void joinSelectedRoom() { + int selectedRow = roomsTable.getSelectedRow(); + if (-1 == selectedRow) { + JOptionPane.showMessageDialog(dlg, "Please select a room to join.", "Group Chat", JOptionPane.INFORMATION_MESSAGE); + return; + } + enterRoom(); + } + + private void addTableListener() { + roomsTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() { + public void valueChanged(ListSelectionEvent e) { + if (e.getValueIsAdjusting()) return; + + int selectedRow = roomsTable.getSelectedRow(); + if (selectedRow != -1) { + joinRoomButton.setEnabled(true); + + String roomName = (String)roomsTable.getValueAt(selectedRow, 1); + String roomJID = (String)roomsTable.getValueAt(selectedRow, 2) + "@" + serviceName; + addRoomButton.setEnabled(true); + if (isBookmarked(roomJID)) { + addBookmarkUI(false); + } + else { + addBookmarkUI(true); + } + } + else { + joinRoomButton.setEnabled(false); + addRoomButton.setEnabled(false); + addBookmarkUI(true); + } + } + }); + } + + /** + * Displays the ConferenceRoomBrowser. + */ + public void invoke() { + SwingWorker worker = new SwingWorker() { + Collection rooms; + + public Object construct() { + try { + rooms = getRoomList(serviceName); + } + catch (Exception e) { + Log.error("Unable to retrieve list of rooms.", e); + } + + return "OK"; + } + + public void finished() { + if (rooms == null) { + JOptionPane.showMessageDialog(serviceTree, "Unable to retrieve conference information. Please try back later.", "Service Not Reachable", JOptionPane.ERROR_MESSAGE); + if (dlg != null) { + dlg.dispose(); + } + } + try { + Iterator iter = rooms.iterator(); + while (iter.hasNext()) { + HostedRoom hostedRoom = (HostedRoom)iter.next(); + String roomName = hostedRoom.getName(); + String roomJID = hostedRoom.getJid(); + + int numberOfOccupants = -1; + + // Need to handle the case where the room info does not contain the number of occupants. If that is the case, + // we should not continue to request this info from the service. + if (!partialDiscovery) { + RoomInfo roomInfo = null; + try { + roomInfo = MultiUserChat.getRoomInfo(SparkManager.getConnection(), roomJID); + } + catch (Exception e) { + } + + if (roomInfo != null) { + numberOfOccupants = roomInfo.getOccupantsCount(); + } + if (roomInfo == null || numberOfOccupants == -1) { + partialDiscovery = true; + } + } + addRoomToTable(roomJID, roomName, numberOfOccupants); + } + } + catch (Exception e) { + Log.error("Error setting up GroupChatTable", e); + } + } + }; + + worker.start(); + // Find Initial Rooms + + + final JOptionPane pane; + + + TitlePanel titlePanel; + + // Create the title panel for this dialog + titlePanel = new TitlePanel("Join or Bookmark Room", "Add room to favorites list or join directly.", SparkRes.getImageIcon(SparkRes.BLANK_IMAGE), true); + + // Construct main panel w/ layout. + final JPanel mainPanel = new JPanel(); + mainPanel.setLayout(new BorderLayout()); + mainPanel.add(titlePanel, BorderLayout.NORTH); + + // The user should only be able to close this dialog. + Object[] options = {"Close"}; + pane = new JOptionPane(this, JOptionPane.PLAIN_MESSAGE, JOptionPane.OK_CANCEL_OPTION, null, options, options[0]); + + mainPanel.add(pane, BorderLayout.CENTER); + + final JOptionPane p = new JOptionPane(); + + dlg = p.createDialog(SparkManager.getMainWindow(), "Browse Conference Rooms - " + serviceName); + dlg.setModal(false); + + dlg.pack(); + dlg.setSize(500, 400); + dlg.setResizable(true); + dlg.setContentPane(mainPanel); + dlg.setLocationRelativeTo(SparkManager.getMainWindow()); + + PropertyChangeListener changeListener = new PropertyChangeListener() { + public void propertyChange(PropertyChangeEvent e) { + String value = (String)pane.getValue(); + if ("Close".equals(value)) { + pane.removePropertyChangeListener(this); + dlg.dispose(); + } + else if ("Add".equals(value)) { + + pane.setValue(JOptionPane.UNINITIALIZED_VALUE); + } + } + }; + + pane.addPropertyChangeListener(changeListener); + + dlg.addKeyListener(new KeyAdapter() { + public void keyPressed(KeyEvent e) { + if (e.getKeyChar() == KeyEvent.VK_ESCAPE) { + dlg.dispose(); + } + } + }); + + dlg.setVisible(true); + dlg.toFront(); + dlg.requestFocus(); + } + + private final class RoomList extends JiveSortableTable { + public RoomList() { + super(new String[]{" ", "Name", "Address", "Occupants"}); + getColumnModel().setColumnMargin(0); + getColumnModel().getColumn(0).setMaxWidth(30); + getColumnModel().getColumn(3).setMaxWidth(80); + + setSelectionBackground(Table.SELECTION_COLOR); + setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + setRowSelectionAllowed(true); + setSortable(true); + + addMouseListener(new MouseAdapter() { + public void mouseClicked(MouseEvent e) { + if (e.getClickCount() == 2) { + enterRoom(); + } + } + + public void mouseReleased(MouseEvent e) { + checkPopup(e); + } + + public void mousePressed(MouseEvent e) { + checkPopup(e); + } + }); + + } + + // Handle image rendering correctly + public TableCellRenderer getCellRenderer(int row, int column) { + Object o = getValueAt(row, column); + if (o != null) { + if (o instanceof JLabel) { + return new JLabelRenderer(false); + } + } + + if (column == 3) { + return new CenterRenderer(); + } + + return super.getCellRenderer(row, column); + } + + private void checkPopup(MouseEvent e) { + if (e.isPopupTrigger()) { + final JPopupMenu popupMenu = new JPopupMenu(); + + Action roomInfoAction = new AbstractAction() { + public void actionPerformed(ActionEvent actionEvent) { + int selectedRow = roomsTable.getSelectedRow(); + if (selectedRow != -1) { + String roomJID = (String)roomsTable.getValueAt(selectedRow, 2) + "@" + serviceName; + RoomBrowser roomBrowser = new RoomBrowser(); + roomBrowser.displayRoomInformation(roomJID); + } + } + }; + + roomInfoAction.putValue(Action.NAME, "View Room Info"); + roomInfoAction.putValue(Action.SMALL_ICON, SparkRes.getImageIcon(SparkRes.SMALL_DATA_FIND_IMAGE)); + + popupMenu.add(roomInfoAction); + popupMenu.show(roomsTable, e.getX(), e.getY()); + } + } + + } + + + public void actionPerformed(ActionEvent e) { + if (e.getSource() == createButton) { + createRoom(); + } + } + + private void enterRoom() { + int selectedRow = roomsTable.getSelectedRow(); + if (-1 == selectedRow) { + JOptionPane.showMessageDialog(dlg, "You must select a room to enter.", "Group Chat", JOptionPane.INFORMATION_MESSAGE); + return; + } + final String roomJID = (String)roomsTable.getValueAt(selectedRow, 2) + "@" + serviceName; + final String roomDescription = (String)roomsTable.getValueAt(selectedRow, 1); + + try { + chatManager.getChatContainer().getChatRoom(roomJID); + } + catch (ChatRoomNotFoundException e1) { + ConferenceUtils.autoJoinConferenceRoom(roomDescription, roomJID, null); + } + } + + /** + * Returns a Collection of all rooms in the specified Conference Service. + * + * @param serviceName the name of the conference service. + * @return a Collection of all rooms in the Conference service. + * @throws Exception + */ + private static Collection getRoomList(String serviceName) throws Exception { + return MultiUserChat.getHostedRooms(SparkManager.getConnection(), serviceName); + } + + + /** + * Create a new room based on room table selection. + */ + private void createRoom() { + ConferenceCreator mucRoomDialog = new ConferenceCreator(); + final MultiUserChat groupChat = mucRoomDialog.createGroupChat(SparkManager.getMainWindow(), serviceName); + final ChatPreferences pref = (ChatPreferences)SparkManager.getPreferenceManager().getPreferenceData(ChatPreference.NAMESPACE); + + if (null != groupChat) { + + // Join Room + try { + GroupChatRoom room = new GroupChatRoom(groupChat); + + chatManager.getChatContainer().addChatRoom(room); + chatManager.getChatContainer().activateChatRoom(room); + groupChat.create(pref.getNickname()); + + // Send Form + Form form = groupChat.getConfigurationForm().createAnswerForm(); + if (mucRoomDialog.isPasswordProtected()) { + String password = mucRoomDialog.getPassword(); + form.setAnswer("muc#roomconfig_passwordprotectedroom", true); + form.setAnswer("muc#roomconfig_roomsecret", password); + } + form.setAnswer("muc#roomconfig_roomname", mucRoomDialog.getRoomName()); + + if (mucRoomDialog.isPermanent()) { + form.setAnswer("muc#roomconfig_persistentroom", true); + } + + List owners = new ArrayList(); + owners.add(SparkManager.getSessionManager().getBareAddress()); + form.setAnswer("muc#roomconfig_roomowners", owners); + + // new DataFormDialog(groupChat, form); + groupChat.sendConfigurationForm(form); + + + } + catch (XMPPException e1) { + Log.error("Error creating new room.", e1); + } + + addRoomToTable(groupChat.getRoom(), StringUtils.parseName(groupChat.getRoom()), 1); + } + } + + /** + * Adds a room to the room table. + * + * @param jid the jid of the conference room. + * @param roomName the name of the conference room. + * @param numberOfOccupants the number of occupants in the conference room. If -1 is specified, + * the the occupant count will show as n/a. + */ + private void addRoomToTable(String jid, String roomName, int numberOfOccupants) { + JLabel bookmarkedLabel = new JLabel(); + if (isBookmarked(jid)) { + bookmarkedLabel.setIcon(SparkRes.getImageIcon(SparkRes.BOOKMARK_ICON)); + } + + String occupants = Integer.toString(numberOfOccupants); + if (numberOfOccupants == -1) { + occupants = "n/a"; + } + + final Object[] insertRoom = new Object[]{bookmarkedLabel, roomName, StringUtils.parseName(jid), occupants}; + roomsTable.getTableModel().addRow(insertRoom); + } + + /** + * Returns true if the room specified is bookmarked. + * + * @param roomJID the jid of the room to check. + * @return true if the room is bookmarked. + */ + private boolean isBookmarked(String roomJID) { + JiveTreeNode rootNode = (JiveTreeNode)serviceTree.getModel().getRoot(); + TreePath path = serviceTree.findByName(serviceTree, new String[]{rootNode.toString(), serviceName}); + + JiveTreeNode serviceNode = (JiveTreeNode)path.getLastPathComponent(); + + // Otherwise, traverse through the path. + int childCount = serviceNode.getChildCount(); + for (int i = 0; i < childCount; i++) { + JiveTreeNode node = (JiveTreeNode)serviceNode.getChildAt(i); + + // If the jid (nodeObject) of the node equals the roomJID specified, then return true. + if (node.getAssociatedObject() != null && node.getAssociatedObject().equals(roomJID)) { + return true; + } + } + return false; + } + + /** + * Toggles the bookmark room button depending on it's state. + * + * @param addBookmark true if the button should display itself as bookmarkable :) + */ + private void addBookmarkUI(boolean addBookmark) { + if (!addBookmark) { + addRoomButton.setText("Remove Bookmark"); + addRoomButton.setIcon(SparkRes.getImageIcon(SparkRes.DELETE_BOOKMARK_ICON)); + } + else { + addRoomButton.setText("Bookmark Room"); + addRoomButton.setIcon(SparkRes.getImageIcon(SparkRes.ADD_BOOKMARK_ICON)); + } + } + + /* + ** Center the text + */ + static class CenterRenderer extends DefaultTableCellRenderer { + public CenterRenderer() { + setHorizontalAlignment(CENTER); + } + + public Component getTableCellRendererComponent( + JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { + super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); + return this; + } + } + + private class RoomObject { + private String roomName; + private String roomJID; + + int numberOfOccupants; + + public String getRoomName() { + return roomName; + } + + public void setRoomName(String roomName) { + this.roomName = roomName; + } + + public String getRoomJID() { + return roomJID; + } + + public void setRoomJID(String roomJID) { + this.roomJID = roomJID; + } + + public int getNumberOfOccupants() { + return numberOfOccupants; + } + + public void setNumberOfOccupants(int numberOfOccupants) { + this.numberOfOccupants = numberOfOccupants; + } + } + + +} + diff --git a/src/java/org/jivesoftware/spark/ui/conferences/ConferenceServiceBrowser.java b/src/java/org/jivesoftware/spark/ui/conferences/ConferenceServiceBrowser.java new file mode 100644 index 00000000..e7ac9ca0 --- /dev/null +++ b/src/java/org/jivesoftware/spark/ui/conferences/ConferenceServiceBrowser.java @@ -0,0 +1,164 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.ui.conferences; + +import org.jivesoftware.resource.SparkRes; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smackx.ServiceDiscoveryManager; +import org.jivesoftware.smackx.packet.DiscoverInfo; +import org.jivesoftware.smackx.packet.DiscoverItems; +import org.jivesoftware.spark.SparkManager; +import org.jivesoftware.spark.component.RolloverButton; +import org.jivesoftware.spark.component.TitlePanel; +import org.jivesoftware.spark.util.ModelUtil; +import org.jivesoftware.spark.util.ResourceUtils; +import org.jivesoftware.spark.util.log.Log; + +import javax.swing.BorderFactory; +import javax.swing.DefaultListModel; +import javax.swing.JDialog; +import javax.swing.JLabel; +import javax.swing.JList; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTextField; +import javax.swing.ListSelectionModel; + +import java.awt.BorderLayout; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; + +public class ConferenceServiceBrowser { + + public String getSelectedService() { + final JLabel serverAddressLabel = new JLabel(); + final JTextField serverAddress = new JTextField(); + final RolloverButton findButton = new RolloverButton(); + + ResourceUtils.resLabel(serverAddressLabel, serverAddress, "&Server Address:"); + ResourceUtils.resButton(findButton, "&Find"); + + final JPanel mainPanel = new JPanel(); + mainPanel.setLayout(new GridBagLayout()); + + mainPanel.add(serverAddressLabel, new GridBagConstraints(0, 1, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + mainPanel.add(serverAddress, new GridBagConstraints(1, 1, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 0, 5), 0, 0)); + mainPanel.add(findButton, new GridBagConstraints(2, 1, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + mainPanel.add(new JLabel("ex. jabber.org"), new GridBagConstraints(1, 2, 1, 1, 0.0, 0.0, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, new Insets(0, 5, 0, 5), 0, 0)); + + // Add Empty CheckBox List + final DefaultListModel model = new DefaultListModel(); + final JList list = new JList(model); + list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + JScrollPane scrollPane = new JScrollPane(list); + scrollPane.setBorder(BorderFactory.createTitledBorder("Conference Services Found")); + mainPanel.add(scrollPane, new GridBagConstraints(0, 3, 3, 1, 1.0, 1.0, GridBagConstraints.WEST, GridBagConstraints.BOTH, new Insets(5, 5, 5, 5), 0, 0)); + + // Add Listener to find button + findButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + String address = serverAddress.getText(); + if (ModelUtil.hasLength(address)) { + try { + Collection col = getConferenceServices(address); + Iterator services = col.iterator(); + while (services.hasNext()) { + String service = (String)services.next(); + model.addElement(service); + } + } + catch (Exception e1) { + Log.error(e1); + } + + } + } + }); + + // The user should only be able to close this dialog. + Object[] options = {"Ok", "Close"}; + final JOptionPane pane = new JOptionPane(mainPanel, JOptionPane.PLAIN_MESSAGE, JOptionPane.OK_CANCEL_OPTION, null, options, options[0]); + + JPanel topPanel = new JPanel(); + topPanel.setLayout(new BorderLayout()); + TitlePanel titlePanel = new TitlePanel("Browse Conference Services", "Find conference services in server", SparkRes.getImageIcon(SparkRes.BLANK_IMAGE), true); + topPanel.add(titlePanel, BorderLayout.NORTH); + topPanel.add(pane, BorderLayout.CENTER); + + final JOptionPane p = new JOptionPane(); + + final JDialog dlg = p.createDialog(SparkManager.getMainWindow(), "Find Conference Service"); + dlg.setModal(true); + + dlg.pack(); + dlg.setSize(600, 400); + dlg.setResizable(true); + dlg.setContentPane(topPanel); + dlg.setLocationRelativeTo(SparkManager.getMainWindow()); + + PropertyChangeListener changeListener = new PropertyChangeListener() { + public void propertyChange(PropertyChangeEvent e) { + String value = (String)pane.getValue(); + if ("Close".equals(value)) { + list.clearSelection(); + pane.removePropertyChangeListener(this); + dlg.dispose(); + } + else if ("Ok".equals(value)) { + pane.removePropertyChangeListener(this); + dlg.dispose(); + } + } + }; + + pane.addPropertyChangeListener(changeListener); + serverAddress.requestFocusInWindow(); + dlg.setVisible(true); + dlg.toFront(); + + + return (String)list.getSelectedValue(); + } + + public Collection getConferenceServices(String server) throws Exception { + List answer = new ArrayList(); + ServiceDiscoveryManager discoManager = ServiceDiscoveryManager.getInstanceFor(SparkManager.getConnection()); + DiscoverItems items = discoManager.discoverItems(server); + for (Iterator it = items.getItems(); it.hasNext();) { + DiscoverItems.Item item = (DiscoverItems.Item)it.next(); + if (item.getEntityID().startsWith("conference") || item.getEntityID().startsWith("private")) { + answer.add(item.getEntityID()); + } + else { + try { + DiscoverInfo info = discoManager.discoverInfo(item.getEntityID()); + if (info.containsFeature("http://jabber.org/protocol/muc")) { + answer.add(item.getEntityID()); + } + } + catch (XMPPException e) { + + } + } + } + return answer; + } +} diff --git a/src/java/org/jivesoftware/spark/ui/conferences/ConferenceUtils.java b/src/java/org/jivesoftware/spark/ui/conferences/ConferenceUtils.java new file mode 100644 index 00000000..2a7162c5 --- /dev/null +++ b/src/java/org/jivesoftware/spark/ui/conferences/ConferenceUtils.java @@ -0,0 +1,546 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.ui.conferences; + +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smackx.Form; +import org.jivesoftware.smackx.FormField; +import org.jivesoftware.smackx.ServiceDiscoveryManager; +import org.jivesoftware.smackx.muc.MultiUserChat; +import org.jivesoftware.smackx.muc.RoomInfo; +import org.jivesoftware.smackx.packet.DataForm; +import org.jivesoftware.smackx.packet.DiscoverInfo; +import org.jivesoftware.spark.ChatManager; +import org.jivesoftware.spark.SparkManager; +import org.jivesoftware.spark.ui.ChatRoomNotFoundException; +import org.jivesoftware.spark.ui.rooms.GroupChatRoom; +import org.jivesoftware.spark.util.ModelUtil; +import org.jivesoftware.spark.util.SwingWorker; +import org.jivesoftware.spark.util.log.Log; +import org.jivesoftware.sparkimpl.preference.chat.ChatPreference; +import org.jivesoftware.sparkimpl.preference.chat.ChatPreferences; + +import javax.swing.JOptionPane; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.Iterator; +import java.util.List; + +public class ConferenceUtils { + private ConferenceUtils() { + } + + /** + * Return a list of available Conference rooms from the server + * based on the service name. + * + * @param serviceName the service name (ex. conference@jivesoftware.com) + * @return a collection of rooms. + * @throws Exception if an error occured during fetch. + */ + public static Collection getRoomList(String serviceName) throws Exception { + return MultiUserChat.getHostedRooms(SparkManager.getConnection(), serviceName); + } + + /** + * Return the number of occupants in a room. + * + * @param roomJID the full JID of the conference room. (ex. dev@conference.jivesoftware.com) + * @return the number of occupants in the room if available. + * @throws XMPPException thrown if an error occured during retrieval of the information. + */ + public static int getNumberOfOccupants(String roomJID) throws XMPPException { + final RoomInfo roomInfo = MultiUserChat.getRoomInfo(SparkManager.getConnection(), roomJID); + return roomInfo.getOccupantsCount(); + } + + /** + * Retrieve the date (in yyyyMMdd) format of the time the room was created. + * + * @param roomJID the jid of the room. + * @return the formatted date. + * @throws Exception throws an exception if we are unable to retrieve the date. + */ + public static String getCreationDate(String roomJID) throws Exception { + ServiceDiscoveryManager discoManager = ServiceDiscoveryManager.getInstanceFor(SparkManager.getConnection()); + + final DateFormat dateFormatter = new SimpleDateFormat("yyyyMMdd'T'HH:mm:ss"); + final SimpleDateFormat simpleFormat = new SimpleDateFormat("EEE MM/dd/yyyy h:mm:ss a"); + DiscoverInfo infoResult = discoManager.discoverInfo(roomJID); + DataForm dataForm = (DataForm)infoResult.getExtension("x", "jabber:x:data"); + if (dataForm == null) { + return "Not available"; + } + Iterator fieldIter = dataForm.getFields(); + String creationDate = ""; + while (fieldIter.hasNext()) { + FormField field = (FormField)fieldIter.next(); + String label = field.getLabel(); + + + if (label != null && "Creation date".equalsIgnoreCase(label)) { + Iterator valueIterator = field.getValues(); + while (valueIterator.hasNext()) { + Object oo = valueIterator.next(); + creationDate = "" + oo; + Date date = dateFormatter.parse(creationDate); + creationDate = simpleFormat.format(date); + } + } + } + return creationDate; + } + + public static void autoJoinConferenceRoom(final String roomName, String roomJID, String password) { + ChatManager chatManager = SparkManager.getChatManager(); + + final MultiUserChat groupChat = new MultiUserChat(SparkManager.getConnection(), roomJID); + ChatPreferences chatPref = (ChatPreferences)SparkManager.getPreferenceManager().getPreferenceData(ChatPreference.NAMESPACE); + final String nickname = chatPref.getNickname().trim(); + + + try { + GroupChatRoom chatRoom = (GroupChatRoom)chatManager.getChatContainer().getChatRoom(roomJID); + MultiUserChat muc = chatRoom.getMultiUserChat(); + if (!muc.isJoined()) { + joinRoom(muc, nickname, password); + } + chatManager.getChatContainer().activateChatRoom(chatRoom); + return; + } + catch (ChatRoomNotFoundException e) { + } + + final GroupChatRoom room = new GroupChatRoom(groupChat); + room.setTabTitle(roomName); + + + if (requiresPassword(roomJID) && password == null) { + password = JOptionPane.showInputDialog(null, "Enter Room Password", "Need Password", JOptionPane.QUESTION_MESSAGE); + if (!ModelUtil.hasLength(password)) { + return; + } + } + + + final List errors = new ArrayList(); + final String userPassword = password; + + final SwingWorker startChat = new SwingWorker() { + public Object construct() { + if (!groupChat.isJoined()) { + int groupChatCounter = 0; + while (true) { + groupChatCounter++; + String joinName = nickname; + if (groupChatCounter > 1) { + joinName = joinName + groupChatCounter; + } + if (groupChatCounter < 10) { + try { + if (ModelUtil.hasLength(userPassword)) { + groupChat.join(joinName, userPassword); + } + else { + groupChat.join(joinName); + } + break; + } + catch (XMPPException ex) { + int code = 0; + if (ex.getXMPPError() != null) { + code = ex.getXMPPError().getCode(); + } + + if (code == 0) { + errors.add("No response from server."); + } + else if (code == 401) { + errors.add("The password did not match the room's password."); + } + else if (code == 403) { + errors.add("You have been banned from this room."); + } + else if (code == 404) { + errors.add("The room you are trying to enter does not exist."); + } + else if (code == 407) { + errors.add("You are not a member of this room.\nThis room requires you to be a member to join."); + } + else if (code != 409) { + break; + } + } + } + else { + break; + } + } + } + return "ok"; + } + + public void finished() { + if (errors.size() > 0) { + String error = (String)errors.get(0); + JOptionPane.showMessageDialog(SparkManager.getMainWindow(), error, "Unable to join the room at this time.", JOptionPane.ERROR_MESSAGE); + return; + } + else if (groupChat.isJoined()) { + ChatManager chatManager = SparkManager.getChatManager(); + chatManager.getChatContainer().addChatRoom(room); + chatManager.getChatContainer().activateChatRoom(room); + } + else { + JOptionPane.showMessageDialog(SparkManager.getMainWindow(), "Unable to join the room.", "Error", JOptionPane.ERROR_MESSAGE); + return; + } + } + }; + + startChat.start(); + + + } + + public static void joinConferenceRoom(final String roomName, String roomJID) { + JoinConferenceRoomDialog joinDialog = new JoinConferenceRoomDialog(); + joinDialog.joinRoom(roomJID, roomName); + } + + public static void enterRoom(final MultiUserChat groupChat, String tabTitle, final String nickname, final String password) { + final GroupChatRoom room = new GroupChatRoom(groupChat); + room.setTabTitle(tabTitle); + if (room == null) { + return; + } + + + final List errors = new ArrayList(); + + + if (!groupChat.isJoined()) { + int groupChatCounter = 0; + while (true) { + groupChatCounter++; + String joinName = nickname; + if (groupChatCounter > 1) { + joinName = joinName + groupChatCounter; + } + if (groupChatCounter < 10) { + try { + if (ModelUtil.hasLength(password)) { + groupChat.join(joinName, password); + } + else { + groupChat.join(joinName); + } + break; + } + catch (XMPPException ex) { + int code = 0; + if (ex.getXMPPError() != null) { + code = ex.getXMPPError().getCode(); + } + + if (code == 0) { + errors.add("No response from server."); + } + else if (code == 401) { + errors.add("A Password is required to enter this room."); + } + else if (code == 403) { + errors.add("You have been banned from this room."); + } + else if (code == 404) { + errors.add("The room you are trying to enter does not exist."); + } + else if (code == 407) { + errors.add("You are not a member of this room.\nThis room requires you to be a member to join."); + } + else if (code != 409) { + break; + } + } + } + else { + break; + } + } + } + + if (errors.size() > 0) { + String error = (String)errors.get(0); + JOptionPane.showMessageDialog(SparkManager.getMainWindow(), error, "Could Not Join Room", JOptionPane.ERROR_MESSAGE); + return; + } + else if (groupChat.isJoined()) { + ChatManager chatManager = SparkManager.getChatManager(); + chatManager.getChatContainer().addChatRoom(room); + chatManager.getChatContainer().activateChatRoom(room); + } + else { + JOptionPane.showMessageDialog(SparkManager.getMainWindow(), "Unable to join room.", "Error", JOptionPane.ERROR_MESSAGE); + return; + } + + } + + public static List joinRoom(MultiUserChat groupChat, String nickname, String password) { + final List errors = new ArrayList(); + if (!groupChat.isJoined()) { + int groupChatCounter = 0; + while (true) { + groupChatCounter++; + String joinName = nickname; + if (groupChatCounter > 1) { + joinName = joinName + groupChatCounter; + } + if (groupChatCounter < 10) { + try { + if (ModelUtil.hasLength(password)) { + groupChat.join(joinName, password); + } + else { + groupChat.join(joinName); + } + break; + } + catch (XMPPException ex) { + int code = 0; + if (ex.getXMPPError() != null) { + code = ex.getXMPPError().getCode(); + } + + if (code == 0) { + errors.add("No response from server."); + } + else if (code == 401) { + errors.add("A Password is required to enter this room."); + } + else if (code == 403) { + errors.add("You have been banned from this room."); + } + else if (code == 404) { + errors.add("The room you are trying to enter does not exist."); + } + else if (code == 407) { + errors.add("You are not a member of this room.\nThis room requires you to be a member to join."); + } + else if (code != 409) { + break; + } + } + } + else { + break; + } + } + } + + return errors; + } + + /** + * Invites users to an existing room. + * + * @param serviceName the service name to use. + * @param roomName the name of the room. + * @param jids a collection of the users to invite. + */ + public static final void inviteUsersToRoom(String serviceName, String roomName, Collection jids) { + ConferenceInviteDialog inviteDialog = new ConferenceInviteDialog(); + inviteDialog.inviteUsersToRoom(serviceName, roomName, jids); + } + + /** + * Returns true if the room requires a password. + * + * @param roomJID the JID of the room. + * @return true if the room requires a password. + */ + public static final boolean requiresPassword(String roomJID) { + // Check to see if the room is password protected + ServiceDiscoveryManager discover = new ServiceDiscoveryManager(SparkManager.getConnection()); + + + try { + DiscoverInfo info = discover.discoverInfo(roomJID); + return info.containsFeature("muc_passwordprotected"); + } + catch (XMPPException e) { + Log.error(e); + } + return false; + } + + /** + * Creates a private conference. + * + * @param serviceName the service name to use for the private conference. + * @param message the message sent to each individual invitee. + * @param roomName the name of the room to create. + * @param jids a collection of the user JIDs to invite. + */ + public static void createPrivateConference(String serviceName, String message, String roomName, Collection jids) { + final MultiUserChat chatRoom = new MultiUserChat(SparkManager.getConnection(), roomName + "@" + serviceName); + + final GroupChatRoom room = new GroupChatRoom(chatRoom); + + try { + ChatPreferences chatPref = (ChatPreferences)SparkManager.getPreferenceManager().getPreferenceData(ChatPreference.NAMESPACE); + chatRoom.create(chatPref.getNickname()); + + // Since this is a private room, make the room not public and set user as owner of the room. + Form submitForm = chatRoom.getConfigurationForm().createAnswerForm(); + submitForm.setAnswer("muc#roomconfig_publicroom", false); + submitForm.setAnswer("muc#roomconfig_roomname", roomName); + + List owners = new ArrayList(); + owners.add(SparkManager.getSessionManager().getBareAddress()); + submitForm.setAnswer("muc#roomconfig_roomowners", owners); + + chatRoom.sendConfigurationForm(submitForm); + } + catch (XMPPException e1) { + Log.error("Unable to send conference room chat configuration form.", e1); + } + + + ChatManager chatManager = SparkManager.getChatManager(); + + // Check if room already is open + try { + chatManager.getChatContainer().getChatRoom(room.getRoomname()); + } + catch (ChatRoomNotFoundException e) { + chatManager.getChatContainer().addChatRoom(room); + chatManager.getChatContainer().activateChatRoom(room); + } + + final Iterator jidsToInvite = jids.iterator(); + while (jidsToInvite.hasNext()) { + String jid = (String)jidsToInvite.next(); + chatRoom.invite(jid, message); + + room.getTranscriptWindow().insertNotificationMessage("Waiting for " + jid + " to join."); + } + } + + public static GroupChatRoom enterRoomOnSameThread(final String roomName, String roomJID, String password) { + ChatManager chatManager = SparkManager.getChatManager(); + + final MultiUserChat groupChat = new MultiUserChat(SparkManager.getConnection(), roomJID); + ChatPreferences chatPref = (ChatPreferences)SparkManager.getPreferenceManager().getPreferenceData(ChatPreference.NAMESPACE); + final String nickname = chatPref.getNickname().trim(); + + + try { + GroupChatRoom chatRoom = (GroupChatRoom)chatManager.getChatContainer().getChatRoom(roomJID); + MultiUserChat muc = chatRoom.getMultiUserChat(); + if (!muc.isJoined()) { + joinRoom(muc, nickname, password); + } + chatManager.getChatContainer().activateChatRoom(chatRoom); + return chatRoom; + } + catch (ChatRoomNotFoundException e) { + } + + final GroupChatRoom room = new GroupChatRoom(groupChat); + room.setTabTitle(roomName); + + + if (requiresPassword(roomJID) && password == null) { + password = JOptionPane.showInputDialog(null, "Enter Room Password", "Need Password", JOptionPane.QUESTION_MESSAGE); + if (!ModelUtil.hasLength(password)) { + return null; + } + } + + + final List errors = new ArrayList(); + final String userPassword = password; + + + if (!groupChat.isJoined()) { + int groupChatCounter = 0; + while (true) { + groupChatCounter++; + String joinName = nickname; + if (groupChatCounter > 1) { + joinName = joinName + groupChatCounter; + } + if (groupChatCounter < 10) { + try { + if (ModelUtil.hasLength(userPassword)) { + groupChat.join(joinName, userPassword); + } + else { + groupChat.join(joinName); + } + break; + } + catch (XMPPException ex) { + int code = 0; + if (ex.getXMPPError() != null) { + code = ex.getXMPPError().getCode(); + } + + if (code == 0) { + errors.add("No response from server."); + } + else if (code == 401) { + errors.add("The password did not match the room's password."); + } + else if (code == 403) { + errors.add("You have been banned from this room."); + } + else if (code == 404) { + errors.add("The room you are trying to enter does not exist."); + } + else if (code == 407) { + errors.add("You are not a member of this room.\nThis room requires you to be a member to join."); + } + else if (code != 409) { + break; + } + } + } + else { + break; + } + } + + } + + + if (errors.size() > 0) { + String error = (String)errors.get(0); + JOptionPane.showMessageDialog(SparkManager.getMainWindow(), error, "Unable to join the room at this time.", JOptionPane.ERROR_MESSAGE); + return null; + } + else if (groupChat.isJoined()) { + chatManager.getChatContainer().addChatRoom(room); + chatManager.getChatContainer().activateChatRoom(room); + } + else { + JOptionPane.showMessageDialog(SparkManager.getMainWindow(), "Unable to join the room.", "Error", JOptionPane.ERROR_MESSAGE); + return null; + } + return room; + } +} + diff --git a/src/java/org/jivesoftware/spark/ui/conferences/Conferences.java b/src/java/org/jivesoftware/spark/ui/conferences/Conferences.java new file mode 100644 index 00000000..4c0f5022 --- /dev/null +++ b/src/java/org/jivesoftware/spark/ui/conferences/Conferences.java @@ -0,0 +1,376 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.ui.conferences; + +import org.jivesoftware.resource.SparkRes; +import org.jivesoftware.smack.XMPPConnection; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.packet.Message; +import org.jivesoftware.smack.util.StringUtils; +import org.jivesoftware.smackx.PrivateDataManager; +import org.jivesoftware.smackx.ServiceDiscoveryManager; +import org.jivesoftware.smackx.muc.InvitationListener; +import org.jivesoftware.smackx.muc.MultiUserChat; +import org.jivesoftware.spark.ChatManager; +import org.jivesoftware.spark.SparkManager; +import org.jivesoftware.spark.Workspace; +import org.jivesoftware.spark.component.JiveTreeNode; +import org.jivesoftware.spark.component.RolloverButton; +import org.jivesoftware.spark.component.Tree; +import org.jivesoftware.spark.plugin.ContextMenuListener; +import org.jivesoftware.spark.ui.ChatRoom; +import org.jivesoftware.spark.ui.ChatRoomButton; +import org.jivesoftware.spark.ui.ChatRoomListener; +import org.jivesoftware.spark.ui.ContactGroup; +import org.jivesoftware.spark.ui.ContactItem; +import org.jivesoftware.spark.ui.ContactList; +import org.jivesoftware.spark.ui.rooms.ChatRoomImpl; +import org.jivesoftware.spark.ui.status.StatusBar; +import org.jivesoftware.spark.util.ModelUtil; +import org.jivesoftware.spark.util.SwingWorker; +import org.jivesoftware.spark.util.log.Log; + +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.JMenu; +import javax.swing.JPanel; +import javax.swing.JPopupMenu; +import javax.swing.SwingUtilities; +import javax.swing.tree.DefaultTreeModel; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.MouseEvent; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; + +/** + * Conference plugin is reponsible for the initial loading of MultiUser Chat support. To disable plugin, + * you can remove from the plugins.xml file located in the classpath of Communicator. + */ +public class Conferences { + private static BookmarkedConferences bookedMarkedConferences; + + private boolean mucSupported; + + public void initialize() { + ServiceDiscoveryManager manager = ServiceDiscoveryManager.getInstanceFor(SparkManager.getConnection()); + mucSupported = manager.includesFeature("http://jabber.org/protocol/muc"); + + if (mucSupported) { + // Load the conference data from Private Data + loadBookmarks(); + + // Add an invitation listener. + addInvitationListener(); + + addChatRoomListener(); + + addPopupListeners(); + + // Add Join Conference Button to StatusBar + // Get command panel and add View Online/Offline, Add Contact + StatusBar statusBar = SparkManager.getWorkspace().getStatusBar(); + JPanel commandPanel = statusBar.getCommandPanel(); + + RolloverButton joinConference = new RolloverButton(SparkRes.getImageIcon(SparkRes.CONFERENCE_IMAGE_16x16)); + joinConference.setToolTipText("Join a conference"); + commandPanel.add(joinConference); + joinConference.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + ConferenceRooms rooms = new ConferenceRooms(bookedMarkedConferences.getTree(), getDefaultServiceName()); + rooms.invoke(); + } + }); + } + } + + /** + * Adds an invitation listener to check for any MUC invites. + */ + private static void addInvitationListener() { + // Add Invite Listener + MultiUserChat.addInvitationListener(SparkManager.getConnection(), new InvitationListener() { + public void invitationReceived(final XMPPConnection conn, final String room, final String inviter, final String reason, final String password, final Message message) { + SwingUtilities.invokeLater(new Runnable() { + public void run() { + Collection listeners = new ArrayList(SparkManager.getChatManager().getInvitationListeners()); + Iterator iter = listeners.iterator(); + while (iter.hasNext()) { + RoomInvitationListener listener = (RoomInvitationListener)iter.next(); + boolean handle = listener.handleInvitation(conn, room, inviter, reason, password, message); + if (handle) { + return; + } + } + + // If no listeners handled the invitation, default to generic invite. + final InvitationDialog inviteDialog = new InvitationDialog(); + inviteDialog.invitationReceived(conn, room, inviter, reason, password, message); + } + }); + + } + }); + } + + /** + * Persists bookmarked data, if any. + */ + public void shutdown() { + if (!mucSupported) { + return; + } + + ConferenceData conferenceData = new ConferenceData(); + + Tree tree = bookedMarkedConferences.getTree(); + DefaultTreeModel model = (DefaultTreeModel)tree.getModel(); + JiveTreeNode rootNode = (JiveTreeNode)model.getRoot(); + + int children = rootNode.getChildCount(); + for (int i = 0; i < children; i++) { + JiveTreeNode serviceNode = (JiveTreeNode)rootNode.getChildAt(i); + int bookmarks = serviceNode.getChildCount(); + for (int j = 0; j < bookmarks; j++) { + JiveTreeNode node = (JiveTreeNode)serviceNode.getChildAt(j); + Bookmark bookmark = new Bookmark(); + bookmark.setRoomJID((String)node.getAssociatedObject()); + bookmark.setRoomName(node.getUserObject().toString()); + bookmark.setServiceName(serviceNode.getUserObject().toString()); + conferenceData.addBookmark(bookmark); + } + } + + PrivateDataManager pdm = SparkManager.getSessionManager().getPersonalDataManager(); + try { + pdm.setPrivateData(conferenceData); + } + catch (XMPPException e) { + Log.error("Unable to save Bookmarks in Private Data.", e); + } + } + + /** + * Load all bookmarked data. + */ + private void loadBookmarks() { + final Workspace workspace = SparkManager.getWorkspace(); + + + SwingWorker lazyWorker = new SwingWorker() { + ConferenceData conferenceData; + + public Object construct() { + PrivateDataManager.addPrivateDataProvider(ConferenceData.ELEMENT, ConferenceData.NAMESPACE, new ConferenceData.ConferencePrivateDataProvider()); + + PrivateDataManager pdm = SparkManager.getSessionManager().getPersonalDataManager(); + try { + conferenceData = (ConferenceData)pdm.getPrivateData(ConferenceData.ELEMENT, ConferenceData.NAMESPACE); + } + catch (XMPPException e) { + Log.error("Unable to load private data for bookmarks.", e); + } + return conferenceData; + } + + public void finished() { + bookedMarkedConferences = new BookmarkedConferences(); + + workspace.getWorkspacePane().addTab("Conferences", SparkRes.getImageIcon(SparkRes.CONFERENCE_IMAGE_16x16), bookedMarkedConferences); + + if (conferenceData != null) { + // Add ConferenceDataProvider + + Collection bookmakrs = conferenceData.getBookmarks(); + bookedMarkedConferences.setBookmarks(bookmakrs); + } + } + }; + + lazyWorker.start(); + } + + private void addChatRoomListener() { + ChatManager chatManager = SparkManager.getChatManager(); + chatManager.addChatRoomListener(new ChatRoomListener() { + public void chatRoomOpened(ChatRoom room) { + if (room instanceof ChatRoomImpl) { + final ChatRoomImpl chatRoom = (ChatRoomImpl)room; + + // Add Conference Invite Button. + ChatRoomButton inviteButton = new ChatRoomButton("", SparkRes.getImageIcon(SparkRes.CONFERENCE_IMAGE_24x24)); + inviteButton.setToolTipText("Invite this user to a conference"); + + room.getToolBar().addChatRoomButton(inviteButton); + + inviteButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + String userName = StringUtils.parseName(SparkManager.getSessionManager().getJID()); + final String roomName = userName + "_" + StringUtils.randomString(3); + + + final List jids = new ArrayList(); + jids.add(chatRoom.getParticipantJID()); + + final String serviceName = getDefaultServiceName(); + if (serviceName != null) { + SwingWorker worker = new SwingWorker() { + public Object construct() { + try { + Thread.sleep(25); + } + catch (InterruptedException e1) { + Log.error(e1); + } + return "ok"; + } + + public void finished() { + ConferenceUtils.createPrivateConference(serviceName, "Please join me in a conference.", roomName, jids); + } + }; + worker.start(); + + } + } + }); + } + } + + public void chatRoomLeft(ChatRoom room) { + + } + + public void chatRoomClosed(ChatRoom room) { + + } + + public void chatRoomActivated(ChatRoom room) { + + } + + public void userHasJoined(ChatRoom room, String userid) { + + } + + public void userHasLeft(ChatRoom room, String userid) { + + } + }); + } + + + public boolean canShutDown() { + return true; + } + + public static String getDefaultServiceName() { + String serviceName = null; + Collection services = bookedMarkedConferences.getMucServices(); + if (services != null) { + Iterator serviceIterator = services.iterator(); + while (serviceIterator.hasNext()) { + serviceName = (String)serviceIterator.next(); + break; + } + } + return serviceName; + } + + private void addPopupListeners() { + final ContactList contactList = SparkManager.getWorkspace().getContactList(); + + // Add ContactList items. + final Action inviteAllAction = new AbstractAction() { + public void actionPerformed(ActionEvent actionEvent) { + Collection contacts = contactList.getActiveGroup().getContactItems(); + startConference(contacts); + + } + }; + + inviteAllAction.putValue(Action.NAME, "Invite group to conference"); + inviteAllAction.putValue(Action.SMALL_ICON, SparkRes.getImageIcon(SparkRes.CONFERENCE_IMAGE_16x16)); + + + final Action conferenceAction = new AbstractAction() { + public void actionPerformed(ActionEvent actionEvent) { + Collection contacts = contactList.getSelectedUsers(); + startConference(contacts); + } + }; + + conferenceAction.putValue(Action.NAME, "Start a Conference..."); + conferenceAction.putValue(Action.SMALL_ICON, SparkRes.getImageIcon(SparkRes.SMALL_WORKGROUP_QUEUE_IMAGE)); + + + contactList.addContextMenuListener(new ContextMenuListener() { + public void poppingUp(Object component, JPopupMenu popup) { + Collection col = contactList.getSelectedUsers(); + if (component instanceof ContactGroup) { + popup.add(inviteAllAction); + } + else if (component instanceof Collection && col.size() > 0) { + popup.add(conferenceAction); + } + } + + public void poppingDown(JPopupMenu popup) { + + } + + public boolean handleDefaultAction(MouseEvent e) { + return false; + } + }); + + // Add to Actions Menu + final JMenu actionsMenu = SparkManager.getMainWindow().getMenuByName("Actions"); + actionsMenu.add(conferenceAction); + } + + private void startConference(Collection items) { + final ContactList contactList = SparkManager.getWorkspace().getContactList(); + List jids = new ArrayList(); + Iterator contacts = items.iterator(); + while (contacts.hasNext()) { + ContactItem item = (ContactItem)contacts.next(); + + ContactGroup contactGroup = contactList.getContactGroup(item.getGroupName()); + contactGroup.clearSelection(); + + if (item.isAvailable()) { + jids.add(item.getFullJID()); + } + } + + String userName = StringUtils.parseName(SparkManager.getSessionManager().getJID()); + final String roomName = userName + "_" + StringUtils.randomString(3); + + String serviceName = getDefaultServiceName(); + if (ModelUtil.hasLength(serviceName)) { + ConferenceUtils.inviteUsersToRoom(serviceName, roomName, jids); + } + } + + /** + * Returns the UI for the addition and removal of Conference bookmarks. + * + * @return the BookedMarkedConferences UI. + */ + public static BookmarkedConferences getBookmarkedConferences() { + return bookedMarkedConferences; + } + +} diff --git a/src/java/org/jivesoftware/spark/ui/conferences/DataFormDialog.java b/src/java/org/jivesoftware/spark/ui/conferences/DataFormDialog.java new file mode 100644 index 00000000..20d21c5f --- /dev/null +++ b/src/java/org/jivesoftware/spark/ui/conferences/DataFormDialog.java @@ -0,0 +1,276 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.ui.conferences; + +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smackx.Form; +import org.jivesoftware.smackx.FormField; +import org.jivesoftware.smackx.muc.MultiUserChat; +import org.jivesoftware.spark.component.CheckBoxList; +import org.jivesoftware.spark.util.GraphicUtils; +import org.jivesoftware.spark.util.ModelUtil; +import org.jivesoftware.spark.util.log.Log; + +import javax.swing.BorderFactory; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JComboBox; +import javax.swing.JComponent; +import javax.swing.JDialog; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JPasswordField; +import javax.swing.JScrollPane; +import javax.swing.JTextArea; +import javax.swing.JTextField; + +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.FlowLayout; +import java.awt.Font; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.StringTokenizer; + +public class DataFormDialog extends JPanel { + private final Map valueMap = new HashMap(); + private int row = 0; + private Form submitForm; + JDialog dialog = null; + + + public DataFormDialog(JFrame parent, final MultiUserChat chat, final Form submitForm) { + dialog = new JDialog(parent, true); + dialog.setTitle("Configure Chat Room"); + + this.setLayout(new GridBagLayout()); + Form form = null; + // Create the room + try { + form = chat.getConfigurationForm(); + } + catch (XMPPException e) { + Log.error(e); + } + + // Create a new form to submit based on the original form + this.submitForm = submitForm; + Iterator fields = form.getFields(); + + // Add default answers to the form to submit + while (fields.hasNext()) { + FormField field = (FormField)fields.next(); + submitForm.addField(field); + String variable = field.getVariable(); + String description = field.getDescription(); + String label = field.getLabel(); + String type = field.getType(); + + Iterator iter = field.getValues(); + List valueList = new ArrayList(); + while (iter.hasNext()) { + valueList.add(iter.next()); + } + + if (type.equals(FormField.TYPE_BOOLEAN)) { + String o = (String)valueList.get(0); + boolean isSelected = o.equals("1"); + JCheckBox box = new JCheckBox(label); + box.setSelected(isSelected); + addField(label, box, variable); + } + else if (type.equals(FormField.TYPE_TEXT_SINGLE) || + type.equals(FormField.TYPE_JID_SINGLE)) { + String value = (String)valueList.get(0); + addField(label, new JTextField(value), variable); + } + else if (type.equals(FormField.TYPE_TEXT_MULTI) || + type.equals(FormField.TYPE_JID_MULTI)) { + StringBuffer buf = new StringBuffer(); + iter = field.getValues(); + + while (iter.hasNext()) { + buf.append((String)iter.next()); + + if (iter.hasNext()) { + buf.append(","); + } + } + addField(label, new JTextArea(buf.toString()), variable); + } + else if (type.equals(FormField.TYPE_TEXT_PRIVATE)) { + addField(label, new JPasswordField(), variable); + } + else if (type.equals(FormField.TYPE_LIST_SINGLE)) { + JComboBox box = new JComboBox(); + iter = field.getOptions(); + while (iter.hasNext()) { + FormField.Option option = (FormField.Option)iter.next(); + String lab = option.getLabel(); + String value = option.getValue(); + box.addItem(value); + } + if (valueList.size() > 0) { + String defaultValue = (String)valueList.get(0); + box.setSelectedItem(defaultValue); + } + + addField(label, box, variable); + } + else if (type.equals(FormField.TYPE_LIST_MULTI)) { + CheckBoxList checkBoxList = new CheckBoxList(); + Iterator i = field.getValues(); + while (i.hasNext()) { + String value = (String)i.next(); + checkBoxList.addCheckBox(new JCheckBox(value), value); + } + addField(label, checkBoxList, variable); + } + } + + + JButton button = new JButton("Update"); + button.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + dialog.dispose(); + // Now submit all information + updateRoomConfiguration(submitForm, chat); + } + }); + + final JScrollPane pane = new JScrollPane(this); + + dialog.getContentPane().setLayout(new BorderLayout()); + + dialog.getContentPane().add(pane, BorderLayout.CENTER); + + JPanel bottomPanel = new JPanel(); + bottomPanel.setLayout(new FlowLayout(FlowLayout.RIGHT)); + bottomPanel.add(button); + + JButton cancelButton = new JButton("Cancel"); + cancelButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent actionEvent) { + dialog.dispose(); + } + }); + + bottomPanel.add(cancelButton); + + dialog.getContentPane().add(bottomPanel, BorderLayout.SOUTH); + + dialog.pack(); + dialog.setSize(600, 400); + GraphicUtils.centerWindowOnScreen(dialog); + dialog.show(); + + + } + + private void updateRoomConfiguration(Form submitForm, MultiUserChat chat) { + Iterator valueIter = valueMap.keySet().iterator(); + while (valueIter.hasNext()) { + String answer = (String)valueIter.next(); + Object o = valueMap.get(answer); + if (o instanceof JCheckBox) { + boolean isSelected = ((JCheckBox)o).isSelected(); + submitForm.setAnswer(answer, isSelected); + } + else if (o instanceof JTextArea) { + List list = new ArrayList(); + String value = ((JTextArea)o).getText(); + StringTokenizer tokenizer = new StringTokenizer(value, ", ", false); + while (tokenizer.hasMoreTokens()) { + list.add(tokenizer.nextToken()); + } + if (list.size() > 0) { + submitForm.setAnswer(answer, list); + } + } + else if (o instanceof JTextField) { + String value = ((JTextField)o).getText(); + if (ModelUtil.hasLength(value)) { + submitForm.setAnswer(answer, value); + } + } + else if (o instanceof JComboBox) { + String value = (String)((JComboBox)o).getSelectedItem(); + List list = new ArrayList(); + list.add(value); + if (list.size() > 0) { + submitForm.setAnswer(answer, list); + } + } + else if (o instanceof CheckBoxList) { + List list = (List)((CheckBoxList)o).getSelectedValues(); + if (list.size() > 0) { + submitForm.setAnswer(answer, list); + } + } + } + + + try { + chat.sendConfigurationForm(submitForm); + } + catch (XMPPException e) { + Log.error(e); + } + } + + private void addField(String label, JComponent comp, String variable) { + + if (!(comp instanceof JCheckBox)) { + JLabel formLabel = new JLabel(label); + formLabel.setFont(new Font("Verdana", Font.BOLD, 10)); + this.add(formLabel, new GridBagConstraints(0, row, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); + } + if (comp instanceof JTextArea) { + JScrollPane pane = new JScrollPane(comp); + pane.setBorder(BorderFactory.createTitledBorder("Comma Delimited")); + this.add(pane, new GridBagConstraints(1, row, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 75, 50)); + } + else if (comp instanceof JCheckBox) { + this.add(comp, new GridBagConstraints(0, row, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); + } + else if (comp instanceof CheckBoxList) { + this.add(comp, new GridBagConstraints(1, row, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 75, 50)); + } + else { + this.add(comp, new GridBagConstraints(1, row, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 75, 0)); + + } + valueMap.put(variable, comp); + row++; + } + + + public String getValue(String label) { + Component comp = (Component)valueMap.get(label); + if (comp instanceof JCheckBox) { + return "" + ((JCheckBox)comp).isSelected(); + } + + if (comp instanceof JTextField) { + return ((JTextField)comp).getText(); + } + return null; + } +} diff --git a/src/java/org/jivesoftware/spark/ui/conferences/InvitationDialog.java b/src/java/org/jivesoftware/spark/ui/conferences/InvitationDialog.java new file mode 100644 index 00000000..c8041471 --- /dev/null +++ b/src/java/org/jivesoftware/spark/ui/conferences/InvitationDialog.java @@ -0,0 +1,136 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.ui.conferences; + +import org.jivesoftware.resource.SparkRes; +import org.jivesoftware.smack.XMPPConnection; +import org.jivesoftware.smack.packet.Message; +import org.jivesoftware.smackx.muc.MultiUserChat; +import org.jivesoftware.spark.SparkManager; +import org.jivesoftware.spark.component.RolloverButton; +import org.jivesoftware.spark.component.TitlePanel; +import org.jivesoftware.spark.util.GraphicUtils; +import org.jivesoftware.spark.util.ResourceUtils; + +import javax.swing.JEditorPane; +import javax.swing.JFrame; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.text.html.HTMLEditorKit; + +import java.awt.BorderLayout; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; + +public class InvitationDialog extends JPanel { + private TitlePanel titlePanel; + private JEditorPane messageLabel; + private RolloverButton acceptButton; + private RolloverButton declineButton; + + private String password; + private String roomName; + private String inviter; + + private JFrame frame; + + public InvitationDialog() { + setLayout(new GridBagLayout()); + + titlePanel = new TitlePanel("Conference Invitation", null, SparkRes.getImageIcon(SparkRes.CONFERENCE_IMAGE_24x24), true); + add(titlePanel, new GridBagConstraints(0, 0, 2, 1, 1.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0)); + + + messageLabel = new JEditorPane(); + acceptButton = new RolloverButton(); + declineButton = new RolloverButton(); + + ResourceUtils.resButton(acceptButton, "&Accept"); + ResourceUtils.resButton(declineButton, "&Decline"); + + add(new JScrollPane(messageLabel), new GridBagConstraints(0, 1, 2, 1, 1.0, 1.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(5, 5, 5, 5), 0, 0)); + add(acceptButton, new GridBagConstraints(0, 2, 1, 1, 1.0, 0.0, GridBagConstraints.EAST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + add(declineButton, new GridBagConstraints(1, 2, 1, 1, 0.0, 0.0, GridBagConstraints.EAST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + + acceptButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent actionEvent) { + acceptInvite(); + } + }); + + declineButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent actionEvent) { + declineInvite(); + } + }); + } + + public void invitationReceived(XMPPConnection conn, final String room, final String inviter, String reason, final String password, Message message) { + this.password = password; + this.roomName = room; + this.inviter = inviter; + + frame = new JFrame("Conference Invite"); + titlePanel.setDescription("Invitation from " + inviter); + frame.setIconImage(SparkManager.getMainWindow().getIconImage()); + + frame.getContentPane().setLayout(new BorderLayout()); + frame.getContentPane().add(this); + + messageLabel.setEditorKit(new HTMLEditorKit()); + + StringBuffer buf = new StringBuffer(); + buf.append(""); + buf.append("Room Name
        ").append(room); + buf.append("

        "); + + if (reason == null) { + reason = "Please join me in this conference room."; + } + + buf.append("Message
        ").append(reason); + + messageLabel.setText(buf.toString()); + frame.setFocusableWindowState(false); + frame.pack(); + frame.setSize(400, 400); + + GraphicUtils.centerWindowOnScreen(frame); + + frame.addWindowListener(new WindowAdapter() { + public void windowActivated(WindowEvent windowEvent) { + SparkManager.getAlertManager().stopFlashing(frame); + } + }); + + // Blink frame if necessary + SparkManager.getChatManager().getChatContainer().blinkFrameIfNecessary(frame); + } + + + private void acceptInvite() { + frame.dispose(); + ConferenceUtils.autoJoinConferenceRoom(roomName, roomName, password); + } + + private void declineInvite() { + frame.dispose(); + + MultiUserChat.decline(SparkManager.getConnection(), roomName, inviter, "No thank you"); + } + + +} diff --git a/src/java/org/jivesoftware/spark/ui/conferences/JoinConferenceRoomDialog.java b/src/java/org/jivesoftware/spark/ui/conferences/JoinConferenceRoomDialog.java new file mode 100644 index 00000000..24a4f11a --- /dev/null +++ b/src/java/org/jivesoftware/spark/ui/conferences/JoinConferenceRoomDialog.java @@ -0,0 +1,140 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.ui.conferences; + +import org.jivesoftware.resource.SparkRes; +import org.jivesoftware.spark.SparkManager; +import org.jivesoftware.spark.component.TitlePanel; +import org.jivesoftware.spark.util.ResourceUtils; +import org.jivesoftware.spark.util.SwingWorker; +import org.jivesoftware.sparkimpl.preference.chat.ChatPreference; +import org.jivesoftware.sparkimpl.preference.chat.ChatPreferences; + +import javax.swing.JDialog; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JPasswordField; +import javax.swing.JTextField; + +import java.awt.BorderLayout; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; + +final class JoinConferenceRoomDialog extends JPanel { + private JLabel roomNameLabel = new JLabel(); + private JLabel nicknameLabel = new JLabel(); + private JLabel passwordLabel = new JLabel(); + private JPasswordField passwordField = new JPasswordField(); + private JTextField nicknameField = new JTextField(); + private GridBagLayout gridBagLayout1 = new GridBagLayout(); + private JLabel roomNameDescription = new JLabel(); + + public JoinConferenceRoomDialog() { + setLayout(gridBagLayout1); + add(nicknameField, new GridBagConstraints(1, 1, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); + add(passwordField, new GridBagConstraints(1, 2, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); + add(passwordLabel, new GridBagConstraints(0, 2, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + add(nicknameLabel, new GridBagConstraints(0, 1, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + add(roomNameLabel, new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + add(roomNameDescription, new GridBagConstraints(1, 0, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); + add(new JLabel(), new GridBagConstraints(0, 3, 2, 1, 0.0, 1.0, GridBagConstraints.WEST, GridBagConstraints.VERTICAL, new Insets(5, 5, 5, 5), 0, 0)); + + // Add Resource Utils + ResourceUtils.resLabel(nicknameLabel, nicknameField, "&Nickname:"); + ResourceUtils.resLabel(passwordLabel, passwordField, "&Password:"); + + roomNameLabel.setText("Room Name:"); + } + + public void joinRoom(final String roomJID, final String roomName) { + final ChatPreferences pref = (ChatPreferences)SparkManager.getPreferenceManager().getPreferenceData(ChatPreference.NAMESPACE); + + // Set default nickname + nicknameField.setText(pref.getNickname()); + + // Enable password field if a password is required + passwordField.setVisible(false); + passwordLabel.setVisible(false); + + + roomNameDescription.setText(roomName); + + final JOptionPane pane; + + + TitlePanel titlePanel; + + // Create the title panel for this dialog + titlePanel = new TitlePanel("Join Conference Room", "Specify information for conference room.", SparkRes.getImageIcon(SparkRes.BLANK_IMAGE), true); + + // Construct main panel w/ layout. + final JPanel mainPanel = new JPanel(); + mainPanel.setLayout(new BorderLayout()); + mainPanel.add(titlePanel, BorderLayout.NORTH); + + // The user should only be able to close this dialog. + Object[] options = {"Join", "Cancel"}; + pane = new JOptionPane(this, JOptionPane.PLAIN_MESSAGE, JOptionPane.OK_CANCEL_OPTION, null, options, options[0]); + + mainPanel.add(pane, BorderLayout.CENTER); + + final JOptionPane p = new JOptionPane(); + + final JDialog dlg = p.createDialog(SparkManager.getMainWindow(), "Conference Rooms"); + dlg.setModal(false); + + dlg.pack(); + dlg.setSize(350, 250); + dlg.setResizable(true); + dlg.setContentPane(mainPanel); + dlg.setLocationRelativeTo(SparkManager.getMainWindow()); + + PropertyChangeListener changeListener = new PropertyChangeListener() { + public void propertyChange(PropertyChangeEvent e) { + String value = (String)pane.getValue(); + if ("Cancel".equals(value)) { + pane.setValue(JOptionPane.UNINITIALIZED_VALUE); + dlg.dispose(); + } + else if ("Join".equals(value)) { + pane.setValue(JOptionPane.UNINITIALIZED_VALUE); + dlg.dispose(); + ConferenceUtils.autoJoinConferenceRoom(roomName, roomJID, null); + } + } + }; + + pane.addPropertyChangeListener(changeListener); + + dlg.setVisible(true); + dlg.toFront(); + dlg.requestFocus(); + + SwingWorker worker = new SwingWorker() { + boolean requiresPassword; + + public Object construct() { + requiresPassword = ConferenceUtils.requiresPassword(roomJID); + return new Boolean(requiresPassword); + } + + public void finished() { + passwordField.setVisible(requiresPassword); + passwordLabel.setVisible(requiresPassword); + } + }; + worker.start(); + } +} diff --git a/src/java/org/jivesoftware/spark/ui/conferences/RoomBrowser.java b/src/java/org/jivesoftware/spark/ui/conferences/RoomBrowser.java new file mode 100644 index 00000000..544d6e31 --- /dev/null +++ b/src/java/org/jivesoftware/spark/ui/conferences/RoomBrowser.java @@ -0,0 +1,187 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.ui.conferences; + +import org.jivesoftware.resource.SparkRes; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smackx.ServiceDiscoveryManager; +import org.jivesoftware.smackx.muc.MultiUserChat; +import org.jivesoftware.smackx.muc.RoomInfo; +import org.jivesoftware.smackx.packet.DiscoverItems; +import org.jivesoftware.spark.SparkManager; +import org.jivesoftware.spark.component.JiveTreeCellRenderer; +import org.jivesoftware.spark.component.JiveTreeNode; +import org.jivesoftware.spark.component.TitlePanel; +import org.jivesoftware.spark.component.Tree; +import org.jivesoftware.spark.util.SwingWorker; +import org.jivesoftware.spark.util.log.Log; + +import javax.swing.JDialog; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.Iterator; + +public class RoomBrowser extends JPanel { + private JLabel descriptionLabel = new JLabel(); + private JLabel subjectLabel = new JLabel(); + private JLabel occupantsLabel = new JLabel(); + private JLabel roomNameLabel = new JLabel(); + + private JLabel descriptionValue = new JLabel(); + private JLabel subjectValue = new JLabel(); + private JLabel occupantsValue = new JLabel(); + private JLabel roomNameValue = new JLabel(); + + private JiveTreeNode rootNode; + private Tree tree; + + public RoomBrowser() { + descriptionLabel.setText("Description:"); + subjectLabel.setText("Subject:"); + occupantsLabel.setText("Occupants:"); + roomNameLabel.setText("Room Name:"); + + // Add labels to UI + setLayout(new GridBagLayout()); + add(descriptionLabel, new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + add(descriptionValue, new GridBagConstraints(1, 0, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); + + add(subjectLabel, new GridBagConstraints(0, 1, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + add(subjectValue, new GridBagConstraints(1, 1, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); + + add(occupantsLabel, new GridBagConstraints(0, 2, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + add(occupantsValue, new GridBagConstraints(1, 2, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); + + add(roomNameLabel, new GridBagConstraints(0, 3, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + add(roomNameValue, new GridBagConstraints(1, 3, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); + + + rootNode = new JiveTreeNode("Users In Room", true); + tree = new Tree(rootNode); + + add(tree, new GridBagConstraints(0, 4, 2, 1, 1.0, 1.0, GridBagConstraints.WEST, GridBagConstraints.BOTH, new Insets(5, 5, 5, 5), 0, 0)); + setBackground(Color.white); + tree.setCellRenderer(new JiveTreeCellRenderer()); + + + } + + public void displayRoomInformation(final String roomJID) { + SwingWorker worker = new SwingWorker() { + RoomInfo roomInfo = null; + DiscoverItems items = null; + + public Object construct() { + try { + roomInfo = MultiUserChat.getRoomInfo(SparkManager.getConnection(), roomJID); + + + ServiceDiscoveryManager manager = ServiceDiscoveryManager.getInstanceFor(SparkManager.getConnection()); + items = manager.discoverItems(roomJID); + } + catch (XMPPException e) { + Log.error(e); + } + return "ok"; + } + + public void finished() { + setupRoomInformationUI(roomJID, roomInfo, items); + } + }; + + worker.start(); + } + + private void setupRoomInformationUI(String roomJID, final RoomInfo roomInfo, final DiscoverItems items) { + descriptionValue.setText("No description available"); + subjectValue.setText("No subject available"); + occupantsValue.setText("n/a"); + roomNameValue.setText("n/a"); + try { + descriptionValue.setText(roomInfo.getDescription()); + subjectValue.setText(roomInfo.getSubject()); + + if (roomInfo.getOccupantsCount() == -1) { + occupantsValue.setText("n/a"); + } + else { + occupantsValue.setText(Integer.toString(roomInfo.getOccupantsCount())); + } + roomNameValue.setText(roomInfo.getRoom()); + + Iterator iter = items.getItems(); + while (iter.hasNext()) { + DiscoverItems.Item item = (DiscoverItems.Item)iter.next(); + String jid = item.getEntityID(); + rootNode.add(new JiveTreeNode(jid, false, SparkRes.getImageIcon(SparkRes.SMALL_USER1_INFORMATION))); + } + tree.expandRow(0); + } + catch (Exception e) { + Log.error(e); + } + + final JOptionPane pane; + + // Create the title panel for this dialog + TitlePanel titlePanel = new TitlePanel("View Room Information", "Room information for " + roomJID, SparkRes.getImageIcon(SparkRes.BLANK_IMAGE), true); + + // Construct main panel w/ layout. + final JPanel mainPanel = new JPanel(); + mainPanel.setLayout(new BorderLayout()); + mainPanel.add(titlePanel, BorderLayout.NORTH); + + // The user should only be able to close this dialog. + Object[] options = {"Close"}; + pane = new JOptionPane(this, JOptionPane.PLAIN_MESSAGE, JOptionPane.OK_CANCEL_OPTION, null, options, options[0]); + + mainPanel.add(pane, BorderLayout.CENTER); + + final JOptionPane p = new JOptionPane(); + + final JDialog dlg = p.createDialog(SparkManager.getMainWindow(), "Room Information"); + dlg.setModal(false); + + dlg.pack(); + dlg.setSize(450, 400); + dlg.setResizable(true); + dlg.setContentPane(mainPanel); + dlg.setLocationRelativeTo(SparkManager.getMainWindow()); + + PropertyChangeListener changeListener = new PropertyChangeListener() { + public void propertyChange(PropertyChangeEvent e) { + String value = (String)pane.getValue(); + if ("Close".equals(value)) { + pane.setValue(JOptionPane.UNINITIALIZED_VALUE); + dlg.dispose(); + } + } + }; + + pane.addPropertyChangeListener(changeListener); + + dlg.setVisible(true); + dlg.toFront(); + dlg.requestFocus(); + } + + +} diff --git a/src/java/org/jivesoftware/spark/ui/conferences/RoomInvitationListener.java b/src/java/org/jivesoftware/spark/ui/conferences/RoomInvitationListener.java new file mode 100644 index 00000000..0b201f5c --- /dev/null +++ b/src/java/org/jivesoftware/spark/ui/conferences/RoomInvitationListener.java @@ -0,0 +1,34 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.ui.conferences; + +import org.jivesoftware.smack.XMPPConnection; +import org.jivesoftware.smack.packet.Message; + + +/** + * Users of this interface may want to intercept MUC Invitations for their own uses. + */ +public interface RoomInvitationListener { + + /** + * Return true if you wish to handle this invitation. + * + * @param conn + * @param room + * @param inviter + * @param reason + * @param password + * @param message + * @return true if handled. + */ + boolean handleInvitation(final XMPPConnection conn, final String room, final String inviter, final String reason, final String password, final Message message); +} diff --git a/src/java/org/jivesoftware/spark/ui/conferences/package.html b/src/java/org/jivesoftware/spark/ui/conferences/package.html new file mode 100644 index 00000000..35247b0a --- /dev/null +++ b/src/java/org/jivesoftware/spark/ui/conferences/package.html @@ -0,0 +1 @@ +Contains Conference Room specific components, such as room browsers and helpful conference utilities. \ No newline at end of file diff --git a/src/java/org/jivesoftware/spark/ui/package.html b/src/java/org/jivesoftware/spark/ui/package.html new file mode 100644 index 00000000..fc588052 --- /dev/null +++ b/src/java/org/jivesoftware/spark/ui/package.html @@ -0,0 +1 @@ +Provides support by providing all chat specific components, such as ChatRoom and ContactList. diff --git a/src/java/org/jivesoftware/spark/ui/rooms/ChatRoomImpl.java b/src/java/org/jivesoftware/spark/ui/rooms/ChatRoomImpl.java new file mode 100644 index 00000000..b10bc82d --- /dev/null +++ b/src/java/org/jivesoftware/spark/ui/rooms/ChatRoomImpl.java @@ -0,0 +1,563 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.ui.rooms; + +import org.jivesoftware.resource.SparkRes; +import org.jivesoftware.smack.PacketListener; +import org.jivesoftware.smack.Roster; +import org.jivesoftware.smack.RosterEntry; +import org.jivesoftware.smack.filter.AndFilter; +import org.jivesoftware.smack.filter.FromContainsFilter; +import org.jivesoftware.smack.filter.PacketFilter; +import org.jivesoftware.smack.filter.PacketTypeFilter; +import org.jivesoftware.smack.packet.Message; +import org.jivesoftware.smack.packet.Packet; +import org.jivesoftware.smack.packet.Presence; +import org.jivesoftware.smack.util.StringUtils; +import org.jivesoftware.smackx.MessageEventManager; +import org.jivesoftware.smackx.MessageEventNotificationListener; +import org.jivesoftware.smackx.MessageEventRequestListener; +import org.jivesoftware.spark.SparkManager; +import org.jivesoftware.spark.ui.ChatRoom; +import org.jivesoftware.spark.ui.ChatRoomButton; +import org.jivesoftware.spark.ui.ContactItem; +import org.jivesoftware.spark.ui.ContactList; +import org.jivesoftware.spark.ui.MessageEventListener; +import org.jivesoftware.spark.ui.RosterDialog; +import org.jivesoftware.spark.ui.VCardPanel; +import org.jivesoftware.spark.ui.status.StatusItem; +import org.jivesoftware.spark.util.ModelUtil; +import org.jivesoftware.spark.util.log.Log; +import org.jivesoftware.sparkimpl.profile.VCardManager; + +import javax.swing.Icon; +import javax.swing.SwingUtilities; +import javax.swing.Timer; +import javax.swing.event.DocumentEvent; + +import java.awt.GridBagConstraints; +import java.awt.Insets; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.Iterator; +import java.util.List; + +/** + * This is the Person to Person implementation of ChatRoom + * This room only allows for 1 to 1 conversations. + */ +public class ChatRoomImpl extends ChatRoom { + + private List messageEventListeners = new ArrayList(); + private String roomname; + private Icon tabIcon; + private String roomTitle; + private String tabTitle; + private String participantJID; + private String participantNickname; + private ChatRoomMessageManager messageManager; + private MessageEventRequestListener messageEventRequestListener; + + boolean isOnline = true; + + private Presence presence; + + private boolean offlineSent; + + private Roster roster; + + private long lastTypedCharTime; + private boolean sendNotification; + + private Timer typingTimer; + private boolean sendTypingNotification; + private String threadID; + + + /** + * Constructs a 1-to-1 ChatRoom. + * + * @param participantJID the participants jid to chat with. + * @param participantNickname the nickname of the participant. + * @param title the title of the room. + */ + public ChatRoomImpl(final String participantJID, String participantNickname, String title) { + this.participantJID = participantJID; + this.participantNickname = participantNickname; + + AndFilter presenceFilter = new AndFilter(new PacketTypeFilter(Presence.class), new FromContainsFilter(this.participantJID)); + + // Register PacketListeners + + AndFilter messageFilter = new AndFilter(new PacketTypeFilter(Message.class), new FromContainsFilter(participantJID)); + SparkManager.getConnection().addPacketListener(this, messageFilter); + + SparkManager.getConnection().addPacketListener(this, presenceFilter); + + // The roomname will be the participantJID + this.roomname = participantJID; + + // Use the agents username as the Tab Title + this.tabTitle = title; + + // The name of the room will be the node of the user jid + conversation. + final SimpleDateFormat formatter = new SimpleDateFormat("h:mm a"); + this.roomTitle = participantNickname + " - Started " + formatter.format(new Date()); + + // Add RoomInfo + this.getSplitPane().setRightComponent(null); + getSplitPane().setDividerSize(0); + + messageManager = new ChatRoomMessageManager(); + + SparkManager.getMessageEventManager().addMessageEventNotificationListener(messageManager); + + roster = SparkManager.getConnection().getRoster(); + presence = roster.getPresence(participantJID); + StatusItem statusItem = SparkManager.getWorkspace().getStatusBar().getItemFromPresence(presence); + RosterEntry entry = roster.getEntry(participantJID); + + if (statusItem == null) { + tabIcon = SparkRes.getImageIcon(SparkRes.CLEAR_BALL_ICON); + } + else { + String status = presence.getStatus(); + if (status != null && status.indexOf("phone") != -1) { + tabIcon = SparkRes.getImageIcon(SparkRes.ON_PHONE_IMAGE); + } + else { + tabIcon = statusItem.getIcon(); + } + } + + PacketFilter filter = new AndFilter(new PacketTypeFilter(Presence.class), new FromContainsFilter(participantJID)); + SparkManager.getConnection().addPacketListener(new PacketListener() { + public void processPacket(Packet packet) { + presence = (Presence)packet; + } + }, filter); + + // Create toolbar buttons. + ChatRoomButton infoButton = new ChatRoomButton("", SparkRes.getImageIcon(SparkRes.PROFILE_IMAGE_24x24)); + infoButton.setToolTipText("View information about this user"); + + // Create basic toolbar. + getToolBar().addChatRoomButton(infoButton); + + // If the user is not in the roster, then allow user to add them. + if (entry == null && !StringUtils.parseResource(participantJID).equals(participantNickname)) { + ChatRoomButton addToRosterButton = new ChatRoomButton("", SparkRes.getImageIcon(SparkRes.ADD_IMAGE_24x24)); + addToRosterButton.setToolTipText("Add this user to your roster."); + getToolBar().addChatRoomButton(addToRosterButton); + addToRosterButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + RosterDialog rosterDialog = new RosterDialog(); + rosterDialog.setDefaultJID(participantJID); + rosterDialog.setDefaultNickname(getParticipantNickname()); + rosterDialog.showRosterDialog(SparkManager.getChatManager().getChatContainer().getChatFrame()); + } + }); + } + + // Show VCard. + infoButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + VCardManager vcard = SparkManager.getVCardManager(); + vcard.viewProfile(participantJID, SparkManager.getChatManager().getChatContainer()); + } + }); + + // If this is a private chat from a group chat room, do not show toolbar. + if (StringUtils.parseResource(participantJID).equals(participantNickname)) { + getToolBar().setVisible(false); + } + + + typingTimer = new Timer(2000, new ActionListener() { + public void actionPerformed(ActionEvent e) { + long now = System.currentTimeMillis(); + if (now - lastTypedCharTime > 2000) { + if (!sendNotification) { + // send cancel + SparkManager.getMessageEventManager().sendCancelledNotification(getParticipantJID(), threadID); + + sendNotification = true; + } + + + } + } + }); + + typingTimer.start(); + + // Add message event request listener + messageEventRequestListener = new ChatMessageEventRequestListener(); + SparkManager.getMessageEventManager().addMessageEventRequestListener(messageEventRequestListener); + + // Add VCard Panel + final VCardPanel vcardPanel = new VCardPanel(this); + getToolBar().add(vcardPanel, new GridBagConstraints(0, 1, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); + } + + public void closeChatRoom() { + super.closeChatRoom(); + + SparkManager.getChatManager().removeChat(this); + + SparkManager.getMessageEventManager().removeMessageEventNotificationListener(messageManager); + + SparkManager.getConnection().removePacketListener(this); + } + + public void sendMessage() { + String text = getChatInputEditor().getText(); + sendMessage(text); + } + + public void sendMessage(String text) { + final Message message = new Message(); + + if (threadID == null) { + threadID = StringUtils.randomString(6); + } + message.setThread(threadID); + + // Set the body of the message using typedMessage + message.setBody(text); + + // IF there is no body, just return and do nothing + if (!ModelUtil.hasLength(text)) { + return; + } + + // Fire Message Filters + SparkManager.getChatManager().filterOutgoingMessage(message); + + sendMessage(message); + + sendNotification = true; + } + + /** + * Sends a message to the appropriate jid. The message is automatically added to the transcript. + * + * @param message the message to send. + */ + public void sendMessage(Message message) { + // Check to see if the user is online to recieve this message. + RosterEntry entry = roster.getEntry(participantJID); + if (presence == null && !offlineSent && entry != null) { + getTranscriptWindow().insertErrorMessage("The user is offline and will receive the message on their next login."); + offlineSent = true; + } + + try { + getTranscriptWindow().insertMessage(getNickname(), message.getBody()); + getChatInputEditor().selectAll(); + + getTranscriptWindow().validate(); + getTranscriptWindow().repaint(); + getChatInputEditor().clear(); + } + catch (Exception ex) { + Log.error("Error sending message", ex); + } + + // Before sending message, let's add our full jid for full verification + message.setType(Message.Type.CHAT); + message.setTo(participantJID); + message.setFrom(SparkManager.getSessionManager().getJID()); + + // Notify users that message has been sent + fireMessageSent(message); + + addToTranscript(message, false); + + getChatInputEditor().setCaretPosition(0); + getChatInputEditor().requestFocusInWindow(); + scrollToBottom(); + + + MessageEventManager.addNotificationsRequests(message, true, true, true, true); + + // Send the message that contains the notifications request + try { + fireOutgoingMessageSending(message); + SparkManager.getConnection().sendPacket(message); + } + catch (Exception ex) { + Log.error("Error sending message", ex); + } + } + + public String getRoomname() { + return roomname; + } + + + public Icon getTabIcon() { + return tabIcon; + } + + public String getTabTitle() { + return tabTitle; + } + + public String getRoomTitle() { + return roomTitle; + } + + public Message.Type getChatType() { + return Message.Type.CHAT; + } + + public void leaveChatRoom() { + // There really is no such thing in Agent to Agent + } + + public boolean isActive() { + return true; + } + + + public String getParticipantJID() { + return participantJID; + } + + /** + * Returns the users full jid (ex. macbeth@jivesoftware.com/spark). + * + * @return the users Full JID. + */ + public String getJID() { + presence = roster.getPresence(getParticipantJID()); + if (presence == null) { + return getParticipantJID(); + } + return presence.getFrom(); + } + + /** + * Process incoming packets. + * + * @param packet - the packet to process + */ + public void processPacket(final Packet packet) { + final Runnable runnable = new Runnable() { + public void run() { + if (packet instanceof Presence) { + final Presence presence = (Presence)packet; + + ContactList list = SparkManager.getWorkspace().getContactList(); + ContactItem contactItem = list.getContactItemByJID(getParticipantJID()); + + final SimpleDateFormat formatter = new SimpleDateFormat("h:mm a"); + String time = formatter.format(new Date()); + + if (presence.getType() == Presence.Type.UNAVAILABLE && contactItem != null) { + if (isOnline) { + getTranscriptWindow().insertNotificationMessage("*** " + participantNickname + " went offline at " + time + "."); + } + isOnline = false; + } + else if (presence.getType() == Presence.Type.AVAILABLE) { + if (!isOnline) { + getTranscriptWindow().insertNotificationMessage("*** " + participantNickname + " is online at " + time + "."); + } + isOnline = true; + } + } + else if (packet instanceof Message) { + // Do something with the incoming packet here. + final Message message = (Message)packet; + if (message.getError() != null) { + SparkManager.getMessageEventManager().removeMessageEventNotificationListener(messageManager); + return; + } + + if (threadID == null) { + threadID = message.getThread(); + if (threadID == null) { + threadID = StringUtils.randomString(6); + } + } + + boolean broadcast = message.getProperty("broadcast") != null; + + // If this is a group chat message, discard + if (message.getType() == Message.Type.GROUP_CHAT || broadcast) { + return; + } + + // Do not accept Administrative messages. + String host = SparkManager.getSessionManager().getServerAddress(); + if (host.equals(message.getFrom())) { + return; + } + + // If the message is not from the current agent. Append to chat. + if (message.getBody() != null) { + if (!participantJID.equals(message.getFrom())) { + // Create new Chat Object. + participantJID = message.getFrom(); + } + participantJID = message.getFrom(); + insertMessage(message); + + // Clear typing + getNotificationLabel().setText(""); + getNotificationLabel().setIcon(SparkRes.getImageIcon(SparkRes.BLANK_IMAGE)); + } + } + } + }; + SwingUtilities.invokeLater(runnable); + } + + /** + * Returns the nickname of the user chatting with. + * + * @return the nickname of the chatting user. + */ + public String getParticipantNickname() { + return participantNickname; + } + + + /** + * Private implementation of the MessageEventNotificationListener. + */ + private class ChatRoomMessageManager implements MessageEventNotificationListener { + + ChatRoomMessageManager() { + + } + + public void deliveredNotification(String from, String packetID) { + } + + public void displayedNotification(String from, String packetID) { + } + + public void composingNotification(final String from, String packetID) { + if (!from.startsWith(participantJID)) { + return; + } + + SwingUtilities.invokeLater(new Runnable() { + public void run() { + String isTypingText = participantNickname + " is typing a message..."; + getNotificationLabel().setText(isTypingText); + getNotificationLabel().setIcon(SparkRes.getImageIcon(SparkRes.SMALL_MESSAGE_EDIT_IMAGE)); + } + }); + } + + public void offlineNotification(String from, String packetID) { + } + + public void cancelledNotification(String from, String packetID) { + // Remove is typing text. + getNotificationLabel().setText(""); + getNotificationLabel().setIcon(SparkRes.getImageIcon(SparkRes.BLANK_IMAGE)); + } + } + + /** + * The current SendField has been updated somehow. + * + * @param e - the DocumentEvent to respond to. + */ + public void insertUpdate(DocumentEvent e) { + checkForText(e); + + lastTypedCharTime = System.currentTimeMillis(); + + // If the user pauses for more than two seconds, send out a new notice. + if (sendNotification) { + try { + if (sendTypingNotification) { + SparkManager.getMessageEventManager().sendComposingNotification(getParticipantJID(), threadID); + } + sendNotification = false; + } + catch (Exception exception) { + Log.error("Error updating", exception); + } + } + } + + public void insertMessage(Message message) { + // Debug info + super.insertMessage(message); + + getTranscriptWindow().insertOthersMessage(participantNickname, message.getBody(), null); + + // Set the participant jid to their full JID. + participantJID = message.getFrom(); + } + + public void addMessageEventListener(MessageEventListener listener) { + messageEventListeners.add(listener); + } + + public void removeMessageEventListener(MessageEventListener listener) { + messageEventListeners.remove(listener); + } + + public Collection getMessageEventListeners() { + return messageEventListeners; + } + + public void fireOutgoingMessageSending(Message message) { + Iterator messageEventListeners = new ArrayList(getMessageEventListeners()).iterator(); + while (messageEventListeners.hasNext()) { + ((MessageEventListener)messageEventListeners.next()).sendingMessage(message); + } + } + + public void fireReceivingIncomingMessage(Message message) { + Iterator messageEventListeners = new ArrayList(getMessageEventListeners()).iterator(); + while (messageEventListeners.hasNext()) { + ((MessageEventListener)messageEventListeners.next()).receivingMessage(message); + } + } + + /** + * Internal implementation of the MessageEventRequestListener. + */ + private class ChatMessageEventRequestListener implements MessageEventRequestListener { + public void deliveredNotificationRequested(String from, String packetID, MessageEventManager messageEventManager) { + // DefaultMessageEventRequestListener automatically responds that the message was delivered when receives this request + messageEventManager.sendDeliveredNotification(from, packetID); + } + + public void displayedNotificationRequested(String from, String packetID, MessageEventManager messageEventManager) { + // Send to the message's sender that the message was displayed + messageEventManager.sendDisplayedNotification(from, packetID); + } + + public void composingNotificationRequested(String from, String packetID, MessageEventManager messageEventManager) { + sendTypingNotification = true; + } + + public void offlineNotificationRequested(String from, String packetID, MessageEventManager messageEventManager) { + // The XMPP server should take care of this request. Do nothing. + } + } + + +} \ No newline at end of file diff --git a/src/java/org/jivesoftware/spark/ui/rooms/GroupChatRoom.java b/src/java/org/jivesoftware/spark/ui/rooms/GroupChatRoom.java new file mode 100644 index 00000000..4a0ef4af --- /dev/null +++ b/src/java/org/jivesoftware/spark/ui/rooms/GroupChatRoom.java @@ -0,0 +1,966 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.ui.rooms; + +import org.jivesoftware.resource.SparkRes; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.filter.AndFilter; +import org.jivesoftware.smack.filter.FromContainsFilter; +import org.jivesoftware.smack.filter.PacketFilter; +import org.jivesoftware.smack.filter.PacketTypeFilter; +import org.jivesoftware.smack.packet.Message; +import org.jivesoftware.smack.packet.Packet; +import org.jivesoftware.smack.packet.Presence; +import org.jivesoftware.smack.util.StringUtils; +import org.jivesoftware.smackx.Form; +import org.jivesoftware.smackx.MessageEventManager; +import org.jivesoftware.smackx.MessageEventNotificationListener; +import org.jivesoftware.smackx.muc.DefaultParticipantStatusListener; +import org.jivesoftware.smackx.muc.DefaultUserStatusListener; +import org.jivesoftware.smackx.muc.MultiUserChat; +import org.jivesoftware.smackx.muc.SubjectUpdatedListener; +import org.jivesoftware.smackx.packet.DelayInformation; +import org.jivesoftware.smackx.packet.MUCUser; +import org.jivesoftware.smackx.packet.MUCUser.Destroy; +import org.jivesoftware.spark.SparkManager; +import org.jivesoftware.spark.plugin.ContextMenuListener; +import org.jivesoftware.spark.ui.ChatContainer; +import org.jivesoftware.spark.ui.ChatFrame; +import org.jivesoftware.spark.ui.ChatRoom; +import org.jivesoftware.spark.ui.ChatRoomNotFoundException; +import org.jivesoftware.spark.ui.conferences.ConferenceRoomInfo; +import org.jivesoftware.spark.ui.conferences.ConferenceUtils; +import org.jivesoftware.spark.ui.conferences.DataFormDialog; +import org.jivesoftware.spark.util.ModelUtil; +import org.jivesoftware.spark.util.log.Log; + +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.Icon; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPopupMenu; +import javax.swing.SwingUtilities; +import javax.swing.Timer; +import javax.swing.event.DocumentEvent; + +import java.awt.GridBagConstraints; +import java.awt.Insets; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.MouseEvent; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.Iterator; +import java.util.List; + +/** + * GroupChatRoom is the conference chat room UI used to have Multi-User Chats. + */ +public final class GroupChatRoom extends ChatRoom { + private final MultiUserChat chat; + private final AndFilter chatFilter; + private final String roomname; + private Icon tabIcon = SparkRes.getImageIcon(SparkRes.CONFERENCE_IMAGE_16x16); + private String tabTitle; + private final String roomTitle; + private boolean isActive = true; + private boolean showPresenceMessages = true; + private JLabel subjectLabel = new JLabel(); + + private List currentUserList = new ArrayList(); + + private String conferenceService; + private List blockedUsers = new ArrayList(); + + private ChatRoomMessageManager messageManager; + private Timer typingTimer; + private int typedChars; + + private ConferenceRoomInfo roomInfo; + + /** + * Creates a GroupChatRoom from a MultiUserChat. + * + * @param chat the MultiUserChat to create a GroupChatRoom from. + */ + public GroupChatRoom(final MultiUserChat chat) { + this.chat = chat; + // Create the filter and register with the current connection + // making sure to filter by room + chatFilter = new AndFilter(new PacketTypeFilter(Message.class), new FromContainsFilter(chat.getRoom())); + // We only want to listen to the presence in this room, no other. + AndFilter presenceFilter = new AndFilter(new PacketTypeFilter(Presence.class), new FromContainsFilter(chat.getRoom())); + // Register PacketListeners + + SparkManager.getConnection().addPacketListener(this, chatFilter); + SparkManager.getConnection().addPacketListener(this, presenceFilter); + // Thie Room Name is the same as the ChatRoom name + roomname = chat.getRoom(); + roomTitle = roomname; + // We are just using a generic Group Chat. + tabTitle = StringUtils.parseName(roomname); + + // Room Information + roomInfo = new ConferenceRoomInfo(); + getSplitPane().setRightComponent(roomInfo.getGUI()); + + roomInfo.setChatRoom(this); + getSplitPane().setResizeWeight(.75); + + setupListeners(); + + + conferenceService = StringUtils.parseServer(chat.getRoom()); + + // Do not show top toolbar + getToolBar().add(subjectLabel, new GridBagConstraints(0, 0, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); + + // Add ContextMenuListener + getTranscriptWindow().addContextMenuListener(new ContextMenuListener() { + public void poppingUp(Object component, JPopupMenu popup) { + popup.addSeparator(); + Action inviteAction = new AbstractAction() { + public void actionPerformed(ActionEvent actionEvent) { + ConferenceUtils.inviteUsersToRoom(conferenceService, getRoomname(), null); + } + }; + + inviteAction.putValue(Action.NAME, "Invite Users"); + inviteAction.putValue(Action.SMALL_ICON, SparkRes.getImageIcon(SparkRes.SMALL_MESSAGE_IMAGE)); + + popup.add(inviteAction); + + + Action configureAction = new AbstractAction() { + public void actionPerformed(ActionEvent actionEvent) { + try { + ChatFrame chatFrame = SparkManager.getChatManager().getChatContainer().getChatFrame(); + Form form = chat.getConfigurationForm().createAnswerForm(); + new DataFormDialog(chatFrame, chat, form); + } + catch (XMPPException e) { + Log.error("Error configuring room.", e); + } + } + }; + + configureAction.putValue(Action.NAME, "Configure Room"); + configureAction.putValue(Action.SMALL_ICON, SparkRes.getImageIcon(SparkRes.SMALL_ALL_AGENTS_IMAGE)); + if (SparkManager.getUserManager().isOwner((GroupChatRoom)getChatRoom(), chat.getNickname())) { + popup.add(configureAction); + } + + Action subjectAction = new AbstractAction() { + public void actionPerformed(ActionEvent actionEvent) { + String newSubject = JOptionPane.showInputDialog(getChatRoom(), "Enter New Subject?", "Change Subject", JOptionPane.QUESTION_MESSAGE); + if (ModelUtil.hasLength(newSubject)) { + try { + chat.changeSubject(newSubject); + } + catch (XMPPException e) { + } + } + } + }; + + subjectAction.putValue(Action.NAME, "Change subject"); + subjectAction.putValue(Action.SMALL_ICON, SparkRes.getImageIcon(SparkRes.SMALL_MESSAGE_EDIT_IMAGE)); + popup.add(subjectAction); + + // Define actions to modify/view room information + Action destroyRoomAction = new AbstractAction() { + public void actionPerformed(ActionEvent e) { + int ok = JOptionPane.showConfirmDialog(getChatRoom(), "Destroying the room removes all users from the room. Continue?", "Destroy Room Confirmation", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE); + if (ok == JOptionPane.NO_OPTION) { + return; + } + + String reason = JOptionPane.showInputDialog(getChatRoom(), "Reason for destroying the room?", "Enter Reason", JOptionPane.QUESTION_MESSAGE); + if (ModelUtil.hasLength(reason)) { + try { + chat.destroy(reason, null); + getChatRoom().leaveChatRoom(); + } + catch (XMPPException e1) { + Log.warning("Unable to destroy room", e1); + } + } + } + }; + + destroyRoomAction.putValue(Action.NAME, "Destroy Room"); + destroyRoomAction.putValue(Action.SMALL_ICON, SparkRes.getImageIcon(SparkRes.SMALL_DELETE)); + if (SparkManager.getUserManager().isOwner((GroupChatRoom)getChatRoom(), getNickname())) { + popup.add(destroyRoomAction); + } + + + } + + public void poppingDown(JPopupMenu popup) { + + } + + public boolean handleDefaultAction(MouseEvent e) { + return false; + } + }); + + + messageManager = new ChatRoomMessageManager(); + } + + /** + * Sets the title to use on the tab describing the Conference room. + * + * @param tabTitle the title to use on the tab. + */ + public void setTabTitle(String tabTitle) { + this.tabTitle = tabTitle; + } + + /** + * Call this method if you wish to hide the participant list. + */ + public void hideParticipantList() { + getSplitPane().setRightComponent(null); + } + + /** + * Have the user leave this chat room and then close it. + */ + public void closeChatRoom() { + // Specify the end time. + super.closeChatRoom(); + + ChatContainer container = SparkManager.getChatManager().getChatContainer(); + container.leaveChatRoom(this); + container.closeTab(this); + } + + /** + * Sends a message. + * + * @param message - the message to send. + */ + public void sendMessage(Message message) { + try { + message.setTo(chat.getRoom()); + message.setType(Message.Type.GROUP_CHAT); + MessageEventManager.addNotificationsRequests(message, true, true, true, true); + // Add packetID to list + addPacketID(message.getPacketID()); + + chat.sendMessage(message); + } + catch (XMPPException ex) { + Log.error("Unable to send message in conference chat.", ex); + } + + try { + getTranscriptWindow().insertMessage(getNickname(), message.getBody()); + getChatInputEditor().selectAll(); + + getTranscriptWindow().validate(); + getTranscriptWindow().repaint(); + getChatInputEditor().clear(); + } + catch (Exception ex) { + Log.error("Error sending message", ex); + } + + // Notify users that message has been sent + fireMessageSent(message); + + addToTranscript(message, false); + + getChatInputEditor().setCaretPosition(0); + getChatInputEditor().requestFocusInWindow(); + scrollToBottom(); + } + + /** + * Sends a message. + * + * @param message - the message to send. + */ + public void sendMessageWithoutNotification(Message message) { + try { + message.setTo(chat.getRoom()); + message.setType(Message.Type.GROUP_CHAT); + MessageEventManager.addNotificationsRequests(message, true, true, true, true); + // Add packetID to list + addPacketID(message.getPacketID()); + + chat.sendMessage(message); + } + catch (XMPPException ex) { + Log.error("Unable to send message in conference chat.", ex); + } + + try { + getTranscriptWindow().insertMessage(getNickname(), message.getBody()); + getChatInputEditor().selectAll(); + + getTranscriptWindow().validate(); + getTranscriptWindow().repaint(); + getChatInputEditor().clear(); + } + catch (Exception ex) { + Log.error("Error sending message", ex); + } + + addToTranscript(message, false); + + getChatInputEditor().setCaretPosition(0); + getChatInputEditor().requestFocusInWindow(); + scrollToBottom(); + } + + /** + * Return name of the room specified when the room was created. + * + * @return the roomname. + */ + public String getRoomname() { + return roomname; + } + + /** + * Retrieve the nickname of the user in this groupchat. + * + * @return the nickname of the agent in this groupchat + */ + public String getNickname() { + return chat.getNickname(); + } + + /** + * Returns the ChatFilter. + * + * @return the chatfilter for this groupchat. + */ + public PacketFilter getPacketFilter() { + return chatFilter; + } + + /** + * Sets the icon to use on the tab. + * + * @param tabIcon the icon to use on the tab. + */ + public void setTabIcon(Icon tabIcon) { + this.tabIcon = tabIcon; + } + + /** + * Return the Icon that should be used in the tab of this GroupChat Pane. + * + * @return the Icon to use in tab. + */ + public Icon getTabIcon() { + return tabIcon; + } + + /** + * Return the title that should be used in the tab. + * + * @return the title to be used on the tab. + */ + public String getTabTitle() { + return tabTitle; + } + + /** + * Return the title of this room. + * + * @return the title of this room. + */ + public String getRoomTitle() { + return getTabTitle(); + } + + /** + * Return the type of chat we are in. + * + * @return the type of chat we are in. + */ + public Message.Type getChatType() { + return Message.Type.GROUP_CHAT; + } + + /** + * Implementation of leaveChatRoom. + */ + public void leaveChatRoom() { + if (!isActive) { + return; + } + + // Remove Packet Listener + SparkManager.getConnection().removePacketListener(this); + + // Disable Send Field + getChatInputEditor().showAsDisabled(); + + // Do not allow other to try and invite or transfer chat + disableToolbar(); + + getToolBar().setVisible(false); + + // Update Room Notice To Inform Agent that he has left the chat. + getTranscriptWindow().insertNotificationMessage(getNickname() + " has left the room."); + + // Leave the Chat. + try { + chat.leave(); + } + catch (Exception e) { + Log.error("Closing Group Chat Room error.", e); + } + + // Set window as greyed out. + getTranscriptWindow().showDisabledWindowUI(); + + // Update Notification Label + getNotificationLabel().setText("Chat session ended on " + SparkManager.DATE_SECOND_FORMATTER.format(new java.util.Date())); + getNotificationLabel().setIcon(null); + getNotificationLabel().setEnabled(false); + + getSplitPane().setRightComponent(null); + getSplitPane().setDividerSize(0); + } + + /** + * If true, will display all presence messages. Set to false to turn off presence + * notifications. + * + * @param showMessages true to display presence messages, otherwise false. + */ + public void showPresenceMessages(boolean showMessages) { + showPresenceMessages = showMessages; + } + + /** + * Returns whether or not this ChatRoom is active. To be active + * means to have the agent still engaged in a conversation with a + * customer. + * + * @return true if the ChatRoom is active. + */ + public boolean isActive() { + return isActive; + } + + /** + * Returns the number of participants in this room. + * + * @return the number of participants in this room. + */ + public int getParticipantCount() { + if (!isActive) { + return 0; + } + return chat.getOccupantsCount(); + } + + /** + * Implementation of processPacket to handle muc related packets. + * + * @param packet the packet. + */ + public void processPacket(final Packet packet) { + super.processPacket(packet); + if (packet instanceof Presence) { + SwingUtilities.invokeLater(new Runnable() { + public void run() { + handlePresencePacket(packet); + } + }); + + } + if (packet instanceof Message) { + SwingUtilities.invokeLater(new Runnable() { + public void run() { + handleMessagePacket(packet); + } + }); + + } + } + + /** + * Handle all MUC related packets. + * + * @param packet the packet. + */ + private void handleMessagePacket(Packet packet) { + // Do something with the incoming packet here. + final Message message = (Message)packet; + if (message.getType() == Message.Type.GROUP_CHAT) { + DelayInformation inf = (DelayInformation)message.getExtension("x", "jabber:x:delay"); + Date sentDate; + if (inf != null) { + sentDate = inf.getStamp(); + } + else { + sentDate = new Date(); + } + + + final boolean hasPacketID = packetIDExists(message.getPacketID()); + + // Do not accept Administrative messages. + String host = SparkManager.getSessionManager().getServerAddress(); + if (host.equals(message.getFrom())) { + return; + } + + String messageNickname = StringUtils.parseResource(message.getFrom()); + + boolean isFromMe = messageNickname.equals(getNickname()) && inf == null; + + // If the message is not from the current user. Append to chat. + if (ModelUtil.hasLength(message.getBody()) && !isFromMe) { + // Update transcript + super.insertMessage(message); + + String from = StringUtils.parseResource(message.getFrom()); + + if (inf != null) { + getTranscriptWindow().insertHistoryMessage(from, message.getBody(), sentDate); + } + else { + if (isBlocked(message.getFrom())) { + return; + } + + boolean isFromRoom = message.getFrom().indexOf("/") == -1; + + if (!SparkManager.getUserManager().hasVoice(this, StringUtils.parseResource(message.getFrom())) && !isFromRoom) { + return; + } + + String body = message.getBody().replaceAll("/me", from); + getTranscriptWindow().insertOthersMessage(from, body, sentDate); + } + + if (typingTimer != null) { + getNotificationLabel().setText(""); + getNotificationLabel().setIcon(SparkRes.getImageIcon(SparkRes.BLANK_IMAGE)); + } + } + } + else if (message.getType() == Message.Type.CHAT) { + ChatRoom chatRoom = null; + try { + chatRoom = SparkManager.getChatManager().getChatContainer().getChatRoom(message.getFrom()); + } + catch (ChatRoomNotFoundException e) { + + // Create new room + chatRoom = new ChatRoomImpl(message.getFrom(), StringUtils.parseResource(message.getFrom()), message.getFrom()); + SparkManager.getChatManager().getChatContainer().addChatRoom(chatRoom); + + SparkManager.getChatManager().getChatContainer().activateChatRoom(chatRoom); + + // Check to see if this is a message notification. + if (message.getBody() != null) { + chatRoom.insertMessage(message); + } + } + + } + else if (message.getError() != null) { + String errorMessage = ""; + + if (message.getError().getCode() == 403 && message.getSubject() != null) { + errorMessage = "You are not allowed to change the subject of this room."; + } + + else if (message.getError().getCode() == 403) { + errorMessage = "Received a forbidden error from the server."; + } + + if (ModelUtil.hasLength(errorMessage)) { + getTranscriptWindow().insertErrorMessage(errorMessage); + } + } + } + + private void handlePresencePacket(Packet packet) { + Presence presence = (Presence)packet; + final String from = presence.getFrom(); + final String nickname = StringUtils.parseResource(from); + + MUCUser mucUser = (MUCUser)packet.getExtension("x", "http://jabber.org/protocol/muc#user"); + String code = ""; + if (mucUser != null) { + code = mucUser.getStatus() != null ? mucUser.getStatus().getCode() : ""; + + Destroy destroy = mucUser.getDestroy(); + if (destroy != null) { + String reason = destroy.getReason(); + JOptionPane.showMessageDialog(this, "This room has been destroyed.\nReason: " + reason, "Room Destroyed", JOptionPane.INFORMATION_MESSAGE); + leaveChatRoom(); + return; + } + } + + + if (presence.getType() == Presence.Type.UNAVAILABLE && !"303".equals(code)) { + if (currentUserList.contains(from)) { + if (showPresenceMessages) { + getTranscriptWindow().insertNotificationMessage(nickname + " has left the room."); + scrollToBottom(); + } + currentUserList.remove(from); + } + } + else { + if (!currentUserList.contains(from)) { + currentUserList.add(from); + getChatInputEditor().setEnabled(true); + if (showPresenceMessages) { + getTranscriptWindow().insertNotificationMessage(nickname + " has joined the room."); + scrollToBottom(); + } + } + } + } + + private void setupListeners() { + chat.addParticipantStatusListener(new DefaultParticipantStatusListener() { + public void kicked(String participant) { + String nickname = StringUtils.parseResource(participant); + insertText(nickname + " has been kicked out of the room."); + } + + public void voiceGranted(String participant) { + String nickname = StringUtils.parseResource(participant); + insertText(nickname + " has been given a voice in this room."); + } + + public void voiceRevoked(String participant) { + String nickname = StringUtils.parseResource(participant); + insertText(nickname + "'s voice has been revoked."); + } + + public void banned(String participant) { + String nickname = StringUtils.parseResource(participant); + insertText(nickname + " has been banned from this room."); + } + + public void membershipGranted(String participant) { + String nickname = StringUtils.parseResource(participant); + insertText(nickname + " has been given membership priviliges."); + } + + public void membershipRevoked(String participant) { + String nickname = StringUtils.parseResource(participant); + insertText(nickname + "'s membership has been revoked."); + } + + public void moderatorGranted(String participant) { + String nickname = StringUtils.parseResource(participant); + insertText(nickname + " has been granted moderator privileges."); + } + + public void moderatorRevoked(String participant) { + String nickname = StringUtils.parseResource(participant); + insertText(nickname + "'s moderator privileges have been revoked."); + } + + public void ownershipGranted(String participant) { + String nickname = StringUtils.parseResource(participant); + insertText(nickname + " has been granted owner privileges."); + } + + public void ownershipRevoked(String participant) { + String nickname = StringUtils.parseResource(participant); + insertText(nickname + "'s owner privileges have been revoked."); + } + + public void adminGranted(String participant) { + String nickname = StringUtils.parseResource(participant); + insertText(nickname + " has been granted administrator privileges."); + } + + public void adminRevoked(String participant) { + String nickname = StringUtils.parseResource(participant); + insertText(nickname + " administrator privileges have been revoked."); + } + + public void nicknameChanged(String participant, String nickname) { + insertText(StringUtils.parseResource(participant) + " is now known as " + nickname); + } + }); + + chat.addUserStatusListener(new DefaultUserStatusListener() { + public void kicked(String s, String reason) { + insertText("You have been kicked by " + s); + getChatInputEditor().setEnabled(false); + getSplitPane().setRightComponent(null); + leaveChatRoom(); + } + + public void voiceGranted() { + insertText("You have been given a voice in this chat."); + getChatInputEditor().setEnabled(true); + } + + public void voiceRevoked() { + insertText("Your voice has been revoked."); + getChatInputEditor().setEnabled(false); + } + + public void banned(String s, String reason) { + insertText("You have been banned from this room."); + } + + public void membershipGranted() { + insertText("You have been granted membership privileges."); + } + + public void membershipRevoked() { + insertText("Your membership has been revoked."); + } + + public void moderatorGranted() { + insertText("You have been granted moderator privileges."); + } + + public void moderatorRevoked() { + insertText("Your moderator privileges have been revoked."); + } + + public void ownershipGranted() { + insertText("You have been granted owner privileges."); + } + + public void ownershipRevoked() { + insertText("Your owner privileges have been revoked."); + } + + public void adminGranted() { + insertText("You have been granted administrator privileges."); + } + + public void adminRevoked() { + insertText("Your admin privileges have been revoked."); + } + }); + + chat.addSubjectUpdatedListener(new SubjectListener()); + } + + /** + * Inserts a notification message within the TranscriptWindow. + * + * @param text the text to insert. + */ + public void insertText(String text) { + getTranscriptWindow().insertNotificationMessage(text); + } + + /** + * Listens for a change in subject and notified containing class. + */ + private class SubjectListener implements SubjectUpdatedListener { + + public void subjectUpdated(String subject, String by) { + subjectLabel.setText("Subject: " + subject); + subjectLabel.setToolTipText(subject); + + String nickname = StringUtils.parseResource(by); + + String insertMessage = "The subject has been changed to: " + subject; + getTranscriptWindow().insertNotificationMessage(insertMessage); + + } + } + + /** + * Returns the user format (e.g. darkcave@macbeth.shakespeare.lit/thirdwitch) of each user in the room. + * + * @return the user format (e.g. darkcave@macbeth.shakespeare.lit/thirdwitch) of each user in the room. + */ + public Collection getParticipants() { + return currentUserList; + } + + /** + * Sends the message that is currently in the send field. The message + * is automatically added to the transcript for later retrieval. + */ + public void sendMessage() { + final String text = getChatInputEditor().getText(); + sendMessage(text); + } + + public void sendMessage(String text) { + // Create message object + Message message = new Message(); + message.setBody(text); + + sendMessage(message); + } + + /** + * Returns a MultiUserChat object associated with this room. + * + * @return the MultiUserChat object associated with this room. + */ + public MultiUserChat getMultiUserChat() { + return chat; + } + + + /** + * Returns the conference service associated with this Conference Chat. + * + * @return the conference service associated with this Conference Chat. + */ + public String getConferenceService() { + return conferenceService; + } + + /** + * Adds a user to the blocked user list. Blocked users is NOT a MUC related + * item, but rather used by the client to not display messages from certain people. + * + * @param usersJID the room jid of the user (ex.spark@conference.jivesoftware.com/Dan) + */ + public void addBlockedUser(String usersJID) { + blockedUsers.add(usersJID); + } + + /** + * Removes a user from the blocked user list. + * + * @param usersJID the jid of the user (ex. spark@conference.jivesoftware.com/Dan) + */ + public void removeBlockedUser(String usersJID) { + blockedUsers.remove(usersJID); + } + + /** + * Returns true if the user is in the blocked user list. + * + * @param usersJID the jid of the user (ex. spark@conference.jivesoftware.com/Dan) + * @return true if the user is blocked, otherwise false. + */ + public boolean isBlocked(String usersJID) { + return blockedUsers.contains(usersJID); + } + + /** + * Specifies whether to use typing notifications or not. By default, + * group chat rooms will NOT use typing notifications. + * + * @param sendAndReceiveTypingNotifications + * true to use typing notifications. + */ + public void setSendAndReceiveTypingNotifications(boolean sendAndReceiveTypingNotifications) { + if (sendAndReceiveTypingNotifications) { + typingTimer = new Timer(10000, new ActionListener() { + public void actionPerformed(ActionEvent e) { + getNotificationLabel().setText(""); + getNotificationLabel().setIcon(SparkRes.getImageIcon(SparkRes.BLANK_IMAGE)); + } + }); + SparkManager.getMessageEventManager().addMessageEventNotificationListener(messageManager); + } + else { + if (typingTimer != null) { + typingTimer.stop(); + } + SparkManager.getMessageEventManager().removeMessageEventNotificationListener(messageManager); + } + } + + /** + * Private implementation of the MessageEventNotificationListener. + */ + private class ChatRoomMessageManager implements MessageEventNotificationListener { + + ChatRoomMessageManager() { + + } + + public void deliveredNotification(String from, String packetID) { + } + + public void displayedNotification(String from, String packetID) { + } + + public void composingNotification(final String from, String packetID) { + + + SwingUtilities.invokeLater(new Runnable() { + public void run() { + String bareAddress = StringUtils.parseBareAddress(from); + + if (bareAddress.equals(getRoomname())) { + String nickname = StringUtils.parseResource(from); + String isTypingText = nickname + " is typing a message..."; + getNotificationLabel().setText(isTypingText); + getNotificationLabel().setIcon(SparkRes.getImageIcon(SparkRes.SMALL_MESSAGE_EDIT_IMAGE)); + } + } + }); + } + + public void offlineNotification(String from, String packetID) { + } + + public void cancelledNotification(String from, String packetID) { + } + } + + /** + * The current SendField has been updated somehow. + * + * @param e - the DocumentEvent to respond to. + */ + public void insertUpdate(DocumentEvent e) { + checkForText(e); + + typedChars++; + + // If the user pauses for more than two seconds, send out a new notice. + if (typedChars >= 10) { + try { + if (typingTimer != null) { + final Iterator iter = chat.getOccupants(); + while (iter.hasNext()) { + String from = (String)iter.next(); + String tFrom = StringUtils.parseResource(from); + String nickname = chat.getNickname(); + if (tFrom != null && !tFrom.equals(nickname)) { + SparkManager.getMessageEventManager().sendComposingNotification(from, "djn"); + } + } + } + typedChars = 0; + } + catch (Exception exception) { + Log.error("Error updating", exception); + } + } + } + + public ConferenceRoomInfo getConferenceRoomInfo() { + return roomInfo; + } +} diff --git a/src/java/org/jivesoftware/spark/ui/rooms/package.html b/src/java/org/jivesoftware/spark/ui/rooms/package.html new file mode 100644 index 00000000..9f29a6c5 --- /dev/null +++ b/src/java/org/jivesoftware/spark/ui/rooms/package.html @@ -0,0 +1 @@ +Contains the two implementations of ChatRoom. ChatRoomImpl is used for one to one chats and GroupChatRoom which is used for Conference Room chats. \ No newline at end of file diff --git a/src/java/org/jivesoftware/spark/ui/status/CustomMessages.java b/src/java/org/jivesoftware/spark/ui/status/CustomMessages.java new file mode 100644 index 00000000..2c4ca7c5 --- /dev/null +++ b/src/java/org/jivesoftware/spark/ui/status/CustomMessages.java @@ -0,0 +1,572 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.ui.status; + +import com.thoughtworks.xstream.XStream; +import org.jivesoftware.smack.packet.Presence; +import org.jivesoftware.spark.SparkManager; +import org.jivesoftware.spark.component.JiveTreeCellRenderer; +import org.jivesoftware.spark.component.JiveTreeNode; +import org.jivesoftware.spark.component.Tree; +import org.jivesoftware.spark.component.renderer.ListIconRenderer; +import org.jivesoftware.spark.util.ResourceUtils; +import org.jivesoftware.spark.util.log.Log; + +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.ImageIcon; +import javax.swing.JCheckBox; +import javax.swing.JComboBox; +import javax.swing.JDialog; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JPopupMenu; +import javax.swing.JScrollPane; +import javax.swing.JTextField; +import javax.swing.tree.DefaultTreeModel; +import javax.swing.tree.TreePath; + +import java.awt.BorderLayout; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.event.ActionEvent; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +public class CustomMessages { + private static File customMessages = new File(SparkManager.getUserDirectory(), "custom_messages.xml"); + private static XStream xstream = new XStream(); + + private CustomMessages() { + + } + + static { + xstream.alias("custom-items", List.class); + xstream.alias("custom-status", CustomStatusItem.class); + } + + + // Handle Custom Messages + public static List load() { + List list = null; + + if (customMessages.exists()) { + try { + list = (List)xstream.fromXML(new FileReader(customMessages)); + } + catch (Exception e) { + xstream.alias("list", List.class); + xstream.alias("com.jivesoftware.workspaces.CustomStatusItem", CustomStatusItem.class); + try { + list = (List)xstream.fromXML(new FileReader(customMessages)); + } + catch (Exception e1) { + Log.error(e1); + } + } + } + + if (list == null) { + list = new ArrayList(); + } + return list; + } + + public static void save(List list) { + xstream.alias("custom-items", List.class); + xstream.alias("custom-status", CustomStatusItem.class); + + try { + xstream.toXML(list, new FileWriter(customMessages)); + } + catch (IOException e) { + Log.error("Could not save custom messages.", e); + } + } + + public static void addCustomMessage() { + CustomStatus status = new CustomStatus(); + status.invoke(null); + } + + public static void editCustomMessages() { + final JiveTreeNode rootNode = new JiveTreeNode("Custom Messages"); + final Tree tree = new Tree(rootNode); + tree.setCellRenderer(new JiveTreeCellRenderer()); + + final List customItems = load(); + + final StatusBar statusBar = SparkManager.getWorkspace().getStatusBar(); + Iterator statusItems = statusBar.getStatusList().iterator(); + while (statusItems.hasNext()) { + StatusItem item = (StatusItem)statusItems.next(); + JiveTreeNode node = new JiveTreeNode(item.getText(), false, item.getIcon()); + Iterator cMessages = customItems.iterator(); + + node.setAllowsChildren(true); + + while (cMessages.hasNext()) { + CustomStatusItem csi = (CustomStatusItem)cMessages.next(); + if (csi.getType().equals(item.getText())) { + JiveTreeNode subNode = new JiveTreeNode(csi.getStatus(), false); + node.add(subNode); + + } + } + + + rootNode.add(node); + + } + + final JScrollPane pane = new JScrollPane(tree); + // The user should only be able to close this dialog. + Object[] options = {"Close"}; + final JOptionPane optionPane = new JOptionPane(pane, JOptionPane.PLAIN_MESSAGE, + JOptionPane.OK_CANCEL_OPTION, null, options, options[0]); + + JPanel mainPanel = new JPanel(); + mainPanel.setLayout(new BorderLayout()); + mainPanel.add(optionPane, BorderLayout.CENTER); + + final JDialog optionsDialog = new JDialog(SparkManager.getMainWindow(), "Edit Custom Messages", true); + optionsDialog.setContentPane(mainPanel); + optionsDialog.pack(); + optionsDialog.setLocationRelativeTo(SparkManager.getMainWindow()); + + optionPane.addPropertyChangeListener(new PropertyChangeListener() { + public void propertyChange(PropertyChangeEvent propertyChangeEvent) { + String value = (String)optionPane.getValue(); + if ("Close".equals(value)) { + optionsDialog.setVisible(false); + return; + } + } + }); + + for (int i = 0; i <= tree.getRowCount(); i++) { + tree.expandPath(tree.getPathForRow(i)); + } + + tree.addMouseListener(new MouseAdapter() { + public void mousePressed(MouseEvent mouseEvent) { + checkPopup(mouseEvent); + } + + public void mouseReleased(MouseEvent mouseEvent) { + checkPopup(mouseEvent); + } + + public void checkPopup(MouseEvent event) { + if (!event.isPopupTrigger()) { + return; + } + + TreePath path = tree.getPathForLocation(event.getX(), event.getY()); + if (path != null) { + tree.setSelectionPath(path); + } + + final JiveTreeNode selectedNode = (JiveTreeNode)tree.getLastSelectedPathComponent(); + + if (selectedNode == null || selectedNode.getParent() == null) { + return; + } + else if (selectedNode.getParent() == rootNode) { + JPopupMenu popup = new JPopupMenu(); + Action addAction = new AbstractAction() { + public void actionPerformed(ActionEvent actionEvent) { + CustomStatus status = new CustomStatus(); + String type = (String)selectedNode.getUserObject(); + status.invoke(type); + reloadTree(rootNode, tree); + } + }; + + addAction.putValue(Action.NAME, "Add"); + popup.add(addAction); + popup.show(tree, event.getX(), event.getY()); + return; + } + + + final JiveTreeNode parentNode = (JiveTreeNode)selectedNode.getParent(); + final String messageStatus = (String)selectedNode.getUserObject(); + final String messageType = (String)parentNode.getUserObject(); + + if (event.isPopupTrigger()) { + JPopupMenu popup = new JPopupMenu(); + Action deleteAction = new AbstractAction() { + public void actionPerformed(ActionEvent actionEvent) { + List list = new ArrayList(); + Iterator iter = customItems.iterator(); + while (iter.hasNext()) { + CustomStatusItem item = (CustomStatusItem)iter.next(); + if (item.getType().equals(messageType) && item.getStatus().equals(messageStatus)) { + + } + else { + list.add(item); + } + } + + parentNode.remove(selectedNode); + DefaultTreeModel model = (DefaultTreeModel)tree.getModel(); + model.nodeStructureChanged(parentNode); + save(list); + } + }; + deleteAction.putValue(Action.NAME, "Delete"); + popup.add(deleteAction); + + + Action editAction = new AbstractAction() { + public void actionPerformed(ActionEvent actionEvent) { + List newItems = load(); + Iterator iter = newItems.iterator(); + while (iter.hasNext()) { + CustomStatusItem item = (CustomStatusItem)iter.next(); + if (item.getType().equals(messageType) && item.getStatus().equals(messageStatus)) { + CustomStatus customStatus = new CustomStatus(); + customStatus.showEditDialog(item); + + // Reload tree. + reloadTree(rootNode, tree); + break; + } + + } + + } + }; + + editAction.putValue(Action.NAME, "Edit"); + popup.add(editAction); + popup.show(tree, event.getX(), event.getY()); + } + } + }); + + optionsDialog.setVisible(true); + optionsDialog.toFront(); + optionsDialog.requestFocus(); + } + + private static void reloadTree(JiveTreeNode rootNode, Tree tree) { + StatusBar statusBar = SparkManager.getWorkspace().getStatusBar(); + rootNode.removeAllChildren(); + Iterator statusItems = statusBar.getStatusList().iterator(); + while (statusItems.hasNext()) { + StatusItem statusItem = (StatusItem)statusItems.next(); + JiveTreeNode node = new JiveTreeNode(statusItem.getText(), false, statusItem.getIcon()); + + List newItems = load(); + Iterator cMessages = newItems.iterator(); + + node.setAllowsChildren(true); + + while (cMessages.hasNext()) { + CustomStatusItem csi = (CustomStatusItem)cMessages.next(); + if (csi.getType().equals(statusItem.getText())) { + JiveTreeNode subNode = new JiveTreeNode(csi.getStatus(), false); + node.add(subNode); + + } + } + + + rootNode.add(node); + } + + DefaultTreeModel model = (DefaultTreeModel)tree.getModel(); + model.nodeStructureChanged(rootNode); + tree.expandTree(); + return; + } + + private static class CustomStatus extends JPanel { + private JLabel typeLabel = new JLabel(); + private JComboBox typeBox = new JComboBox(); + + private JLabel statusLabel = new JLabel(); + private JTextField statusField = new JTextField(); + + private JLabel priorityLabel = new JLabel(); + private JTextField priorityField = new JTextField(); + + private JCheckBox persistBox = new JCheckBox(); + + public CustomStatus() { + StatusBar statusBar = SparkManager.getWorkspace().getStatusBar(); + + // Add Mnemonics + ResourceUtils.resLabel(typeLabel, typeBox, "&Presence:"); + ResourceUtils.resLabel(statusLabel, statusField, "&Message:"); + ResourceUtils.resLabel(priorityLabel, priorityField, "P&riority:"); + ResourceUtils.resButton(persistBox, "&Save for future use"); + + setLayout(new GridBagLayout()); + + add(typeLabel, new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + add(statusLabel, new GridBagConstraints(0, 1, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + add(priorityLabel, new GridBagConstraints(0, 2, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + + add(typeBox, new GridBagConstraints(1, 0, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 150, 0)); + add(statusField, new GridBagConstraints(1, 1, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); + add(priorityField, new GridBagConstraints(1, 2, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); + + add(persistBox, new GridBagConstraints(0, 3, 2, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + + persistBox.setSelected(true); + + typeBox.setRenderer(new ListIconRenderer()); + // Add Types + Iterator statusIterator = statusBar.getStatusList().iterator(); + while (statusIterator.hasNext()) { + final StatusItem statusItem = (StatusItem)statusIterator.next(); + ImageIcon icon = (ImageIcon)statusItem.getIcon(); + + ImageIcon newIcon = new ImageIcon(icon.getImage()); + newIcon.setDescription(statusItem.getText()); + + typeBox.addItem(newIcon); + } + + priorityField.setText("1"); + statusField.setText("I'm Available"); + + } + + public String getType() { + ImageIcon icon = (ImageIcon)typeBox.getSelectedItem(); + return icon.getDescription(); + } + + public String getStatus() { + return statusField.getText(); + } + + public int getPriority() { + try { + return Integer.parseInt(priorityField.getText()); + } + catch (NumberFormatException e) { + return 1; + } + } + + public void showEditDialog(final CustomStatusItem item) { + // Construct main panel w/ layout. + final JPanel mainPanel = new JPanel(); + mainPanel.setLayout(new BorderLayout()); + + // The user should only be able to close this dialog. + Object[] options = {"Ok", "Cancel"}; + final JOptionPane optionPane = new JOptionPane(this, JOptionPane.PLAIN_MESSAGE, + JOptionPane.OK_CANCEL_OPTION, null, options, options[0]); + + mainPanel.add(optionPane, BorderLayout.CENTER); + + final JDialog optionsDialog = new JDialog(SparkManager.getMainWindow(), "Edit Custom Message", true); + optionsDialog.setContentPane(mainPanel); + optionsDialog.pack(); + + persistBox.setVisible(false); + + priorityField.setText(Integer.toString(item.getPriority())); + statusField.setText(item.getStatus()); + + String type = item.getType(); + int count = typeBox.getItemCount(); + for (int i = 0; i < count; i++) { + ImageIcon icon = (ImageIcon)typeBox.getItemAt(i); + if (icon.getDescription().equals(type)) { + typeBox.setSelectedIndex(i); + break; + } + } + + optionsDialog.setLocationRelativeTo(SparkManager.getMainWindow()); + optionPane.addPropertyChangeListener(new PropertyChangeListener() { + public void propertyChange(PropertyChangeEvent propertyChangeEvent) { + String value = (String)optionPane.getValue(); + if ("Cancel".equals(value)) { + optionsDialog.setVisible(false); + return; + } + else if ("Ok".equals(value)) { + List list = load(); + Iterator iter = list.iterator(); + + CustomStatusItem changeItem = null; + while (iter.hasNext()) { + CustomStatusItem customItem = (CustomStatusItem)iter.next(); + if (customItem.getType().equals(item.getType()) && + customItem.getStatus().equals(item.getStatus())) { + + changeItem = customItem; + break; + } + } + + + Iterator customListIterator = list.iterator(); + boolean exists = false; + while (customListIterator.hasNext()) { + CustomStatusItem customItem = (CustomStatusItem)customListIterator.next(); + String type = customItem.getType(); + String status = customItem.getStatus(); + + if (type.equals(getType()) && status.equals(getStatus())) { + exists = true; + } + } + + if (changeItem != null) { + changeItem.setPriority(getPriority()); + changeItem.setStatus(getStatus()); + changeItem.setType(getType()); + + } + + // Otherwise save. + if (!exists) { + save(list); + } + optionsDialog.setVisible(false); + } + } + }); + + optionsDialog.setVisible(true); + optionsDialog.toFront(); + optionsDialog.requestFocus(); + } + + + public void invoke(String selectedType) { + final StatusBar statusBar = SparkManager.getWorkspace().getStatusBar(); + + // Construct main panel w/ layout. + final JPanel mainPanel = new JPanel(); + mainPanel.setLayout(new BorderLayout()); + + // The user should only be able to close this dialog. + Object[] options = {"Ok", "Cancel"}; + final JOptionPane optionPane = new JOptionPane(this, JOptionPane.PLAIN_MESSAGE, + JOptionPane.OK_CANCEL_OPTION, null, options, options[0]); + + mainPanel.add(optionPane, BorderLayout.CENTER); + + final JDialog optionsDialog = new JDialog(SparkManager.getMainWindow(), "Set Status Message", true); + optionsDialog.setContentPane(mainPanel); + optionsDialog.pack(); + + if (selectedType != null) { + int count = typeBox.getItemCount(); + for (int i = 0; i < count; i++) { + ImageIcon icon = (ImageIcon)typeBox.getItemAt(i); + if (icon.getDescription().equals(selectedType)) { + typeBox.setSelectedIndex(i); + break; + } + } + persistBox.setSelected(true); + } + + + optionsDialog.setLocationRelativeTo(SparkManager.getMainWindow()); + optionPane.addPropertyChangeListener(new PropertyChangeListener() { + public void propertyChange(PropertyChangeEvent propertyChangeEvent) { + String value = (String)optionPane.getValue(); + if ("Cancel".equals(value)) { + optionsDialog.setVisible(false); + } + else if ("Ok".equals(value)) { + + if (!persistBox.isSelected()) { + // Change presence and quit. + StatusItem item = statusBar.getStatusItem(getType()); + Presence oldPresence = item.getPresence(); + + Presence presence = StatusBar.copyPresence(oldPresence); + presence.setStatus(getStatus()); + presence.setPriority(getPriority()); + + statusBar.changeAvailability(presence); + statusBar.setStatus(getStatus()); + optionsDialog.setVisible(false); + + return; + } + + List list = load(); + + CustomStatusItem customStatusItem = new CustomStatusItem(); + customStatusItem.setPriority(getPriority()); + customStatusItem.setStatus(getStatus()); + customStatusItem.setType(getType()); + + + Iterator customListIterator = list.iterator(); + boolean exists = false; + while (customListIterator.hasNext()) { + CustomStatusItem customItem = (CustomStatusItem)customListIterator.next(); + String type = customItem.getType(); + String status = customItem.getStatus(); + + if (type.equals(customStatusItem.getType()) && status.equals(customStatusItem.getStatus())) { + exists = true; + } + } + + // Otherwise save. + if (!exists) { + list.add(customStatusItem); + + // Update current status. + StatusItem item = statusBar.getStatusItem(getType()); + Presence oldPresence = item.getPresence(); + Presence presence = StatusBar.copyPresence(oldPresence); + presence.setStatus(getStatus()); + presence.setPriority(getPriority()); + + statusBar.changeAvailability(presence); + statusBar.setStatus(getStatus()); + + // Persist new item. + save(list); + } + optionsDialog.setVisible(false); + } + } + }); + + optionsDialog.setVisible(true); + optionsDialog.toFront(); + optionsDialog.requestFocus(); + } + } + + +} diff --git a/src/java/org/jivesoftware/spark/ui/status/CustomStatusItem.java b/src/java/org/jivesoftware/spark/ui/status/CustomStatusItem.java new file mode 100644 index 00000000..4e486285 --- /dev/null +++ b/src/java/org/jivesoftware/spark/ui/status/CustomStatusItem.java @@ -0,0 +1,41 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.ui.status; + +public class CustomStatusItem { + private String type; + private String status; + private int priority; + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + public int getPriority() { + return priority; + } + + public void setPriority(int priority) { + this.priority = priority; + } +} \ No newline at end of file diff --git a/src/java/org/jivesoftware/spark/ui/status/StatusBar.java b/src/java/org/jivesoftware/spark/ui/status/StatusBar.java new file mode 100644 index 00000000..89f01aa8 --- /dev/null +++ b/src/java/org/jivesoftware/spark/ui/status/StatusBar.java @@ -0,0 +1,553 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.ui.status; + +import org.jivesoftware.resource.Default; +import org.jivesoftware.resource.SparkRes; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.packet.PacketExtension; +import org.jivesoftware.smack.packet.Presence; +import org.jivesoftware.smack.packet.XMPPError; +import org.jivesoftware.smackx.packet.VCard; +import org.jivesoftware.spark.SparkManager; +import org.jivesoftware.spark.component.borders.PartialLineBorder; +import org.jivesoftware.spark.ui.PresenceListener; +import org.jivesoftware.spark.util.GraphicUtils; +import org.jivesoftware.spark.util.ModelUtil; +import org.jivesoftware.spark.util.SwingWorker; +import org.jivesoftware.spark.util.log.Log; +import org.jivesoftware.sparkimpl.profile.VCardManager; + +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.BorderFactory; +import javax.swing.Icon; +import javax.swing.ImageIcon; +import javax.swing.JLabel; +import javax.swing.JMenu; +import javax.swing.JMenuItem; +import javax.swing.JPanel; +import javax.swing.JPopupMenu; +import javax.swing.SwingUtilities; +import javax.swing.border.Border; + +import java.awt.Color; +import java.awt.FlowLayout; +import java.awt.Font; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Image; +import java.awt.Insets; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.geom.AffineTransform; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; + +public class StatusBar extends JPanel { + private List dndList = new ArrayList(); + + private JLabel imageLabel = new JLabel(); + private JLabel nicknameLabel = new JLabel(); + private StatusPanel statusPanel = new StatusPanel(); + + private Image backgroundImage; + + private Presence currentPresence; + + private JPanel commandPanel; + + public StatusBar() { + setLayout(new GridBagLayout()); + + + backgroundImage = Default.getImageIcon(Default.TOP_BOTTOM_BACKGROUND_IMAGE).getImage(); + + // Initialze command panel + commandPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); + commandPanel.setOpaque(false); + + ImageIcon brandedImage = Default.getImageIcon(Default.BRANDED_IMAGE); + if (brandedImage != null && brandedImage.getIconWidth() > 1) { + final JLabel brandedLabel = new JLabel(brandedImage); + // brandedLabel.setBorder(new PartialLineBorder(Color.LIGHT_GRAY, 1)); + add(brandedLabel, new GridBagConstraints(3, 0, 1, 3, 1.0, 0.0, GridBagConstraints.EAST, GridBagConstraints.NONE, new Insets(2, 2, 2, 2), 0, 0)); + } + + + add(imageLabel, new GridBagConstraints(0, 0, 1, 4, 0.0, 0.0, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, new Insets(2, 2, 2, 2), 0, 0)); + + add(nicknameLabel, new GridBagConstraints(1, 0, 2, 2, 1.0, 0.0, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, new Insets(5, 5, 0, 0), 0, 0)); + add(statusPanel, new GridBagConstraints(1, 2, 1, 1, 0.0, 0.0, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, new Insets(0, 2, 0, 0), 0, 0)); + + // Add Command Panel. We want adding command buttons to be simple. + add(commandPanel, new GridBagConstraints(1, 3, 3, 1, 1.0, 0.0, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0)); + + nicknameLabel.setToolTipText(SparkManager.getConnection().getUser()); + nicknameLabel.setFont(new Font("Dialog", Font.BOLD, 12)); + + + populateDndList(); + + + setStatus("Online"); + currentPresence = new Presence(Presence.Type.AVAILABLE, "Online", -1, Presence.Mode.AVAILABLE); + + + setBorder(BorderFactory.createLineBorder(new Color(197, 213, 230), 1)); + + loadVCard(); + + SparkManager.getSessionManager().addPresenceListener(new PresenceListener() { + public void presenceChanged(Presence presence) { + changeAvailability(presence); + } + }); + + // Show profile on double click of image label + imageLabel.addMouseListener(new MouseAdapter() { + public void mouseClicked(MouseEvent mouseEvent) { + if (mouseEvent.getClickCount() == 1) { + VCardManager vcardManager = SparkManager.getVCardManager(); + vcardManager.showProfile(SparkManager.getWorkspace()); + } + } + + public void mouseEntered(MouseEvent e) { + imageLabel.setCursor(GraphicUtils.HAND_CURSOR); + } + + public void mouseExited(MouseEvent e) { + imageLabel.setCursor(GraphicUtils.DEFAULT_CURSOR); + } + }); + } + + public void setAvatar(Icon icon) { + imageLabel.setIcon(icon); + invalidate(); + validateTree(); + } + + public void setNickname(String nickname) { + nicknameLabel.setText(nickname); + } + + /** + * Sets the current status text in the Status Manager. + * + * @param status the status to set. + */ + public void setStatus(String status) { + statusPanel.setStatus(status); + } + + public void showPopup(MouseEvent e) { + final JPopupMenu popup = new JPopupMenu(); + + List custom = CustomMessages.load(); + if (custom == null) { + custom = new ArrayList(); + } + + // Build menu from dndList + Iterator statusIterator = dndList.iterator(); + while (statusIterator.hasNext()) { + final StatusItem statusItem = (StatusItem)statusIterator.next(); + + final Action statusAction = new AbstractAction() { + public void actionPerformed(ActionEvent actionEvent) { + final String text = statusItem.getText(); + final StatusItem si = getStatusItem(text); + if (si == null) { + // Custom status + return; + } + + SwingWorker worker = new SwingWorker() { + public Object construct() { + changeAvailability(si.getPresence()); + return "ok"; + } + + public void finished() { + setStatus(text); + } + }; + worker.start(); + } + }; + + statusAction.putValue(Action.NAME, statusItem.getText()); + statusAction.putValue(Action.SMALL_ICON, statusItem.getIcon()); + + // Has Children + boolean hasChildren = false; + Iterator customItemIterator = custom.iterator(); + while (customItemIterator.hasNext()) { + final CustomStatusItem cItem = (CustomStatusItem)customItemIterator.next(); + String type = cItem.getType(); + if (type.equals(statusItem.getText())) { + hasChildren = true; + } + } + + if (!hasChildren) { + // Add as Menu Item + popup.add(statusAction); + } + else { + + final JMenu mainStatusItem = new JMenu(statusAction); + + + popup.add(mainStatusItem); + + // Add Custom Messages + customItemIterator = custom.iterator(); + while (customItemIterator.hasNext()) { + final CustomStatusItem customItem = (CustomStatusItem)customItemIterator.next(); + String type = customItem.getType(); + if (type.equals(statusItem.getText())) { + // Add Child Menu + Action action = new AbstractAction() { + public void actionPerformed(ActionEvent actionEvent) { + final String text = mainStatusItem.getText(); + final StatusItem si = getStatusItem(text); + if (si == null) { + // Custom status + return; + } + + SwingWorker worker = new SwingWorker() { + public Object construct() { + Presence oldPresence = si.getPresence(); + Presence presence = copyPresence(oldPresence); + presence.setStatus(customItem.getStatus()); + presence.setPriority(customItem.getPriority()); + changeAvailability(presence); + return "ok"; + } + + public void finished() { + String status = customItem.getType() + " - " + customItem.getStatus(); + setStatus(status); + } + }; + worker.start(); + } + }; + action.putValue(Action.NAME, customItem.getStatus()); + action.putValue(Action.SMALL_ICON, statusItem.getIcon()); + mainStatusItem.add(action); + } + } + + // If menu has children, allow it to still be clickable. + mainStatusItem.addMouseListener(new MouseAdapter() { + public void mouseClicked(MouseEvent mouseEvent) { + statusAction.actionPerformed(null); + popup.setVisible(false); + } + }); + } + } + + // Add change message + final JMenuItem changeStatusMenu = new JMenuItem("Set status message...", SparkRes.getImageIcon(SparkRes.BLANK_IMAGE)); + popup.addSeparator(); + + + popup.add(changeStatusMenu); + changeStatusMenu.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + CustomMessages.addCustomMessage(); + } + }); + + + Action editMessagesAction = new AbstractAction() { + public void actionPerformed(ActionEvent actionEvent) { + CustomMessages.editCustomMessages(); + } + }; + + editMessagesAction.putValue(Action.NAME, "Edit custom status messages..."); + popup.add(editMessagesAction); + + popup.show(statusPanel, 0, statusPanel.getHeight()); + } + + public void changeAvailability(final Presence presence) { + if (presence == null) { + return; + } + + if ((presence.getMode() == currentPresence.getMode()) && (presence.getType() == currentPresence.getType()) && (presence.getStatus().equals(currentPresence.getStatus()))) { + PacketExtension pe = presence.getExtension("x", "vcard-temp:x:update"); + if (pe != null) { + // Update VCard + loadVCard(); + } + return; + } + + final Runnable changePresenceRunnable = new Runnable() { + public void run() { + + currentPresence = presence; + + setStatus(presence.getStatus()); + StatusItem item = getItemFromPresence(currentPresence); + if (item != null) { + statusPanel.setIcon(item.getIcon()); + } + SparkManager.getSessionManager().changePresence(presence); + } + }; + + SwingUtilities.invokeLater(changePresenceRunnable); + } + + /** + * Populates the current Dnd List. + */ + private void populateDndList() { + final ImageIcon availableIcon = SparkRes.getImageIcon(SparkRes.GREEN_BALL); + final ImageIcon awayIcon = SparkRes.getImageIcon(SparkRes.IM_AWAY); + final ImageIcon dndIcon = SparkRes.getImageIcon(SparkRes.IM_DND); + + StatusItem online = new StatusItem(new Presence(Presence.Type.AVAILABLE, "Online", -1, Presence.Mode.AVAILABLE), availableIcon); + StatusItem freeToChat = new StatusItem(new Presence(Presence.Type.AVAILABLE, "Free To Chat", -1, Presence.Mode.CHAT), SparkRes.getImageIcon(SparkRes.FREE_TO_CHAT_IMAGE)); + StatusItem away = new StatusItem(new Presence(Presence.Type.AVAILABLE, "Away", -1, Presence.Mode.AWAY), awayIcon); + StatusItem dnd = new StatusItem(new Presence(Presence.Type.AVAILABLE, "Do Not Disturb", -1, Presence.Mode.DO_NOT_DISTURB), dndIcon); + StatusItem extendedAway = new StatusItem(new Presence(Presence.Type.AVAILABLE, "Extended Away", -1, Presence.Mode.EXTENDED_AWAY), awayIcon); + + dndList.add(freeToChat); + dndList.add(online); + dndList.add(away); + dndList.add(extendedAway); + dndList.add(dnd); + + // Set default presence icon (Avaialble) + statusPanel.setIcon(availableIcon); + } + + public StatusItem getItemFromPresence(Presence presence) { + // Handle offline presence + if (presence == null) { + return null; + } + + Iterator statusItemIterator = dndList.iterator(); + while (statusItemIterator.hasNext()) { + StatusItem item = (StatusItem)statusItemIterator.next(); + if ((presence.getMode() == item.getPresence().getMode()) && (presence.getType() == item.getPresence().getType())) { + return item; + } + } + + return null; + } + + public Collection getStatusList() { + return dndList; + } + + public Presence getPresence() { + return currentPresence; + } + + public StatusItem getStatusItem(String label) { + Iterator iter = dndList.iterator(); + while (iter.hasNext()) { + StatusItem item = (StatusItem)iter.next(); + if (item.getText().equals(label)) { + return item; + } + } + return null; + } + + public void paintComponent(Graphics g) { + double scaleX = getWidth() / (double)backgroundImage.getWidth(null); + double scaleY = getHeight() / (double)backgroundImage.getHeight(null); + AffineTransform xform = AffineTransform.getScaleInstance(scaleX, scaleY); + ((Graphics2D)g).drawImage(backgroundImage, xform, this); + } + + + private void loadVCard() { + final VCard vCard = new VCard(); + + final SwingWorker worker = new SwingWorker() { + public Object construct() { + try { + vCard.load(SparkManager.getConnection()); + } + catch (XMPPException e) { + vCard.setError(new XMPPError(404)); + } + return vCard; + } + + public void finished() { + populateWithVCardInfo(vCard); + } + }; + + worker.start(); + } + + private void populateWithVCardInfo(VCard vCard) { + if (vCard.getError() == null) { + String firstName = vCard.getFirstName(); + String lastName = vCard.getLastName(); + if (ModelUtil.hasLength(firstName) && ModelUtil.hasLength(lastName)) { + setNickname(firstName + " " + lastName); + } + else if (ModelUtil.hasLength(firstName)) { + setNickname(firstName); + } + else { + String nickname = SparkManager.getSessionManager().getUsername(); + setNickname(nickname); + } + } + else { + String nickname = SparkManager.getSessionManager().getUsername(); + setNickname(nickname); + return; + } + + + byte[] avatarBytes = null; + try { + avatarBytes = vCard.getAvatar(); + } + catch (Exception e) { + Log.error("Cannot retrieve avatar bytes.", e); + } + + + if (avatarBytes != null) { + try { + ImageIcon avatarIcon = new ImageIcon(avatarBytes); + avatarIcon = VCardManager.scale(avatarIcon); + imageLabel.setIcon(avatarIcon); + imageLabel.setBorder(new PartialLineBorder(Color.LIGHT_GRAY, 1)); + imageLabel.invalidate(); + imageLabel.validate(); + imageLabel.repaint(); + } + catch (Exception e) { + // no issue + } + } + } + + public static Presence copyPresence(Presence presence) { + return new Presence(presence.getType(), presence.getStatus(), presence.getPriority(), presence.getMode()); + } + + /** + * Return the nickname Component used to display the users profile name. + * + * @return the label. + */ + public JLabel getNicknameLabel() { + return nicknameLabel; + } + + private class StatusPanel extends JPanel { + private JLabel iconLabel; + private JLabel statusLabel; + + public StatusPanel() { + super(); + + setOpaque(false); + + iconLabel = new JLabel(); + statusLabel = new JLabel(); + + setLayout(new GridBagLayout()); + + // Remove padding from icon label + iconLabel.setIconTextGap(0); + + add(iconLabel, new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0)); + add(statusLabel, new GridBagConstraints(1, 0, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, 2, 0, 0), 0, 0)); + + statusLabel.setFont(new Font("Dialog", Font.PLAIN, 11)); + statusLabel.setIcon(SparkRes.getImageIcon(SparkRes.DOWN_ARROW_IMAGE)); + statusLabel.setHorizontalTextPosition(JLabel.LEFT); + + setOpaque(false); + + final Border border = BorderFactory.createEmptyBorder(2, 2, 2, 2); + setBorder(border); + + + statusLabel.addMouseListener(new MouseAdapter() { + public void mouseReleased(MouseEvent e) { + showPopup(e); + } + + public void mouseEntered(MouseEvent e) { + setCursor(GraphicUtils.HAND_CURSOR); + + setBorder(BorderFactory.createBevelBorder(0)); + } + + public void mouseExited(MouseEvent e) { + setCursor(GraphicUtils.DEFAULT_CURSOR); + setBorder(border); + } + + public void mousePressed(MouseEvent e) { + setBorder(BorderFactory.createBevelBorder(1)); + } + }); + + } + + public void setStatus(String status) { + int length = status.length(); + String visualStatus = status; + if (length > 30) { + visualStatus = status.substring(0, 27) + "..."; + } + + statusLabel.setText(visualStatus); + statusLabel.setToolTipText(status); + } + + public void setIcon(Icon icon) { + iconLabel.setIcon(icon); + } + } + + public void setBackgroundImage(Image image) { + this.backgroundImage = image; + } + + public JPanel getCommandPanel() { + return commandPanel; + } + +} diff --git a/src/java/org/jivesoftware/spark/ui/status/StatusItem.java b/src/java/org/jivesoftware/spark/ui/status/StatusItem.java new file mode 100644 index 00000000..87ed8c4e --- /dev/null +++ b/src/java/org/jivesoftware/spark/ui/status/StatusItem.java @@ -0,0 +1,38 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.ui.status; + +import org.jivesoftware.smack.packet.Presence; + +import javax.swing.ImageIcon; +import javax.swing.JLabel; + +public class StatusItem extends JLabel { + private Presence presence; + private ImageIcon icon; + + public StatusItem(Presence presence, ImageIcon icon) { + this.presence = presence; + this.icon = icon; + setIcon(icon); + setText(presence.getStatus()); + } + + public Presence getPresence() { + return presence; + } + + public ImageIcon getImageIcon() { + return icon; + } + + +} diff --git a/src/java/org/jivesoftware/spark/ui/status/package.html b/src/java/org/jivesoftware/spark/ui/status/package.html new file mode 100644 index 00000000..a1f081f4 --- /dev/null +++ b/src/java/org/jivesoftware/spark/ui/status/package.html @@ -0,0 +1 @@ +Provides support for plugging in ones own details into the StatusManager of Spark. \ No newline at end of file diff --git a/src/java/org/jivesoftware/spark/util/Base64.java b/src/java/org/jivesoftware/spark/util/Base64.java new file mode 100644 index 00000000..6ff6bb59 --- /dev/null +++ b/src/java/org/jivesoftware/spark/util/Base64.java @@ -0,0 +1,1236 @@ +package org.jivesoftware.spark.util; + +import org.jivesoftware.spark.util.log.Log; + +/** + * Used to encrypt and decrypt bytes -- string using Base64 encoding and decoding. + */ +public class Base64 { + +/* ******** P U B L I C F I E L D S ******** */ + + + /** + * No options specified. Value is zero. + */ + public final static int NO_OPTIONS = 0; + + /** + * Specify encoding. + */ + public final static int ENCODE = 1; + + + /** + * Specify decoding. + */ + public final static int DECODE = 0; + + + /** + * Specify that data should be gzip-compressed. + */ + public final static int GZIP = 2; + + + /** + * Don't break lines when encoding (violates strict Base64 specification) + */ + public final static int DONT_BREAK_LINES = 8; + +/* ******** P R I V A T E F I E L D S ******** */ + + + /** + * Maximum line length (76) of Base64 output. + */ + private final static int MAX_LINE_LENGTH = 76; + + + /** + * The equals sign (=) as a byte. + */ + private final static byte EQUALS_SIGN = (byte)'='; + + + /** + * The new line character (\n) as a byte. + */ + private final static byte NEW_LINE = (byte)'\n'; + + + /** + * Preferred encoding. + */ + private final static String PREFERRED_ENCODING = "UTF-8"; + + + /** + * The 64 valid Base64 values. + */ + private final static byte[] ALPHABET; + private final static byte[] _NATIVE_ALPHABET = /* May be something funny like EBCDIC */ + { + (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G', + (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N', + (byte)'O', (byte)'P', (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U', + (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z', + (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g', + (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n', + (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u', + (byte)'v', (byte)'w', (byte)'x', (byte)'y', (byte)'z', + (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', + (byte)'6', (byte)'7', (byte)'8', (byte)'9', (byte)'+', (byte)'/' + }; + + /** Determine which ALPHABET to use. */ + static { + byte[] __bytes; + try { + __bytes = new String("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/").getBytes(PREFERRED_ENCODING); + } // end try + catch (java.io.UnsupportedEncodingException use) { + __bytes = _NATIVE_ALPHABET; // Fall back to native encoding + } // end catch + ALPHABET = __bytes; + } // end static + + + /** + * Translates a Base64 value to either its 6-bit reconstruction value + * or a negative number indicating some other meaning. + */ + private final static byte[] DECODABET = + { + -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 0 - 8 + -5, -5, // Whitespace: Tab and Linefeed + -9, -9, // Decimal 11 - 12 + -5, // Whitespace: Carriage Return + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 14 - 26 + -9, -9, -9, -9, -9, // Decimal 27 - 31 + -5, // Whitespace: Space + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 33 - 42 + 62, // Plus sign at decimal 43 + -9, -9, -9, // Decimal 44 - 46 + 63, // Slash at decimal 47 + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // Numbers zero through nine + -9, -9, -9, // Decimal 58 - 60 + -1, // Equals sign at decimal 61 + -9, -9, -9, // Decimal 62 - 64 + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, // Letters 'A' through 'N' + 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // Letters 'O' through 'Z' + -9, -9, -9, -9, -9, -9, // Decimal 91 - 96 + 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, // Letters 'a' through 'm' + 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, // Letters 'n' through 'z' + -9, -9, -9, -9 // Decimal 123 - 126 + /*,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 127 - 139 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 */ + }; + + private final static byte BAD_ENCODING = -9; // Indicates error in encoding + private final static byte WHITE_SPACE_ENC = -5; // Indicates white space in encoding + private final static byte EQUALS_SIGN_ENC = -1; // Indicates equals sign in encoding + + + /** + * Defeats instantiation. + */ + private Base64() { + } + +/* ******** E N C O D I N G M E T H O D S ******** */ + + + /** + * Encodes the first three bytes of array threeBytes + * and returns a four-byte array in Base64 notation. + * + * @param threeBytes the array to convert + * @return four byte array in Base64 notation. + * @since 1.3 + */ + private static byte[] encode3to4(byte[] threeBytes) { + return encode3to4(threeBytes, 3); + } // end encodeToBytes + + + /** + * Encodes up to the first three bytes of array threeBytes + * and returns a four-byte array in Base64 notation. + * The actual number of significant bytes in your array is + * given by numSigBytes. + * The array threeBytes needs only be as big as + * numSigBytes. + * + * @param threeBytes the array to convert + * @param numSigBytes the number of significant bytes in your array + * @return four byte array in Base64 notation. + * @since 1.3 + */ + private static byte[] encode3to4(byte[] threeBytes, int numSigBytes) { + byte[] dest = new byte[4]; + encode3to4(threeBytes, 0, numSigBytes, dest, 0); + return dest; + } + + /** + * Encodes up to the first three bytes of array threeBytes + * and returns a four-byte array in Base64 notation. + * The actual number of significant bytes in your array is + * given by numSigBytes. + * The array threeBytes needs only be as big as + * numSigBytes. + * Code can reuse a byte array by passing a four-byte array as b4. + * + * @param b4 A reusable byte array to reduce array instantiation + * @param threeBytes the array to convert + * @param numSigBytes the number of significant bytes in your array + * @return four byte array in Base64 notation. + * @since 1.5.1 + */ + private static byte[] encode3to4(byte[] b4, byte[] threeBytes, int numSigBytes) { + encode3to4(threeBytes, 0, numSigBytes, b4, 0); + return b4; + } // end encode3to4 + + + /** + * Encodes up to three bytes of the array source + * and writes the resulting four Base64 bytes to destination. + * The source and destination arrays can be manipulated + * anywhere along their length by specifying + * srcOffset and destOffset. + * This method does not check to make sure your arrays + * are large enough to accomodate srcOffset + 3 for + * the source array or destOffset + 4 for + * the destination array. + * The actual number of significant bytes in your array is + * given by numSigBytes. + * + * @param source the array to convert + * @param srcOffset the index where conversion begins + * @param numSigBytes the number of significant bytes in your array + * @param destination the array to hold the conversion + * @param destOffset the index where output will be put + * @return the destination array + * @since 1.3 + */ + private static byte[] encode3to4(byte[] source, int srcOffset, int numSigBytes, + byte[] destination, int destOffset) { + // 1 2 3 + // 01234567890123456789012345678901 Bit position + // --------000000001111111122222222 Array position from threeBytes + // --------| || || || | Six bit groups to index ALPHABET + // >>18 >>12 >> 6 >> 0 Right shift necessary + // 0x3f 0x3f 0x3f Additional AND + + // Create buffer with zero-padding if there are only one or two + // significant bytes passed in the array. + // We have to shift left 24 in order to flush out the 1's that appear + // when Java treats a value as negative that is cast from a byte to an int. + int inBuff = (numSigBytes > 0 ? ((source[srcOffset] << 24) >>> 8) : 0) + | (numSigBytes > 1 ? ((source[srcOffset + 1] << 24) >>> 16) : 0) + | (numSigBytes > 2 ? ((source[srcOffset + 2] << 24) >>> 24) : 0); + + switch (numSigBytes) { + case 3: + destination[destOffset] = ALPHABET[(inBuff >>> 18)]; + destination[destOffset + 1] = ALPHABET[(inBuff >>> 12) & 0x3f]; + destination[destOffset + 2] = ALPHABET[(inBuff >>> 6) & 0x3f]; + destination[destOffset + 3] = ALPHABET[(inBuff) & 0x3f]; + return destination; + + case 2: + destination[destOffset] = ALPHABET[(inBuff >>> 18)]; + destination[destOffset + 1] = ALPHABET[(inBuff >>> 12) & 0x3f]; + destination[destOffset + 2] = ALPHABET[(inBuff >>> 6) & 0x3f]; + destination[destOffset + 3] = EQUALS_SIGN; + return destination; + + case 1: + destination[destOffset] = ALPHABET[(inBuff >>> 18)]; + destination[destOffset + 1] = ALPHABET[(inBuff >>> 12) & 0x3f]; + destination[destOffset + 2] = EQUALS_SIGN; + destination[destOffset + 3] = EQUALS_SIGN; + return destination; + + default: + return destination; + } // end switch + } // end encode3to4 + + + /** + * Serializes an object and returns the Base64-encoded + * version of that serialized object. If the object + * cannot be serialized or there is another error, + * the method will return null. + * The object is not GZip-compressed before being encoded. + * + * @param serializableObject The object to encode + * @return The Base64-encoded object + * @since 1.4 + */ + public static String encodeObject(java.io.Serializable serializableObject) { + return encodeObject(serializableObject, NO_OPTIONS); + } // end encodeObject + + + /** + * Serializes an object and returns the Base64-encoded + * version of that serialized object. If the object + * cannot be serialized or there is another error, + * the method will return null. + *

        + * Valid options:

        +     *   GZIP: gzip-compresses object before encoding it.
        +     *   DONT_BREAK_LINES: don't break lines at 76 characters
        +     *     Note: Technically, this makes your encoding non-compliant.
        +     * 
        + *

        + * Example: encodeObject( myObj, Base64.GZIP ) or + *

        + * Example: encodeObject( myObj, Base64.GZIP | Base64.DONT_BREAK_LINES ) + * + * @param serializableObject The object to encode + * @return The Base64-encoded object + * @see Base64#GZIP + * @see Base64#DONT_BREAK_LINES + * @since 2.0 + */ + public static String encodeObject(java.io.Serializable serializableObject, int options) { + // Streams + java.io.ByteArrayOutputStream baos = null; + java.io.OutputStream b64os = null; + java.io.ObjectOutputStream oos = null; + java.util.zip.GZIPOutputStream gzos = null; + + // Isolate options + int gzip = (options & GZIP); + int dontBreakLines = (options & DONT_BREAK_LINES); + + try { + // ObjectOutputStream -> (GZIP) -> Base64 -> ByteArrayOutputStream + baos = new java.io.ByteArrayOutputStream(); + b64os = new Base64.OutputStream(baos, ENCODE | dontBreakLines); + + // GZip? + if (gzip == GZIP) { + gzos = new java.util.zip.GZIPOutputStream(b64os); + oos = new java.io.ObjectOutputStream(gzos); + } // end if: gzip + else + oos = new java.io.ObjectOutputStream(b64os); + + oos.writeObject(serializableObject); + } // end try + catch (java.io.IOException e) { + Log.error(e); + return null; + } // end catch + finally { + try { + oos.close(); + } + catch (Exception e) { + } + try { + gzos.close(); + } + catch (Exception e) { + } + try { + b64os.close(); + } + catch (Exception e) { + } + try { + baos.close(); + } + catch (Exception e) { + } + } // end finally + + // Return value according to relevant encoding. + try { + return new String(baos.toByteArray(), PREFERRED_ENCODING); + } // end try + catch (java.io.UnsupportedEncodingException uue) { + return new String(baos.toByteArray()); + } // end catch + + } // end encode + + + /** + * Encodes a byte array into Base64 notation. + * Does not GZip-compress data. + * + * @param source The data to convert + * @since 1.4 + */ + public static String encodeBytes(byte[] source) { + return encodeBytes(source, 0, source.length, NO_OPTIONS); + } // end encodeBytes + + + /** + * Encodes a byte array into Base64 notation. + *

        + * Valid options:

        +     *   GZIP: gzip-compresses object before encoding it.
        +     *   DONT_BREAK_LINES: don't break lines at 76 characters
        +     *     Note: Technically, this makes your encoding non-compliant.
        +     * 
        + *

        + * Example: encodeBytes( myData, Base64.GZIP ) or + *

        + * Example: encodeBytes( myData, Base64.GZIP | Base64.DONT_BREAK_LINES ) + * + * @param source The data to convert + * @param options Specified options + * @see Base64#GZIP + * @see Base64#DONT_BREAK_LINES + * @since 2.0 + */ + public static String encodeBytes(byte[] source, int options) { + return encodeBytes(source, 0, source.length, options); + } // end encodeBytes + + + /** + * Encodes a byte array into Base64 notation. + * Does not GZip-compress data. + * + * @param source The data to convert + * @param off Offset in array where conversion should begin + * @param len Length of data to convert + * @since 1.4 + */ + public static String encodeBytes(byte[] source, int off, int len) { + return encodeBytes(source, off, len, NO_OPTIONS); + } // end encodeBytes + + + /** + * Encodes a byte array into Base64 notation. + *

        + * Valid options:

        +     *   GZIP: gzip-compresses object before encoding it.
        +     *   DONT_BREAK_LINES: don't break lines at 76 characters
        +     *     Note: Technically, this makes your encoding non-compliant.
        +     * 
        + *

        + * Example: encodeBytes( myData, Base64.GZIP ) or + *

        + * Example: encodeBytes( myData, Base64.GZIP | Base64.DONT_BREAK_LINES ) + * + * @param source The data to convert + * @param off Offset in array where conversion should begin + * @param len Length of data to convert + * @param options Specified options + * @see Base64#GZIP + * @see Base64#DONT_BREAK_LINES + * @since 2.0 + */ + public static String encodeBytes(byte[] source, int off, int len, int options) { + // Isolate options + int dontBreakLines = (options & DONT_BREAK_LINES); + int gzip = (options & GZIP); + + // Compress? + if (gzip == GZIP) { + java.io.ByteArrayOutputStream baos = null; + java.util.zip.GZIPOutputStream gzos = null; + Base64.OutputStream b64os = null; + + + try { + // GZip -> Base64 -> ByteArray + baos = new java.io.ByteArrayOutputStream(); + b64os = new Base64.OutputStream(baos, ENCODE | dontBreakLines); + gzos = new java.util.zip.GZIPOutputStream(b64os); + + gzos.write(source, off, len); + gzos.close(); + } // end try + catch (java.io.IOException e) { + Log.error(e); + return null; + } // end catch + finally { + try { + gzos.close(); + } + catch (Exception e) { + } + try { + b64os.close(); + } + catch (Exception e) { + } + try { + baos.close(); + } + catch (Exception e) { + } + } // end finally + + // Return value according to relevant encoding. + try { + return new String(baos.toByteArray(), PREFERRED_ENCODING); + } // end try + catch (java.io.UnsupportedEncodingException uue) { + return new String(baos.toByteArray()); + } // end catch + } // end if: compress + + // Else, don't compress. Better not to use streams at all then. + else { + // Convert option to boolean in way that code likes it. + boolean breakLines = dontBreakLines == 0; + + int len43 = len * 4 / 3; + byte[] outBuff = new byte[(len43) // Main 4:3 + + ((len % 3) > 0 ? 4 : 0) // Account for padding + + (breakLines ? (len43 / MAX_LINE_LENGTH) : 0)]; // New lines + int d = 0; + int e = 0; + int len2 = len - 2; + int lineLength = 0; + for (; d < len2; d += 3, e += 4) { + encode3to4(source, d + off, 3, outBuff, e); + + lineLength += 4; + if (breakLines && lineLength == MAX_LINE_LENGTH) { + outBuff[e + 4] = NEW_LINE; + e++; + lineLength = 0; + } // end if: end of line + } // en dfor: each piece of array + + if (d < len) { + encode3to4(source, d + off, len - d, outBuff, e); + e += 4; + } // end if: some padding needed + + // Return value according to relevant encoding. + try { + return new String(outBuff, 0, e, PREFERRED_ENCODING); + } // end try + catch (java.io.UnsupportedEncodingException uue) { + return new String(outBuff, 0, e); + } // end catch + + } // end else: don't compress + + } // end encodeBytes + +/* ******** D E C O D I N G M E T H O D S ******** */ + + + /** + * Decodes the first four bytes of array fourBytes + * and returns an array up to three bytes long with the + * decoded values. + * + * @param fourBytes the array with Base64 content + * @return array with decoded values + * @since 1.3 + */ + private static byte[] decode4to3(byte[] fourBytes) { + byte[] outBuff1 = new byte[3]; + int count = decode4to3(fourBytes, 0, outBuff1, 0); + byte[] outBuff2 = new byte[count]; + + for (int i = 0; i < count; i++) + outBuff2[i] = outBuff1[i]; + + return outBuff2; + } + + + /** + * Decodes four bytes from array source + * and writes the resulting bytes (up to three of them) + * to destination. + * The source and destination arrays can be manipulated + * anywhere along their length by specifying + * srcOffset and destOffset. + * This method does not check to make sure your arrays + * are large enough to accomodate srcOffset + 4 for + * the source array or destOffset + 3 for + * the destination array. + * This method returns the actual number of bytes that + * were converted from the Base64 encoding. + * + * @param source the array to convert + * @param srcOffset the index where conversion begins + * @param destination the array to hold the conversion + * @param destOffset the index where output will be put + * @return the number of decoded bytes converted + * @since 1.3 + */ + private static int decode4to3(byte[] source, int srcOffset, byte[] destination, int destOffset) { + // Example: Dk== + if (source[srcOffset + 2] == EQUALS_SIGN) { + // Two ways to do the same thing. Don't know which way I like best. + //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) + // | ( ( DECODABET[ source[ srcOffset + 1] ] << 24 ) >>> 12 ); + int outBuff = ((DECODABET[source[srcOffset]] & 0xFF) << 18) + | ((DECODABET[source[srcOffset + 1]] & 0xFF) << 12); + + destination[destOffset] = (byte)(outBuff >>> 16); + return 1; + } + + // Example: DkL= + else if (source[srcOffset + 3] == EQUALS_SIGN) { + // Two ways to do the same thing. Don't know which way I like best. + //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) + // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 ) + // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 ); + int outBuff = ((DECODABET[source[srcOffset]] & 0xFF) << 18) + | ((DECODABET[source[srcOffset + 1]] & 0xFF) << 12) + | ((DECODABET[source[srcOffset + 2]] & 0xFF) << 6); + + destination[destOffset] = (byte)(outBuff >>> 16); + destination[destOffset + 1] = (byte)(outBuff >>> 8); + return 2; + } + + // Example: DkLE + else { + try { + // Two ways to do the same thing. Don't know which way I like best. + //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) + // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 ) + // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 ) + // | ( ( DECODABET[ source[ srcOffset + 3 ] ] << 24 ) >>> 24 ); + int outBuff = ((DECODABET[source[srcOffset]] & 0xFF) << 18) + | ((DECODABET[source[srcOffset + 1]] & 0xFF) << 12) + | ((DECODABET[source[srcOffset + 2]] & 0xFF) << 6) + | ((DECODABET[source[srcOffset + 3]] & 0xFF)); + + + destination[destOffset] = (byte)(outBuff >> 16); + destination[destOffset + 1] = (byte)(outBuff >> 8); + destination[destOffset + 2] = (byte)(outBuff); + + return 3; + } + catch (Exception e) { + System.out.println("" + source[srcOffset] + ": " + (DECODABET[source[srcOffset]])); + System.out.println("" + source[srcOffset + 1] + ": " + (DECODABET[source[srcOffset + 1]])); + System.out.println("" + source[srcOffset + 2] + ": " + (DECODABET[source[srcOffset + 2]])); + System.out.println("" + source[srcOffset + 3] + ": " + (DECODABET[source[srcOffset + 3]])); + return -1; + } //e nd catch + } + } // end decodeToBytes + + + /** + * Very low-level access to decoding ASCII characters in + * the form of a byte array. Does not support automatically + * gunzipping or any other "fancy" features. + * + * @param source The Base64 encoded data + * @param off The offset of where to begin decoding + * @param len The length of characters to decode + * @return decoded data + * @since 1.3 + */ + public static byte[] decode(byte[] source, int off, int len) { + int len34 = len * 3 / 4; + byte[] outBuff = new byte[len34]; // Upper limit on size of output + int outBuffPosn = 0; + + byte[] b4 = new byte[4]; + int b4Posn = 0; + int i = 0; + byte sbiCrop = 0; + byte sbiDecode = 0; + for (i = off; i < off + len; i++) { + sbiCrop = (byte)(source[i] & 0x7f); // Only the low seven bits + sbiDecode = DECODABET[sbiCrop]; + + if (sbiDecode >= WHITE_SPACE_ENC) // White space, Equals sign or better + { + if (sbiDecode >= EQUALS_SIGN_ENC) { + b4[b4Posn++] = sbiCrop; + if (b4Posn > 3) { + outBuffPosn += decode4to3(b4, 0, outBuff, outBuffPosn); + b4Posn = 0; + + // If that was the equals sign, break out of 'for' loop + if (sbiCrop == EQUALS_SIGN) + break; + } // end if: quartet built + + } // end if: equals sign or better + + } // end if: white space, equals sign or better + else { + System.err.println("Bad Base64 input character at " + i + ": " + source[i] + "(decimal)"); + return null; + } // end else: + } // each input character + + byte[] out = new byte[outBuffPosn]; + System.arraycopy(outBuff, 0, out, 0, outBuffPosn); + return out; + } // end decode + + + /** + * Decodes data from Base64 notation, automatically + * detecting gzip-compressed data and decompressing it. + * + * @param s the string to decode + * @return the decoded data + * @since 1.4 + */ + public static byte[] decode(String s) { + byte[] bytes; + try { + bytes = s.getBytes(PREFERRED_ENCODING); + } // end try + catch (java.io.UnsupportedEncodingException uee) { + bytes = s.getBytes(); + } // end catch + // + + // Decode + bytes = decode(bytes, 0, bytes.length); + + // Check to see if it's gzip-compressed + // GZIP Magic Two-Byte Number: 0x8b1f (35615) + if (bytes.length >= 2) { + + int head = ((int)bytes[0] & 0xff) | ((bytes[1] << 8) & 0xff00); + if ( + bytes != null && // In case decoding returned null + bytes.length >= 4 && // Don't want to get ArrayIndexOutOfBounds exception + java.util.zip.GZIPInputStream.GZIP_MAGIC == head) { + java.io.ByteArrayInputStream bais = null; + java.util.zip.GZIPInputStream gzis = null; + java.io.ByteArrayOutputStream baos = null; + byte[] buffer = new byte[2048]; + int length = 0; + + try { + baos = new java.io.ByteArrayOutputStream(); + bais = new java.io.ByteArrayInputStream(bytes); + gzis = new java.util.zip.GZIPInputStream(bais); + + while ((length = gzis.read(buffer)) >= 0) { + baos.write(buffer, 0, length); + } // end while: reading input + + // No error? Get new bytes. + bytes = baos.toByteArray(); + + } // end try + catch (java.io.IOException e) { + // Just return originally-decoded bytes + } // end catch + finally { + try { + baos.close(); + } + catch (Exception e) { + } + try { + gzis.close(); + } + catch (Exception e) { + } + try { + bais.close(); + } + catch (Exception e) { + } + } // end finally + + } // end if: gzipped + } // end if: bytes.length >= 2 + + return bytes; + } // end decode + + + /** + * Attempts to decode Base64 data and deserialize a Java + * Object within. Returns null if there was an error. + * + * @param encodedObject The Base64 data to decode + * @return The decoded and deserialized object + * @since 1.5 + */ + public static Object decodeToObject(String encodedObject) { + // Decode and gunzip if necessary + byte[] objBytes = decode(encodedObject); + + java.io.ByteArrayInputStream bais = null; + java.io.ObjectInputStream ois = null; + Object obj = null; + + try { + bais = new java.io.ByteArrayInputStream(objBytes); + ois = new java.io.ObjectInputStream(bais); + + obj = ois.readObject(); + } // end try + catch (java.io.IOException e) { + Log.error(e); + obj = null; + } // end catch + catch (java.lang.ClassNotFoundException e) { + Log.error(e); + obj = null; + } // end catch + finally { + try { + bais.close(); + } + catch (Exception e) { + } + try { + ois.close(); + } + catch (Exception e) { + } + } // end finally + + return obj; + } // end decodeObject + + /* ******** I N N E R C L A S S I N P U T S T R E A M ******** */ + + + /** + * A InputStream will read data from another + * {@link java.io.InputStream}, given in the constructor, + * and encode/decode to/from Base64 notation on the fly. + * + * @see Base64 + * @see java.io.FilterInputStream + * @since 1.3 + */ + public static class InputStream extends java.io.FilterInputStream { + private int options; // Options specified + private boolean encode; // Encoding or decoding + private int position; // Current position in the buffer + private byte[] buffer; // Small buffer holding converted data + private int bufferLength; // Length of buffer (3 or 4) + private int numSigBytes; // Number of meaningful bytes in the buffer + private int lineLength; + private boolean breakLines; // Break lines at less than 80 characters + + + /** + * Constructs a InputStream in DECODE mode. + * + * @param in the {@link java.io.InputStream} from which to read data. + * @since 1.3 + */ + public InputStream(java.io.InputStream in) { + this(in, DECODE); + } // end constructor + + + /** + * Constructs a InputStream in + * either ENCODE or DECODE mode. + *

        + * Valid options:

        +         *   ENCODE or DECODE: Encode or Decode as data is read.
        +         *   DONT_BREAK_LINES: don't break lines at 76 characters
        +         *     (only meaningful when encoding)
        +         *     Note: Technically, this makes your encoding non-compliant.
        +         * 
        + *

        + * Example: new Base64.InputStream( in, Base64.DECODE ) + * + * @param in the {@link java.io.InputStream} from which to read data. + * @param options Specified options + * @see Base64#ENCODE + * @see Base64#DECODE + * @see Base64#DONT_BREAK_LINES + * @since 2.0 + */ + public InputStream(java.io.InputStream in, int options) { + super(in); + this.options = options; + this.breakLines = (options & DONT_BREAK_LINES) != DONT_BREAK_LINES; + this.encode = (options & ENCODE) == ENCODE; + this.breakLines = breakLines; + this.encode = encode; + this.bufferLength = encode ? 4 : 3; + this.buffer = new byte[bufferLength]; + this.position = -1; + this.lineLength = 0; + } // end constructor + + /** + * Reads enough of the input stream to convert + * to/from Base64 and returns the next byte. + * + * @return next byte + * @since 1.3 + */ + public int read() throws java.io.IOException { + // Do we need to get data? + if (position < 0) { + if (encode) { + byte[] b3 = new byte[3]; + int numBinaryBytes = 0; + for (int i = 0; i < 3; i++) { + try { + int b = in.read(); + + // If end of stream, b is -1. + if (b >= 0) { + b3[i] = (byte)b; + numBinaryBytes++; + } // end if: not end of stream + + } // end try: read + catch (java.io.IOException e) { + // Only a problem if we got no data at all. + if (i == 0) + throw e; + + } // end catch + } // end for: each needed input byte + + if (numBinaryBytes > 0) { + encode3to4(b3, 0, numBinaryBytes, buffer, 0); + position = 0; + numSigBytes = 4; + } // end if: got data + else { + return -1; + } // end else + } // end if: encoding + + // Else decoding + else { + byte[] b4 = new byte[4]; + int i = 0; + for (i = 0; i < 4; i++) { + // Read four "meaningful" bytes: + int b = 0; + do { + b = in.read(); + } + while (b >= 0 && DECODABET[b & 0x7f] <= WHITE_SPACE_ENC); + + if (b < 0) + break; // Reads a -1 if end of stream + + b4[i] = (byte)b; + } // end for: each needed input byte + + if (i == 4) { + numSigBytes = decode4to3(b4, 0, buffer, 0); + position = 0; + } // end if: got four characters + else if (i == 0) { + return -1; + } // end else if: also padded correctly + else { + // Must have broken out from above. + throw new java.io.IOException("Improperly padded Base64 input."); + } // end + + } // end else: decode + } // end else: get data + + // Got data? + if (position >= 0) { + // End of relevant data? + if (/*!encode &&*/ position >= numSigBytes) + return -1; + + if (encode && breakLines && lineLength >= MAX_LINE_LENGTH) { + lineLength = 0; + return '\n'; + } // end if + else { + lineLength++; // This isn't important when decoding + // but throwing an extra "if" seems + // just as wasteful. + + int b = buffer[position++]; + + if (position >= bufferLength) + position = -1; + + return b & 0xFF; // This is how you "cast" a byte that's + // intended to be unsigned. + } // end else + } // end if: position >= 0 + + // Else error + else { + // When JDK1.4 is more accepted, use an assertion here. + throw new java.io.IOException("Error in Base64 code reading stream."); + } // end else + } // end read + + + /** + * Calls {@link #read} repeatedly until the end of stream + * is reached or len bytes are read. + * Returns number of bytes read into array or -1 if + * end of stream is encountered. + * + * @param dest array to hold values + * @param off offset for array + * @param len max number of bytes to read into array + * @return bytes read into array or -1 if end of stream is encountered. + * @since 1.3 + */ + public int read(byte[] dest, int off, int len) throws java.io.IOException { + int i; + int b; + for (i = 0; i < len; i++) { + b = read(); + + //if( b < 0 && i == 0 ) + // return -1; + + if (b >= 0) + dest[off + i] = (byte)b; + else if (i == 0) + return -1; + else + break; // Out of 'for' loop + } // end for: each byte read + return i; + } // end read + + } // end inner class InputStream + + /* ******** I N N E R C L A S S O U T P U T S T R E A M ******** */ + + + /** + * A outputStream will write data to another + * {@link java.io.OutputStream}, given in the constructor, + * and encode/decode to/from Base64 notation on the fly. + * + * @see Base64 + * @see java.io.FilterOutputStream + * @since 1.3 + */ + public static class OutputStream extends java.io.FilterOutputStream { + private int options; + private boolean encode; + private int position; + private byte[] buffer; + private int bufferLength; + private int lineLength; + private boolean breakLines; + private byte[] b4; // Scratch used in a few places + private boolean suspendEncoding; + + /** + * Constructs a OutputStream in ENCODE mode. + * + * @param out the {@link java.io.OutputStream} to which data will be written. + * @since 1.3 + */ + public OutputStream(java.io.OutputStream out) { + this(out, ENCODE); + } // end constructor + + + /** + * Constructs a OutputStream in + * either ENCODE or DECODE mode. + *

        + * Valid options:

        +         *   ENCODE or DECODE: Encode or Decode as data is read.
        +         *   DONT_BREAK_LINES: don't break lines at 76 characters
        +         *     (only meaningful when encoding)
        +         *     Note: Technically, this makes your encoding non-compliant.
        +         * 
        + *

        + * Example: new Base64.OutputStream( out, Base64.ENCODE ) + * + * @param out the {@link java.io.OutputStream} to which data will be written. + * @param options Specified options. + * @see Base64#ENCODE + * @see Base64#DECODE + * @see Base64#DONT_BREAK_LINES + * @since 1.3 + */ + public OutputStream(java.io.OutputStream out, int options) { + super(out); + this.options = options; + this.breakLines = (options & DONT_BREAK_LINES) != DONT_BREAK_LINES; + this.encode = (options & ENCODE) == ENCODE; + this.bufferLength = encode ? 3 : 4; + this.buffer = new byte[bufferLength]; + this.position = 0; + this.lineLength = 0; + this.suspendEncoding = false; + this.b4 = new byte[4]; + } // end constructor + + + /** + * Writes the byte to the output stream after + * converting to/from Base64 notation. + * When encoding, bytes are buffered three + * at a time before the output stream actually + * gets a write() call. + * When decoding, bytes are buffered four + * at a time. + * + * @param theByte the byte to write + * @since 1.3 + */ + public void write(int theByte) throws java.io.IOException { + // Encoding suspended? + if (suspendEncoding) { + super.out.write(theByte); + return; + } // end if: supsended + + // Encode? + if (encode) { + buffer[position++] = (byte)theByte; + if (position >= bufferLength) // Enough to encode. + { + out.write(encode3to4(b4, buffer, bufferLength)); + + lineLength += 4; + if (breakLines && lineLength >= MAX_LINE_LENGTH) { + out.write(NEW_LINE); + lineLength = 0; + } // end if: end of line + + position = 0; + } // end if: enough to output + } // end if: encoding + + // Else, Decoding + else { + // Meaningful Base64 character? + if (DECODABET[theByte & 0x7f] > WHITE_SPACE_ENC) { + buffer[position++] = (byte)theByte; + if (position >= bufferLength) // Enough to output. + { + int len = Base64.decode4to3(buffer, 0, b4, 0); + out.write(b4, 0, len); + //out.write( Base64.decode4to3( buffer ) ); + position = 0; + } // end if: enough to output + } // end if: meaningful base64 character + else if (DECODABET[theByte & 0x7f] != WHITE_SPACE_ENC) { + throw new java.io.IOException("Invalid character in Base64 data."); + } // end else: not white space either + } // end else: decoding + } // end write + + + /** + * Calls {@link #write} repeatedly until len + * bytes are written. + * + * @param theBytes array from which to read bytes + * @param off offset for array + * @param len max number of bytes to read into array + * @since 1.3 + */ + public void write(byte[] theBytes, int off, int len) throws java.io.IOException { + // Encoding suspended? + if (suspendEncoding) { + super.out.write(theBytes, off, len); + return; + } // end if: supsended + + for (int i = 0; i < len; i++) { + write(theBytes[off + i]); + } // end for: each byte written + + } // end write + + + /** + * Method added by PHIL. [Thanks, PHIL. -Rob] + * This pads the buffer without closing the stream. + */ + public void flushBase64() throws java.io.IOException { + if (position > 0) { + if (encode) { + out.write(encode3to4(b4, buffer, position)); + position = 0; + } // end if: encoding + else { + throw new java.io.IOException("Base64 input not properly padded."); + } // end else: decoding + } // end if: buffer partially full + + } // end flush + + + /** + * Flushes and closes (I think, in the superclass) the stream. + * + * @since 1.3 + */ + public void close() throws java.io.IOException { + // 1. Ensure that pending characters are written + flushBase64(); + + // 2. Actually close the stream + // Base class both flushes and closes. + super.close(); + + buffer = null; + out = null; + } // end close + + + /** + * Suspends encoding of the stream. + * May be helpful if you need to embed a piece of + * base640-encoded data in a stream. + * + * @since 1.5.1 + */ + public void suspendEncoding() throws java.io.IOException { + flushBase64(); + this.suspendEncoding = true; + } // end suspendEncoding + + + /** + * Resumes encoding of the stream. + * May be helpful if you need to embed a piece of + * base640-encoded data in a stream. + * + * @since 1.5.1 + */ + public void resumeEncoding() { + this.suspendEncoding = false; + } // end resumeEncoding + + + } // end inner class OutputStream + + +} // end class Base64 diff --git a/src/java/org/jivesoftware/spark/util/BrowserLauncher.java b/src/java/org/jivesoftware/spark/util/BrowserLauncher.java new file mode 100644 index 00000000..2057aed7 --- /dev/null +++ b/src/java/org/jivesoftware/spark/util/BrowserLauncher.java @@ -0,0 +1,549 @@ +package org.jivesoftware.spark.util; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +/** + * BrowserLauncher is a class that provides one static method, openURL, which opens the default + * web browser for the current user of the system to the given URL. It may support other + * protocols depending on the system -- mailto, ftp, etc. -- but that has not been rigorously + * tested and is not guaranteed to work. + *

        + * Yes, this is platform-specific code, and yes, it may rely on classes on certain platforms + * that are not part of the standard JDK. What we're trying to do, though, is to take something + * that's frequently desirable but inherently platform-specific -- opening a default browser -- + * and allow programmers (you, for example) to do so without worrying about dropping into native + * code or doing anything else similarly evil. + *

        + * Anyway, this code is completely in Java and will run on all JDK 1.1-compliant systems without + * modification or a need for additional libraries. All classes that are required on certain + * platforms to allow this to run are dynamically loaded at runtime via reflection and, if not + * found, will not cause this to do anything other than returning an error when opening the + * browser. + *

        + * There are certain system requirements for this class, as it's running through Runtime.exec(), + * which is Java's way of making a native system call. Currently, this requires that a Macintosh + * have a Finder which supports the GURL event, which is true for Mac OS 8.0 and 8.1 systems that + * have the Internet Scripting AppleScript dictionary installed in the Scripting Additions folder + * in the Extensions folder (which is installed by default as far as I know under Mac OS 8.0 and + * 8.1), and for all Mac OS 8.5 and later systems. On Windows, it only runs under Win32 systems + * (Windows 95, 98, and NT 4.0, as well as later versions of all) that include the + * FileProtocolHandler method in url.dll, which has been the case for all systems that I have + * tested. On other systems, this drops back from the inherently platform-sensitive concept of + * a default browser and simply attempts to launch Netscape via a shell command. + *

        + * This code is Copyright 1999 by Eric Albert (ejalbert@cs.stanford.edu) and may be redistributed + * or modified in any form without restrictions as long as the portion of this comment from this + * paragraph through the end of the comment is not removed. The author requests that he be + * notified of any application, applet, or other binary that makes use of this code, but that's + * more out of curiosity than anything and is not required. This software includes no warranty. + *

        + * Credits: + *
        Steven Spencer, JavaWorld magazine (Java Tip 66) + *
        Ron B. Yeh, ZeroG + *
        Ben Engber, The New York Times + * + * @author Eric Albert (ejalbert@cs.stanford.edu) + * @author loki der quaeler (modified for osX before adding to weltschmerz frameworks) + * @version 1.1 (Released July 3, 1999) + */ +public class BrowserLauncher { + + static protected final String OS_X_URL_FILE_NAME = "/tmp/bla_wdml.url"; + + /** + * The Java virtual machine that we are running on. Actually, in most cases we only care + * about the operating system, but some operating systems require us to switch on the VM. + */ + private static int jvm; + + /** + * The browser for the system + */ + private static Object browser; + + /** + * Caches whether any classes, methods, and fields that are not part of the JDK and need to + * be dynamically loaded at runtime loaded successfully. + *

        + * Note that if this is false, openURL() will always return an + * IOException. + */ + private static boolean loadedWithoutErrors; + + /** + * The com.apple.mrj.MRJFileUtils class + */ + private static Class mrjFileUtilsClass; + + /** + * The com.apple.mrj.MRJOSType class + */ + private static Class mrjOSTypeClass; + + /** + * The com.apple.MacOS.MacOSError class + */ + private static Class macOSErrorClass; + + /** + * The com.apple.MacOS.AEDesc class + */ + private static Class aeDescClass; + + /** + * The (int) method of com.apple.MacOS.AETarget + */ + private static Constructor aeTargetConstructor; + + /** + * The (int, int, int) method of com.apple.MacOS.AppleEvent + */ + private static Constructor appleEventConstructor; + + /** + * The (String) method of com.apple.MacOS.AEDesc + */ + private static Constructor aeDescConstructor; + + /** + * The findFolder method of com.apple.mrj.MRJFileUtils + */ + private static Method findFolder; + + /** + * The getFileType method of com.apple.mrj.MRJOSType + */ + private static Method getFileType; + + /** + * The makeOSType method of com.apple.MacOS.OSUtils + */ + private static Method makeOSType; + + /** + * The putParameter method of com.apple.MacOS.AppleEvent + */ + private static Method putParameter; + + /** + * The sendNoReply method of com.apple.MacOS.AppleEvent + */ + private static Method sendNoReply; + + /** + * Actually an MRJOSType pointing to the System Folder on a Macintosh + */ + private static Object kSystemFolderType; + + /** + * The keyDirectObject AppleEvent parameter type + */ + private static Integer keyDirectObject; + + /** + * The kAutoGenerateReturnID AppleEvent code + */ + private static Integer kAutoGenerateReturnID; + + /** + * The kAnyTransactionID AppleEvent code + */ + private static Integer kAnyTransactionID; + + /** + * JVM constant for MRJ 2.0 + */ + private static final int MRJ_2_0 = 0; + + /** + * JVM constant for MRJ 2.1 or later + */ + private static final int MRJ_2_1 = 1; + + /** + * JVM constant for any Win32 JVM + */ + private static final int WINDOWS = 2; + + /** + * even though OS X is a unix variant, would like to differentiate + */ + static private final int OS_X = 3; + + /** + * JVM constant for any other platform + */ + private static final int OTHER = -1; + + /** + * The file type of the Finder on a Macintosh. Hardcoding "Finder" would keep non-U.S. English + * systems from working properly. + */ + private static final String FINDER_TYPE = "FNDR"; + + /** + * The creator code of the Finder on a Macintosh, which is needed to send AppleEvents to the + * application. + */ + private static final String FINDER_CREATOR = "MACS"; + + /** + * The name for the AppleEvent type corresponding to a GetURL event. + */ + private static final String GURL_EVENT = "GURL"; + + /** + * The first parameter that needs to be passed into Runtime.exec() to open the default web + * browser on Windows. + */ + private static final String WINDOWS_PARAMETER = "url.dll,FileProtocolHandler"; + + /** + * The shell parameters for Netscape that opens a given URL in an already-open copy of Netscape + * on many command-line systems. + */ + private static final String NETSCAPE_OPEN_PARAMETER_START = " -remote openURL("; + private static final String NETSCAPE_OPEN_PARAMETER_END = ")"; + + /** + * The message from any exception thrown throughout the initialization process. + */ + private static String errorMessage; + + /** + * An initialization block that determines the operating system and loads the necessary + * runtime data. + */ + static { + loadedWithoutErrors = true; + String osName = System.getProperty("os.name"); + if ("Mac OS".equals(osName)) { + String mrjVersion = System.getProperty("mrj.version"); + String majorMRJVersion = mrjVersion.substring(0, 3); + try { + double version = Double.valueOf(majorMRJVersion).intValue(); + if (version == 2) { + jvm = MRJ_2_0; + } + else if (version >= 2.1) { + // For the time being, assume that all post-2.0 versions of MRJ work the same + jvm = MRJ_2_1; + } + else { + loadedWithoutErrors = false; + errorMessage = "Unsupported MRJ version: " + version; + } + } + catch (NumberFormatException nfe) { + loadedWithoutErrors = false; + errorMessage = "Invalid MRJ version: " + mrjVersion; + } + } + else if (osName.startsWith("Windows")) { + jvm = WINDOWS; + } + else if (osName.startsWith("Mac OS X")) { + jvm = OS_X; + } + else { + jvm = OTHER; + } + + if (loadedWithoutErrors) { // if we haven't hit any errors yet + loadedWithoutErrors = loadClasses(); + } + } + + /** + * This class should be never be instantiated; this just ensures so. + */ + private BrowserLauncher() { + } + + /** + * Called by a static initializer to load any classes, fields, and methods required at runtime + * to locate the user's web browser. + * + * @return true if all intialization succeeded + * false if any portion of the initialization failed + */ + private static boolean loadClasses() { + switch (jvm) { + case MRJ_2_0: + try { + Class aeTargetClass = Class.forName("com.apple.MacOS.AETarget"); + macOSErrorClass = Class.forName("com.apple.MacOS.MacOSError"); + Class osUtilsClass = Class.forName("com.apple.MacOS.OSUtils"); + Class appleEventClass = Class.forName("com.apple.MacOS.AppleEvent"); + Class aeClass = Class.forName("com.apple.MacOS.ae"); + aeDescClass = Class.forName("com.apple.MacOS.AEDesc"); + + aeTargetConstructor = aeTargetClass.getDeclaredConstructor(new Class[]{int.class}); + appleEventConstructor = appleEventClass.getDeclaredConstructor(new Class[]{int.class, int.class, aeTargetClass, int.class, int.class}); + aeDescConstructor = aeDescClass.getDeclaredConstructor(new Class[]{String.class}); + + makeOSType = osUtilsClass.getDeclaredMethod("makeOSType", new Class[]{String.class}); + putParameter = appleEventClass.getDeclaredMethod("putParameter", new Class[]{int.class, aeDescClass}); + sendNoReply = appleEventClass.getDeclaredMethod("sendNoReply", new Class[]{}); + + Field keyDirectObjectField = aeClass.getDeclaredField("keyDirectObject"); + keyDirectObject = (Integer)keyDirectObjectField.get(null); + Field autoGenerateReturnIDField = appleEventClass.getDeclaredField("kAutoGenerateReturnID"); + kAutoGenerateReturnID = (Integer)autoGenerateReturnIDField.get(null); + Field anyTransactionIDField = appleEventClass.getDeclaredField("kAnyTransactionID"); + kAnyTransactionID = (Integer)anyTransactionIDField.get(null); + } + catch (ClassNotFoundException cnfe) { + errorMessage = cnfe.getMessage(); + return false; + } + catch (NoSuchMethodException nsme) { + errorMessage = nsme.getMessage(); + return false; + } + catch (NoSuchFieldException nsfe) { + errorMessage = nsfe.getMessage(); + return false; + } + catch (IllegalAccessException iae) { + errorMessage = iae.getMessage(); + return false; + } + break; + case MRJ_2_1: + try { + mrjFileUtilsClass = Class.forName("com.apple.mrj.MRJFileUtils"); + mrjOSTypeClass = Class.forName("com.apple.mrj.MRJOSType"); + Field systemFolderField = mrjFileUtilsClass.getDeclaredField("kSystemFolderType"); + kSystemFolderType = systemFolderField.get(null); + findFolder = mrjFileUtilsClass.getDeclaredMethod("findFolder", new Class[]{mrjOSTypeClass}); + getFileType = mrjFileUtilsClass.getDeclaredMethod("getFileType", new Class[]{File.class}); + } + catch (ClassNotFoundException cnfe) { + errorMessage = cnfe.getMessage(); + return false; + } + catch (NoSuchFieldException nsfe) { + errorMessage = nsfe.getMessage(); + return false; + } + catch (NoSuchMethodException nsme) { + errorMessage = nsme.getMessage(); + return false; + } + catch (SecurityException se) { + errorMessage = se.getMessage(); + return false; + } + catch (IllegalAccessException iae) { + errorMessage = iae.getMessage(); + return false; + } + break; + } + return true; + } + + /** + * Attempts to locate the default web browser on the local system. Caches results so it + * only locates the browser once for each use of this class per JVM instance. + * + * @return The browser for the system. Note that this may not be what you would consider + * to be a standard web browser; instead, it's the application that gets called to + * open the default web browser. In some cases, this will be a non-String object + * that provides the means of calling the default browser. + */ + private static Object locateBrowser() { + if (browser != null) { + return browser; + } + switch (jvm) { + case MRJ_2_0: + try { + Integer finderCreatorCode = (Integer)makeOSType.invoke(null, new Object[]{FINDER_CREATOR}); + Object aeTarget = aeTargetConstructor.newInstance(new Object[]{finderCreatorCode}); + Integer gurlType = (Integer)makeOSType.invoke(null, new Object[]{GURL_EVENT}); + Object appleEvent = appleEventConstructor.newInstance(new Object[]{gurlType, gurlType, aeTarget, kAutoGenerateReturnID, kAnyTransactionID}); + // Don't set browser = appleEvent because then the next time we call + // locateBrowser(), we'll get the same AppleEvent, to which we'll already have + // added the relevant parameter. Instead, regenerate the AppleEvent every time. + // There's probably a way to do this better; if any has any ideas, please let + // me know. + return appleEvent; + } + catch (IllegalAccessException iae) { + browser = null; + errorMessage = iae.getMessage(); + return browser; + } + catch (InstantiationException ie) { + browser = null; + errorMessage = ie.getMessage(); + return browser; + } + catch (InvocationTargetException ite) { + browser = null; + errorMessage = ite.getMessage(); + return browser; + } + case MRJ_2_1: + File systemFolder; + try { + systemFolder = (File)findFolder.invoke(null, new Object[]{kSystemFolderType}); + } + catch (IllegalArgumentException iare) { + browser = null; + errorMessage = iare.getMessage(); + return browser; + } + catch (IllegalAccessException iae) { + browser = null; + errorMessage = iae.getMessage(); + return browser; + } + catch (InvocationTargetException ite) { + browser = null; + errorMessage = ite.getTargetException().getClass() + ": " + ite.getTargetException().getMessage(); + return browser; + } + String[] systemFolderFiles = systemFolder.list(); + // Avoid a FilenameFilter because that can't be stopped mid-list + for (int i = 0; i < systemFolderFiles.length; i++) { + try { + File file = new File(systemFolder, systemFolderFiles[i]); + if (!file.isFile()) { + continue; + } + Object fileType = getFileType.invoke(null, new Object[]{file}); + if (FINDER_TYPE.equals(fileType.toString())) { + browser = file.toString(); // Actually the Finder, but that's OK + return browser; + } + } + catch (IllegalArgumentException iare) { + browser = browser; + errorMessage = iare.getMessage(); + return null; + } + catch (IllegalAccessException iae) { + browser = null; + errorMessage = iae.getMessage(); + return browser; + } + catch (InvocationTargetException ite) { + browser = null; + errorMessage = ite.getTargetException().getClass() + ": " + ite.getTargetException().getMessage(); + return browser; + } + } + browser = null; + break; + case WINDOWS: + browser = "rundll32.exe"; + break; + case OTHER: + default: + browser = "netscape"; + break; + } + return browser; + } + + /** + * Attempts to open the default web browser to the given URL. + * + * @param url The URL to open + * @throws IOException If the web browser could not be located or does not run + */ + public static void openURL(String url) throws IOException { + if (!loadedWithoutErrors) { + throw new IOException("Exception in finding browser: " + errorMessage); + } + Object browser = locateBrowser(); + if (browser == null) { + throw new IOException("Unable to locate browser: " + errorMessage); + } + switch (jvm) { + case MRJ_2_0: + Object aeDesc = null; + try { + aeDesc = aeDescConstructor.newInstance(new Object[]{url}); + putParameter.invoke(browser, new Object[]{keyDirectObject, aeDesc}); + sendNoReply.invoke(browser, new Object[]{}); + } + catch (InvocationTargetException ite) { + throw new IOException("InvocationTargetException while creating AEDesc: " + ite.getMessage()); + } + catch (IllegalAccessException iae) { + throw new IOException("IllegalAccessException while building AppleEvent: " + iae.getMessage()); + } + catch (InstantiationException ie) { + throw new IOException("InstantiationException while creating AEDesc: " + ie.getMessage()); + } + finally { + aeDesc = null; // Encourage it to get disposed if it was created + browser = null; // Ditto + } + break; + case MRJ_2_1: + Runtime.getRuntime().exec(new String[]{(String)browser, url}); + break; + case WINDOWS: + Runtime.getRuntime().exec(new String[]{(String)browser, WINDOWS_PARAMETER, url}); + break; + case OS_X: + BrowserLauncher.launchForOSX(url); + break; + case OTHER: + // Assume that we're on Unix and that Netscape is installed + + // First, attempt to open the URL in a currently running session of Netscape + Process process = Runtime.getRuntime().exec((String)browser + + NETSCAPE_OPEN_PARAMETER_START + url + + NETSCAPE_OPEN_PARAMETER_END); + try { + int exitCode = process.waitFor(); + if (exitCode != 0) { // if Netscape was not open + Runtime.getRuntime().exec(new String[]{(String)browser, url}); + } + } + catch (InterruptedException ie) { + throw new IOException("InterruptedException while launching browser: " + ie.getMessage()); + } + break; + default: + // This should never occur, but if it does, we'll try the simplest thing possible + Runtime.getRuntime().exec(new String[]{(String)browser, url}); + break; + } + } + + /** + * THERE HAS GOT TO BE A BETTER WAY TO DO THIS -- email out to mailing + * lists; waiting for reply. + */ + static protected void launchForOSX(String url) + throws IOException { + String run = "open " + url; + Process process = Runtime.getRuntime().exec(run); + + try { + int exitCode = process.waitFor(); + + if (exitCode != 0) { // failed :-( + System.out.println("browser launch failed - exit code: " + + exitCode); + } + } + catch (InterruptedException ie) { + throw new IOException("Exception while launching browser: " + + ie.getMessage()); + } + } + +} diff --git a/src/java/org/jivesoftware/spark/util/ByteFormat.java b/src/java/org/jivesoftware/spark/util/ByteFormat.java new file mode 100644 index 00000000..ffa680eb --- /dev/null +++ b/src/java/org/jivesoftware/spark/util/ByteFormat.java @@ -0,0 +1,86 @@ +/** + * $RCSfile: ByteFormat.java,v $ + * $Revision: 1.6 $ + * $Date: 2005/04/12 19:17:41 $ + * + * Copyright (C) 1999-2002 Jive Software. All rights reserved. + * + * This software is the proprietary information of Jive Software. Use is subject to license terms. + */ + +package org.jivesoftware.spark.util; + +import java.text.DecimalFormat; +import java.text.FieldPosition; +import java.text.Format; +import java.text.ParsePosition; + +/** + * A formatter for formatting byte sizes. For example, formatting 12345 byes results in + * "12.1 K" and 1234567 results in "1.18 MB". + * + * @author Bill Lynch + */ +public class ByteFormat extends Format { + + public ByteFormat() { + } + + // Implemented from the Format class + + /** + * Formats a long which represent a number of bytes. + */ + public String format(long bytes) { + return format(new Long(bytes)); + } + + /** + * Formats a long which represent a number of kilobytes. + */ + public String formatKB(long kilobytes) { + return format(new Long(kilobytes * 1024)); + } + + /** + * Format the given object (must be a Long). + * + * @param obj assumed to be the number of bytes as a Long. + * @param buf the StringBuffer to append to. + * @param pos + * @return A formatted string representing the given bytes in more human-readable form. + */ + public StringBuffer format(Object obj, StringBuffer buf, FieldPosition pos) { + if (obj instanceof Long) { + long numBytes = ((Long)obj).longValue(); + if (numBytes < 1024) { + DecimalFormat formatter = new DecimalFormat("#,##0"); + buf.append(formatter.format((double)numBytes)).append(" bytes"); + } + else if (numBytes < 1024 * 1024) { + DecimalFormat formatter = new DecimalFormat("#,##0.0"); + buf.append(formatter.format((double)numBytes / 1024.0)).append(" K"); + } + else if (numBytes < 1024 * 1024 * 1024) { + DecimalFormat formatter = new DecimalFormat("#,##0.0"); + buf.append(formatter.format((double)numBytes / (1024.0 * 1024.0))).append(" MB"); + } + else { + DecimalFormat formatter = new DecimalFormat("#,##0.0"); + buf.append(formatter.format((double)numBytes / (1024.0 * 1024.0 * 1024.0))).append(" GB"); + } + } + return buf; + } + + /** + * In this implementation, returns null always. + * + * @param source + * @param pos + * @return returns null in this implementation. + */ + public Object parseObject(String source, ParsePosition pos) { + return null; + } +} diff --git a/src/java/org/jivesoftware/spark/util/ColorUtil.java b/src/java/org/jivesoftware/spark/util/ColorUtil.java new file mode 100644 index 00000000..090bb253 --- /dev/null +++ b/src/java/org/jivesoftware/spark/util/ColorUtil.java @@ -0,0 +1,232 @@ +/** + * $Revision$ + * $Date$ + * + * Copyright (C) 1999-2005 Jive Software. All rights reserved. + * This software is the proprietary information of Jive Software. Use is subject to license terms. + */ + +package org.jivesoftware.spark.util; + +/* + * This code is free software; you can redistribute it 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 code is distributed in the hope that 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. + */ + + +import java.awt.Color; + + +/** + * Common color utilities. + * + * @author Jacob Dreyer + */ +public class ColorUtil { + /** + * Blend two colors. + * + * @param color1 First color to blend. + * @param color2 Second color to blend. + * @param ratio Blend ratio. 0.5 will give even blend, 1.0 will return + * color1, 0.0 will return color2 and so on. + * @return Blended color. + */ + public static Color blend(Color color1, Color color2, double ratio) { + float r = (float)ratio; + float ir = (float)1.0 - r; + + float rgb1[] = new float[3]; + float rgb2[] = new float[3]; + + color1.getColorComponents(rgb1); + color2.getColorComponents(rgb2); + + Color color = new Color(rgb1[0] * r + rgb2[0] * ir, + rgb1[1] * r + rgb2[1] * ir, + rgb1[2] * r + rgb2[2] * ir); + + return color; + } + + + /** + * Make an even blend between two colors. + * + * @param color1 First color to blend. + * @param color2 Second color to blend. + * @return Blended color. + */ + public static Color blend(Color color1, Color color2) { + return ColorUtil.blend(color1, color2, 0.5); + } + + + /** + * Make a color darker. + * + * @param color Color to make darker. + * @param fraction Darkness fraction. + * @return Darker color. + */ + public static Color darker(Color color, double fraction) { + int red = (int)Math.round(color.getRed() * (1.0 - fraction)); + int green = (int)Math.round(color.getGreen() * (1.0 - fraction)); + int blue = (int)Math.round(color.getBlue() * (1.0 - fraction)); + + if (red < 0) red = 0; + else if (red > 255) red = 255; + if (green < 0) green = 0; + else if (green > 255) green = 255; + if (blue < 0) blue = 0; + else if (blue > 255) blue = 255; + + int alpha = color.getAlpha(); + + return new Color(red, green, blue, alpha); + } + + + /** + * Make a color lighter. + * + * @param color Color to make lighter. + * @param fraction Darkness fraction. + * @return Lighter color. + */ + public static Color lighter(Color color, double fraction) { + int red = (int)Math.round(color.getRed() * (1.0 + fraction)); + int green = (int)Math.round(color.getGreen() * (1.0 + fraction)); + int blue = (int)Math.round(color.getBlue() * (1.0 + fraction)); + + if (red < 0) red = 0; + else if (red > 255) red = 255; + if (green < 0) green = 0; + else if (green > 255) green = 255; + if (blue < 0) blue = 0; + else if (blue > 255) blue = 255; + + int alpha = color.getAlpha(); + + return new Color(red, green, blue, alpha); + } + + + /** + * Return the hex name of a specified color. + * + * @param color Color to get hex name of. + * @return Hex name of color: "rrggbb". + */ + public static String getHexName(Color color) { + int r = color.getRed(); + int g = color.getGreen(); + int b = color.getBlue(); + + String rHex = Integer.toString(r, 16); + String gHex = Integer.toString(g, 16); + String bHex = Integer.toString(b, 16); + + return (rHex.length() == 2 ? "" + rHex : "0" + rHex) + + (gHex.length() == 2 ? "" + gHex : "0" + gHex) + + (bHex.length() == 2 ? "" + bHex : "0" + bHex); + } + + + /** + * Return the "distance" between two colors. The rgb entries are taken + * to be coordinates in a 3D space [0.0-1.0], and this method returnes + * the distance between the coordinates for the first and second color. + * + * @param r1, g1, b1 First color. + * @param r2, g2, b2 Second color. + * @return Distance bwetween colors. + */ + public static double colorDistance(double r1, double g1, double b1, + double r2, double g2, double b2) { + double a = r2 - r1; + double b = g2 - g1; + double c = b2 - b1; + + return Math.sqrt(a * a + b * b + c * c); + } + + + /** + * Return the "distance" between two colors. + * + * @param color1 First color [r,g,b]. + * @param color2 Second color [r,g,b]. + * @return Distance bwetween colors. + */ + public static double colorDistance(double[] color1, double[] color2) { + return ColorUtil.colorDistance(color1[0], color1[1], color1[2], + color2[0], color2[1], color2[2]); + } + + + /** + * Return the "distance" between two colors. + * + * @param color1 First color. + * @param color2 Second color. + * @return Distance between colors. + */ + public static double colorDistance(Color color1, Color color2) { + float rgb1[] = new float[3]; + float rgb2[] = new float[3]; + + color1.getColorComponents(rgb1); + color2.getColorComponents(rgb2); + + return ColorUtil.colorDistance(rgb1[0], rgb1[1], rgb1[2], + rgb2[0], rgb2[1], rgb2[2]); + } + + + /** + * Check if a color is more dark than light. Useful if an entity of + * this color is to be labeled: Use white label on a "dark" color and + * black label on a "light" color. + * + * @param r,g,b Color to check. + * @return True if this is a "dark" color, false otherwise. + */ + public static boolean isDark(double r, double g, double b) { + // Measure distance to white and black respectively + double dWhite = ColorUtil.colorDistance(r, g, b, 1.0, 1.0, 1.0); + double dBlack = ColorUtil.colorDistance(r, g, b, 0.0, 0.0, 0.0); + + return dBlack < dWhite; + } + + + /** + * Check if a color is more dark than light. Useful if an entity of + * this color is to be labeled: Use white label on a "dark" color and + * black label on a "light" color. + * + * @param color Color to check. + * @return True if this is a "dark" color, false otherwise. + */ + public static boolean isDark(Color color) { + float r = color.getRed() / 255.0f; + float g = color.getGreen() / 255.0f; + float b = color.getBlue() / 255.0f; + + return isDark(r, g, b); + } +} + diff --git a/src/java/org/jivesoftware/spark/util/Encryptor.java b/src/java/org/jivesoftware/spark/util/Encryptor.java new file mode 100644 index 00000000..c400beb6 --- /dev/null +++ b/src/java/org/jivesoftware/spark/util/Encryptor.java @@ -0,0 +1,88 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.util; + +import org.jivesoftware.smack.util.StringUtils; +import org.jivesoftware.spark.util.log.Log; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; + +import java.io.UnsupportedEncodingException; + +/** + * Encrypts and Decrypts text based on DESede keys. + * + * @author Derek DeMoro + */ +public class Encryptor { + + private static String secretKey = "ugfpV1dMC5jyJtqwVAfTpHkxqJ0+E0ae"; + + private static Cipher ecipher; + private static Cipher dcipher; + + static { + try { + SecretKey key = decodeKey(); + ecipher = Cipher.getInstance("DESede"); + dcipher = Cipher.getInstance("DESede"); + ecipher.init(Cipher.ENCRYPT_MODE, key); + dcipher.init(Cipher.DECRYPT_MODE, key); + } + catch (Exception e) { + Log.error(e); + } + } + + public static String encrypt(String string) throws Exception { + byte[] utf8 = string.getBytes("UTF8"); + + // Encrypt + byte[] enc = ecipher.doFinal(utf8); + return org.jivesoftware.smack.util.StringUtils.encodeBase64(enc); + } + + public static String decrypt(String string) { + byte[] dec = StringUtils.decodeBase64(string); + + try { + // Decrypt + byte[] utf8 = dcipher.doFinal(dec); + + // Decode using utf-8 + return new String(utf8, "UTF8"); + } + catch (IllegalBlockSizeException e) { + Log.error(e); + } + catch (BadPaddingException e) { + Log.error(e); + } + catch (UnsupportedEncodingException e) { + Log.error(e); + } + return null; + } + + private static SecretKey decodeKey() throws Exception { + byte[] bytes = StringUtils.decodeBase64(secretKey); + return new SecretKeySpec(bytes, "DESede"); + } + + public static void main(String[] args) throws Exception { + String encoded = encrypt("How are you today"); + System.out.println(decrypt(encoded)); + } +} diff --git a/src/java/org/jivesoftware/spark/util/GraphicUtils.java b/src/java/org/jivesoftware/spark/util/GraphicUtils.java new file mode 100644 index 00000000..bed986d8 --- /dev/null +++ b/src/java/org/jivesoftware/spark/util/GraphicUtils.java @@ -0,0 +1,618 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.util; + +import org.jivesoftware.resource.SparkRes; +import org.jivesoftware.spark.util.log.Log; + +import javax.imageio.ImageIO; +import javax.swing.Icon; +import javax.swing.ImageIcon; +import javax.swing.JComponent; +import javax.swing.JFileChooser; +import javax.swing.JLabel; +import javax.swing.JPopupMenu; + +import java.awt.Color; +import java.awt.Component; +import java.awt.Container; +import java.awt.Cursor; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.GraphicsConfiguration; +import java.awt.GraphicsDevice; +import java.awt.GraphicsEnvironment; +import java.awt.Image; +import java.awt.Insets; +import java.awt.Label; +import java.awt.MediaTracker; +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.Toolkit; +import java.awt.Window; +import java.awt.event.MouseEvent; +import java.awt.image.BufferedImage; +import java.awt.image.ConvolveOp; +import java.awt.image.Kernel; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.util.Hashtable; +import java.util.StringTokenizer; + +/** + * GraphicsUtils class defines common user-interface related utility + * functions. + */ +public final class GraphicUtils { + private static final Insets HIGHLIGHT_INSETS = new Insets(1, 1, 1, 1); + public static final Color SELECTION_COLOR = new java.awt.Color(166, 202, 240); + public static final Color TOOLTIP_COLOR = new java.awt.Color(166, 202, 240); + + protected final static Component component = new Component() { + }; + protected final static MediaTracker tracker = new MediaTracker(component); + + private static Hashtable imageCache = new Hashtable(); + + /** + * The default Hand cursor. + */ + public static final Cursor HAND_CURSOR = new Cursor(Cursor.HAND_CURSOR); + + /** + * The default Text Cursor. + */ + public static final Cursor DEFAULT_CURSOR = new Cursor(Cursor.DEFAULT_CURSOR); + + private GraphicUtils() { + } + + + /** + * Sets the location of the specified window so that it is centered on screen. + * + * @param window The window to be centered. + */ + public static void centerWindowOnScreen(Window window) { + final Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); + final Dimension size = window.getSize(); + + if (size.height > screenSize.height) { + size.height = screenSize.height; + } + + if (size.width > screenSize.width) { + size.width = screenSize.width; + } + + window.setLocation((screenSize.width - size.width) / 2, + (screenSize.height - size.height) / 2); + } + + /** + * Draws a single-line highlight border rectangle. + * + * @param g The graphics context to use for drawing. + * @param x The left edge of the border. + * @param y The top edge of the border. + * @param width The width of the border. + * @param height The height of the border. + * @param raised true if the border is to be drawn raised, + * false if lowered. + * @param shadow The shadow color for the border. + * @param highlight The highlight color for the border. + * @see javax.swing.border.EtchedBorder + */ + public static void drawHighlightBorder(Graphics g, int x, int y, + int width, int height, boolean raised, + Color shadow, Color highlight) { + final Color oldColor = g.getColor(); + g.translate(x, y); + + g.setColor(raised ? highlight : shadow); + g.drawLine(0, 0, width - 2, 0); + g.drawLine(0, 1, 0, height - 2); + + g.setColor(raised ? shadow : highlight); + g.drawLine(width - 1, 0, width - 1, height - 1); + g.drawLine(0, height - 1, width - 2, height - 1); + + g.translate(-x, -y); + g.setColor(oldColor); + } + + /** + * Return the amount of space taken up by a highlight border drawn by + * drawHighlightBorder(). + * + * @return The Insets needed for the highlight border. + * @see #drawHighlightBorder + */ + public static Insets getHighlightBorderInsets() { + return HIGHLIGHT_INSETS; + } + + public static ImageIcon createImageIcon(Image image) { + if (image == null) { + return null; + } + + synchronized (tracker) { + tracker.addImage(image, 0); + try { + tracker.waitForID(0, 0); + } + catch (InterruptedException e) { + System.out.println("INTERRUPTED while loading Image"); + } + tracker.removeImage(image, 0); + } + + return new ImageIcon(image); + } + + /** + * Returns a point where the given popup menu should be shown. The + * point is calculated by adjusting the X and Y coordinates from the + * given mouse event so that the popup menu will not be clipped by + * the screen boundaries. + * + * @param popup the popup menu + * @param event the mouse event + * @return the point where the popup menu should be shown + */ + public static Point getPopupMenuShowPoint(JPopupMenu popup, MouseEvent event) { + Component source = (Component)event.getSource(); + Point topLeftSource = source.getLocationOnScreen(); + Point ptRet = getPopupMenuShowPoint(popup, + topLeftSource.x + event.getX(), + topLeftSource.y + event.getY()); + ptRet.translate(-topLeftSource.x, -topLeftSource.y); + return ptRet; + } + + /** + * Returns a point where the given popup menu should be shown. The + * point is calculated by adjusting the X and Y coordinates so that + * the popup menu will not be clipped by the screen boundaries. + * + * @param popup the popup menu + * @param x the x position in screen coordinate + * @param y the y position in screen coordinates + * @return the point where the popup menu should be shown in screen + * coordinates + */ + public static Point getPopupMenuShowPoint(JPopupMenu popup, int x, int y) { + Dimension sizeMenu = popup.getPreferredSize(); + Point bottomRightMenu = new Point(x + sizeMenu.width, y + sizeMenu.height); + + Rectangle[] screensBounds = getScreenBounds(); + int n = screensBounds.length; + for (int i = 0; i < n; i++) { + Rectangle screenBounds = screensBounds[i]; + if (screenBounds.x <= x && x <= (screenBounds.x + screenBounds.width)) { + Dimension sizeScreen = screenBounds.getSize(); + sizeScreen.height -= 32; // Hack to help prevent menu being clipped by Windows/Linux/Solaris Taskbar. + + int xOffset = 0; + if (bottomRightMenu.x > (screenBounds.x + sizeScreen.width)) + xOffset = -sizeMenu.width; + + int yOffset = 0; + if (bottomRightMenu.y > (screenBounds.y + sizeScreen.height)) + yOffset = sizeScreen.height - bottomRightMenu.y; + + return new Point(x + xOffset, y + yOffset); + } + } + + return new Point(x, y); // ? that would mean that the top left point was not on any screen. + } + + /** + * Centers the window over a component (usually another window). + * The window must already have been sized. + */ + public static void centerWindowOnComponent(Window window, Component over) { + if ((over == null) || !over.isShowing()) { + centerWindowOnScreen(window); + return; + } + + + Point parentLocation = over.getLocationOnScreen(); + Dimension parentSize = over.getSize(); + Dimension size = window.getSize(); + + // Center it. + int x = parentLocation.x + (parentSize.width - size.width) / 2; + int y = parentLocation.y + (parentSize.height - size.height) / 2; + + // Now, make sure it's onscreen + Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); + + // This doesn't actually work on the Mac, where the screen + // doesn't necessarily start at 0,0 + if (x + size.width > screenSize.width) + x = screenSize.width - size.width; + + if (x < 0) + x = 0; + + if (y + size.height > screenSize.height) + y = screenSize.height - size.height; + + if (y < 0) + y = 0; + + window.setLocation(x, y); + } + + /** + * @return returns true if the component of one of its child has the focus + */ + public static boolean isAncestorOfFocusedComponent(Component c) { + if (c.hasFocus()) { + return true; + } + else { + if (c instanceof Container) { + Container cont = (Container)c; + int n = cont.getComponentCount(); + for (int i = 0; i < n; i++) { + Component child = cont.getComponent(i); + if (isAncestorOfFocusedComponent(child)) + return true; + } + } + } + return false; + } + + /** + * Returns the first component in the tree of c that can accept + * the focus. + * + * @param c the root of the component hierarchy to search + * @see #focusComponentOrChild + * @deprecated replaced by {@link #getFocusableComponentOrChild(Component, boolean)} + */ + public static Component getFocusableComponentOrChild(Component c) { + return getFocusableComponentOrChild(c, false); + } + + /** + * Returns the first component in the tree of c that can accept + * the focus. + * + * @param c the root of the component hierarchy to search + * @param deepest if deepest is true the method will return the first and deepest component that can accept the + * focus. For example, if both a child and its parent are focusable and deepest is true, the child is + * returned. + * @see #focusComponentOrChild + */ + public static Component getFocusableComponentOrChild(Component c, boolean deepest) { + if (c != null && c.isEnabled() && c.isVisible()) { + if (c instanceof Container) { + Container cont = (Container)c; + + if (deepest == false) { // first one is a good one + if (c instanceof JComponent) { + JComponent jc = (JComponent)c; + if (jc.isRequestFocusEnabled()) { + return jc; + } + } + } + + int n = cont.getComponentCount(); + for (int i = 0; i < n; i++) { + Component child = cont.getComponent(i); + Component focused = getFocusableComponentOrChild(child, deepest); + if (focused != null) { + return focused; + } + } + + if (c instanceof JComponent) { + if (deepest == true) { + JComponent jc = (JComponent)c; + if (jc.isRequestFocusEnabled()) { + return jc; + } + } + } + else { + return c; + } + } + } + + return null; + } + + /** + * Puts the focus on the first component in the tree of c that + * can accept the focus. + * + * @see #getFocusableComponentOrChild + */ + public static Component focusComponentOrChild(Component c) { + return focusComponentOrChild(c, false); + } + + /** + * Puts the focus on the first component in the tree of c that + * can accept the focus. + * + * @param c the root of the component hierarchy to search + * @param deepest if deepest is true the method will focus the first and deepest component that can + * accept the focus. + * For example, if both a child and its parent are focusable and deepest is true, the child is focused. + * @see #getFocusableComponentOrChild + */ + public static Component focusComponentOrChild(Component c, boolean deepest) { + final Component focusable = getFocusableComponentOrChild(c, deepest); + if (focusable != null) { + focusable.requestFocus(); + } + return focusable; + } + + /** + * Loads an {@link Image} named imageName as a resource + * relative to the Class cls. If the Image can + * not be loaded, then null is returned. Images loaded here + * will be added to an internal cache based upon the full {@link URL} to + * their location. + *

        + * This method replaces legacy code from JDeveloper 3.x and earlier. + * + * @see Class#getResource(String) + * @see Toolkit#createImage(URL) + */ + public static Image loadFromResource(String imageName, Class cls) { + try { + final URL url = cls.getResource(imageName); + + if (url == null) { + return null; + } + + Image image = (Image)imageCache.get(url.toString()); + + if (image == null) { + image = Toolkit.getDefaultToolkit().createImage(url); + imageCache.put(url.toString(), image); + } + + return image; + } + catch (Exception e) { + Log.error(e); + } + + return null; + } + + public static Rectangle[] getScreenBounds() { + GraphicsEnvironment graphicsEnvironment = GraphicsEnvironment.getLocalGraphicsEnvironment(); + final GraphicsDevice[] screenDevices = graphicsEnvironment.getScreenDevices(); + Rectangle[] screenBounds = new Rectangle[screenDevices.length]; + for (int i = 0; i < screenDevices.length; i++) { + GraphicsDevice screenDevice = screenDevices[i]; + final GraphicsConfiguration defaultConfiguration = screenDevice.getDefaultConfiguration(); + screenBounds[i] = defaultConfiguration.getBounds(); + } + + return screenBounds; + } + + public static final void makeSameSize(JComponent[] comps) { + if (comps.length == 0) { + return; + } + + int max = 0; + for (int i = 0; i < comps.length; i++) { + int w = comps[i].getPreferredSize().width; + max = w > max ? w : max; + } + + Dimension dim = new Dimension(max, comps[0].getPreferredSize().height); + for (int i = 0; i < comps.length; i++) { + comps[i].setPreferredSize(dim); + } + } + + /** + * Return the hexidecimal color from a java.awt.Color + * + * @param c + * @return hexadecimal string + */ + public static final String toHTMLColor(Color c) { + int color = c.getRGB(); + color |= 0xff000000; + String s = Integer.toHexString(color); + return s.substring(2); + } + + public static final String createToolTip(String text, int width) { + final String htmlColor = toHTMLColor(TOOLTIP_COLOR); + final String toolTip = "
        " + text + "
        "; + return toolTip; + } + + public static final String createToolTip(String text) { + final String htmlColor = toHTMLColor(TOOLTIP_COLOR); + final String toolTip = "
        " + text + "
        "; + return toolTip; + } + + public static final String getHighlightedWords(String text, String query) { + final StringTokenizer tkn = new StringTokenizer(query, " "); + int tokenCount = tkn.countTokens(); + String[] words = new String[tokenCount]; + for (int j = 0; j < tokenCount; j++) { + String queryWord = tkn.nextToken(); + words[j] = queryWord; + } + + String highlightedWords; + try { + highlightedWords = StringUtils.highlightWords(text, words, "", ""); + } + catch (Exception e) { + highlightedWords = text; + } + + + return highlightedWords; + } + + public static ImageIcon createShadowPicture(Image buf) { + buf = removeTransparency(buf); + + BufferedImage splash = null; + + JLabel label = new JLabel(); + int width = buf.getWidth(null); + int height = buf.getHeight(null); + int extra = 4; + + splash = new BufferedImage(width + extra, height + extra, BufferedImage.TYPE_INT_ARGB); + Graphics2D g2 = (Graphics2D)splash.getGraphics(); + + + BufferedImage shadow = new BufferedImage(width + extra, height + extra, BufferedImage.TYPE_INT_ARGB); + Graphics g = shadow.getGraphics(); + g.setColor(new Color(0.0f, 0.0f, 0.0f, 0.3f)); + g.fillRoundRect(0, 0, width, height, 12, 12); + + g2.drawImage(shadow, getBlurOp(7), 0, 0); + g2.drawImage(buf, 0, 0, label); + return new ImageIcon(splash); + } + + public static BufferedImage removeTransparency(Image image) { + int w = image.getWidth(null); + int h = image.getHeight(null); + BufferedImage bi2 = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB); + Graphics2D g = bi2.createGraphics(); + g.setColor(Color.WHITE); + g.fillRect(0, 0, w, h); + g.drawImage(image, 0, 0, null); + g.dispose(); + + return bi2; + } + + public static Image toImage(BufferedImage bufferedImage) { + return Toolkit.getDefaultToolkit().createImage(bufferedImage.getSource()); + } + + private static ConvolveOp getBlurOp(int size) { + float[] data = new float[size * size]; + float value = 1 / (float)(size * size); + for (int i = 0; i < data.length; i++) { + data[i] = value; + } + return new ConvolveOp(new Kernel(size, size, data)); + } + + public static BufferedImage convert(Image im) throws InterruptedException, IOException { + load(im); + BufferedImage bi = new BufferedImage(im.getWidth(null), im.getHeight(null), BufferedImage.TYPE_INT_RGB); + Graphics bg = bi.getGraphics(); + bg.drawImage(im, 0, 0, null); + bg.dispose(); + return bi; + } + + public static void load(Image image) throws InterruptedException, IOException { + MediaTracker tracker = new MediaTracker(new Label()); //any component will do + tracker.addImage(image, 0); + tracker.waitForID(0); + if (tracker.isErrorID(0)) + throw new IOException("error loading image"); + } + + public static byte[] getBytesFromImage(Image image) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try { + ImageIO.write(convert(image), "PNG", baos); + } + catch (IOException e) { + Log.error(e); + } + catch (InterruptedException e) { + Log.error(e); + } + byte[] bytes = baos.toByteArray(); + + return bytes; + } + + + /** + * Returns a scaled down image if the height or width is smaller than + * the image size. + * + * @param icon the image icon. + * @param newHeight the preferred height. + * @param newWidth the preferred width. + * @return the icon. + */ + public static ImageIcon scaleImageIcon(ImageIcon icon, int newHeight, int newWidth) { + Image img = icon.getImage(); + int height = icon.getIconHeight(); + int width = icon.getIconWidth(); + + if (height > newHeight) { + height = newHeight; + } + + if (width > newWidth) { + width = newWidth; + } + img = img.getScaledInstance(width, height, Image.SCALE_SMOOTH); + return new ImageIcon(img); + } + + /** + * Returns the native icon, if one exists for the filetype, otherwise + * returns a default document icon. + * + * @param file the file to check icon type. + * @return the native icon, otherwise default document icon. + */ + public static Icon getIcon(File file) { + try { + sun.awt.shell.ShellFolder sf = sun.awt.shell.ShellFolder.getShellFolder(file); + + // Get large icon + return new ImageIcon(sf.getIcon(true), sf.getFolderType()); + } + catch (Exception e) { + try { + return new JFileChooser().getIcon(file); + } + catch (Exception e1) { + } + } + + return SparkRes.getImageIcon(SparkRes.DOCUMENT_INFO_32x32); + } +} diff --git a/src/java/org/jivesoftware/spark/util/ModelUtil.java b/src/java/org/jivesoftware/spark/util/ModelUtil.java new file mode 100644 index 00000000..1da12132 --- /dev/null +++ b/src/java/org/jivesoftware/spark/util/ModelUtil.java @@ -0,0 +1,334 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.util; + + +import java.util.ArrayList; +import java.util.Date; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; + +/** + * Utility methods frequently used by data classes and design-time + * classes. + */ +public final class ModelUtil { + + private ModelUtil() { + // Prevent instantiation. + } + + /** + * This is a utility method that compares two objects when one or + * both of the objects might be null The result of + * this method is determined as follows: + *

          + *
        1. If o1 and o2 are the same object + * according to the == operator, return + * true. + *
        2. Otherwise, if either o1 or o2 is + * null, return false. + *
        3. Otherwise, return o1.equals(o2). + *
        + *

        + * This method produces the exact logically inverted result as the + * {@link #areDifferent(Object, Object)} method.

        + *

        + * For array types, one of the equals methods in + * {@link java.util.Arrays} should be used instead of this method. + * Note that arrays with more than one dimension will require some + * custom code in order to implement equals properly. + */ + public static final boolean areEqual(Object o1, Object o2) { + if (o1 == o2) { + return true; + } + else if (o1 == null || o2 == null) { + return false; + } + else { + return o1.equals(o2); + } + } + + /** + * This is a utility method that compares two Booleans when one or + * both of the objects might be null The result of + * this method is determined as follows: + *

          + *
        1. If b1 and b2 are both TRUE or + * neither b1 nor b2 is TRUE, + * return true. + *
        2. Otherwise, return false. + *
        + *

        + * This method produces the exact logically inverted result as the + * areDifferent(Boolean, Boolean) method.

        + */ + public static final boolean areBooleansEqual(Boolean b1, Boolean b2) { + // !jwetherb treat NULL the same as Boolean.FALSE + return (b1 == Boolean.TRUE && b2 == Boolean.TRUE) || + (b1 != Boolean.TRUE && b2 != Boolean.TRUE); + } + + /** + * This is a utility method that compares two objects when one or + * both of the objects might be null. The result + * returned by this method is determined as follows: + *

          + *
        1. If o1 and o2 are the same object + * according to the == operator, return + * false. + *
        2. Otherwise, if either o1 or o2 is + * null, return true. + *
        3. Otherwise, return !o1.equals(o2). + *
        + *

        + * This method produces the exact logically inverted result as the + * {@link #areEqual(Object, Object)} method.

        + *

        + * For array types, one of the equals methods in + * {@link java.util.Arrays} should be used instead of this method. + * Note that arrays with more than one dimension will require some + * custom code in order to implement equals properly. + */ + public static final boolean areDifferent(Object o1, Object o2) { + return !areEqual(o1, o2); + } + + + /** + * This is a utility method that compares two Booleans when one or + * both of the objects might be null The result of + * this method is determined as follows: + *

          + *
        1. If b1 and b2 are both TRUE or + * neither b1 nor b2 is TRUE, + * return false. + *
        2. Otherwise, return true. + *
        + *

        + * This method produces the exact logically inverted result as the + * {@link #areBooleansEqual(Boolean, Boolean)} method.

        + */ + public static final boolean areBooleansDifferent(Boolean b1, Boolean b2) { + return !areBooleansEqual(b1, b2); + } + + + /** + * Returns true if the specified array is not null + * and contains a non-null element. Returns false + * if the array is null or if all the array elements are null. + */ + public static final boolean hasNonNullElement(Object[] array) { + if (array != null) { + final int n = array.length; + for (int i = 0; i < n; i++) { + if (array[i] != null) { + return true; + } + } + } + return false; + } + + /** + * Returns a single string that is the concatenation of all the + * strings in the specified string array. A single space is + * put between each string array element. Null array elements + * are skipped. If the array itself is null, the empty string + * is returned. This method is guaranteed to return a non-null + * value, if no expections are thrown. + */ + public static final String concat(String[] strs) { + return concat(strs, " "); //NOTRANS + } + + /** + * Returns a single string that is the concatenation of all the + * strings in the specified string array. The strings are separated + * by the specified delimiter. Null array elements are skipped. If + * the array itself is null, the empty string is returned. This + * method is guaranteed to return a non-null value, if no expections + * are thrown. + */ + public static final String concat(String[] strs, String delim) { + if (strs != null) { + final StringBuffer buf = new StringBuffer(); + final int n = strs.length; + for (int i = 0; i < n; i++) { + final String str = strs[i]; + if (str != null) { + buf.append(str).append(delim); + } + } + final int length = buf.length(); + if (length > 0) { + // Trim trailing space. + buf.setLength(length - 1); + } + return buf.toString(); + } + else { + return ""; // NOTRANS + } + } + + /** + * Returns true if the specified {@link String} is not + * null and has a length greater than zero. This is + * a very frequently occurring check. + */ + public static final boolean hasLength(String s) { + return (s != null && s.length() > 0); + } + + + /** + * Returns null if the specified string is empty or + * null. Otherwise the string itself is returned. + */ + public static final String nullifyIfEmpty(String s) { + return ModelUtil.hasLength(s) ? s : null; + } + + /** + * Returns null if the specified object is null + * or if its toString() representation is empty. + * Otherwise, the toString() representation of the + * object itself is returned. + */ + public static final String nullifyingToString(Object o) { + return o != null ? nullifyIfEmpty(o.toString()) : null; + } + + /** + * Determines if a string has been changed. + * + * @param oldString is the initial value of the String + * @param newString is the new value of the String + * @return true If both oldString and newString are null or if they are + * both not null and equal to each other. Otherwise returns false. + */ + public static boolean hasStringChanged(String oldString, String newString) { + if (oldString == null && newString == null) { + return false; + } + else if ((oldString == null && newString != null) + || (oldString != null && newString == null)) { + return true; + } + else { + return !oldString.equals(newString); + } + } + + /** + * Returns a formatted String from time. + * + * @param diff the amount of elapsed time. + * @return the formatte String. + */ + public static String getTimeFromLong(long diff) { + final String HOURS = "h"; + final String MINUTES = "min"; + final String SECONDS = "sec"; + + final long MS_IN_A_DAY = 1000 * 60 * 60 * 24; + final long MS_IN_AN_HOUR = 1000 * 60 * 60; + final long MS_IN_A_MINUTE = 1000 * 60; + final long MS_IN_A_SECOND = 1000; + Date currentTime = new Date(); + long numDays = diff / MS_IN_A_DAY; + diff = diff % MS_IN_A_DAY; + long numHours = diff / MS_IN_AN_HOUR; + diff = diff % MS_IN_AN_HOUR; + long numMinutes = diff / MS_IN_A_MINUTE; + diff = diff % MS_IN_A_MINUTE; + long numSeconds = diff / MS_IN_A_SECOND; + diff = diff % MS_IN_A_SECOND; + long numMilliseconds = diff; + + StringBuffer buf = new StringBuffer(); + if (numHours > 0) { + buf.append(numHours + " " + HOURS + ", "); + } + + if (numMinutes > 0) { + buf.append(numMinutes + " " + MINUTES); + } + + //buf.append(numSeconds + " " + SECONDS); + + String result = buf.toString(); + + if (numMinutes < 1) { + result = "< 1 minute"; + } + + return result; + } + + + /** + * Build a List of all elements in an Iterator. + */ + public static List iteratorAsList(Iterator i) { + ArrayList list = new ArrayList(10); + while (i.hasNext()) { + list.add(i.next()); + } + return list; + } + + /** + * Creates an Iterator that is the reverse of a ListIterator. + */ + public static Iterator reverseListIterator(ListIterator i) { + return new ReverseListIterator(i); + } +} + +/** + * An Iterator that is the reverse of a ListIterator. + */ +class ReverseListIterator implements Iterator { + private ListIterator _i; + + ReverseListIterator(ListIterator i) { + _i = i; + while (_i.hasNext()) _i.next(); + } + + public boolean hasNext() { + return _i.hasPrevious(); + } + + public Object next() { + return _i.previous(); + } + + public void remove() { + _i.remove(); + } +} + + + + + + + + + + diff --git a/src/java/org/jivesoftware/spark/util/ResourceUtils.java b/src/java/org/jivesoftware/spark/util/ResourceUtils.java new file mode 100644 index 00000000..9bcfbf24 --- /dev/null +++ b/src/java/org/jivesoftware/spark/util/ResourceUtils.java @@ -0,0 +1,110 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.util; + +import org.jivesoftware.Spark; + +import javax.swing.AbstractButton; +import javax.swing.JLabel; + +import java.awt.Component; + +/** + * Utility class to make using resources easier. + *

        + * Example use: + *

        + *

        + * 

        + * JLabel lblUsername = new JLabel(); + * JTextField tfUsername = new JTextField(); + * ResourceUtils.resLabel( lblUserName, tfUserName, + * IdeArb.getString ( IdeArb.USERNAME ) ); + *

        + * JButton b = new JButton(); + * ResourceUtils.resButton( b, IdeArb.getString( IdeArb.SOME_STRING ) ); + *

        + *

        + */ +public final class ResourceUtils { + /** + * Sets the resources on a {@link JLabel}. It sets the text, mnemonic, + * and labelFor property. + * + * @param label The Label on which to set the properties + * @param labelFor the {@link Component} to set with the + * labelFor property on the label. + * @param labelText The text label to set on the label + * @see JLabel#setText(String) + * @see JLabel#setLabelFor(Component) + * @see JLabel#setDisplayedMnemonic(int) + */ + public static void resLabel(JLabel label, Component labelFor, String labelText) { + label.setText(stripMnemonic(labelText)); + + if (Spark.isWindows()) { + label.setDisplayedMnemonic(getMnemonicKeyCode(labelText)); + } + label.setLabelFor(labelFor); + } + + /** + * Sets the resources on a subclass of {@link AbstractButton}. The common + * classes are {@link javax.swing.JRadioButton}, {@link javax.swing.JButton}, + * and {@link javax.swing.JCheckBox} + *

        + * This method sets the text and mnemonic. + * + * @param button The button on which to set the text and mnemonoic + * @param labelText the text which contains the displayed text and mnemonic + * @see AbstractButton#setText(String) + * @see AbstractButton#setMnemonic(int) + */ + public static void resButton(AbstractButton button, String labelText) { + button.setText(stripMnemonic(labelText)); + + if (Spark.isWindows()) { + button.setMnemonic(getMnemonicKeyCode(labelText)); + } + } + + public static final String stripMnemonic(String label) { + String text = ""; + int index = label.indexOf("&"); + if (index != -1) { + text = label.substring(0, index); + if (label.length() > index) { + text = text + label.substring(index + 1); + return text; + } + } + return label; + } + + public static final int getMnemonicKeyCode(String mnemonic) { + int mindex = mnemonic.indexOf("&"); + String text = ""; + if (mindex > -1) { + text = mnemonic.substring(0, mindex); + if (mnemonic.length() > mindex) { + text = text + mnemonic.substring(mindex + 1); + } + + return (int)mnemonic.charAt(mindex + 1); + } + return 0; + } + + private ResourceUtils() { + } + + +} diff --git a/src/java/org/jivesoftware/spark/util/StringUtils.java b/src/java/org/jivesoftware/spark/util/StringUtils.java new file mode 100644 index 00000000..5cb809fb --- /dev/null +++ b/src/java/org/jivesoftware/spark/util/StringUtils.java @@ -0,0 +1,2844 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.util; + + +import javax.swing.KeyStroke; + +import java.awt.event.InputEvent; +import java.awt.event.KeyEvent; +import java.io.UnsupportedEncodingException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.text.BreakIterator; +import java.util.ArrayList; +import java.util.BitSet; +import java.util.Date; +import java.util.Locale; +import java.util.Random; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + + +/** + * Utility class to peform common String manipulation algorithms. + */ + +public class StringUtils { + + // Constants used by escapeHTMLTags + + private static final char[] QUOTE_ENCODE = """.toCharArray(); + + private static final char[] AMP_ENCODE = "&".toCharArray(); + + private static final char[] LT_ENCODE = "<".toCharArray(); + + private static final char[] GT_ENCODE = ">".toCharArray(); + + // patterns for the email address checks + + private static Pattern basicAddressPattern; + + private static Pattern validUserPattern; + + private static Pattern domainPattern; + + private static Pattern ipDomainPattern; + + private static Pattern tldPattern; + + // prepare the patterns + + static { + + // constants used in the parsing of email addresses + + String basicAddress = "^([\\w\\.-]+)@([\\w\\.-]+)$"; + + String specialChars = "\\(\\)><@,;:\\\\\\\"\\.\\[\\]"; + + String validChars = "[^ \f\n\r\t" + specialChars + "]"; + + String atom = validChars + "+"; + + String quotedUser = "(\"[^\"]+\")"; + + String word = "(" + atom + "|" + quotedUser + ")"; + + String validUser = "^" + word + "(\\." + word + ")*$"; + + String domain = "^" + atom + "(\\." + atom + ")+$"; + + String ipDomain = "^(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$"; + + // from http://www.icann.org/tlds/ + + String knownTLDs = "^\\.(com|net|org|edu|int|mil|gov|arpa|biz|aero|name|coop|info|pro|museum)$"; + + + basicAddressPattern = Pattern.compile(basicAddress, Pattern.CASE_INSENSITIVE); + + validUserPattern = Pattern.compile(validUser, Pattern.CASE_INSENSITIVE); + + domainPattern = Pattern.compile(domain, Pattern.CASE_INSENSITIVE); + + ipDomainPattern = Pattern.compile(ipDomain, Pattern.CASE_INSENSITIVE); + + tldPattern = Pattern.compile(knownTLDs, Pattern.CASE_INSENSITIVE); + + } + + + /** + * Replaces all instances of oldString with newString in string. + * + * @param string the String to search to perform replacements on + * @param oldString the String that should be replaced by newString + * @param newString the String that will replace all instances of oldString + * @return a String will all instances of oldString replaced by newString + */ + + public static final String replace(String string, String oldString, String newString) { + + if (string == null) { + + return null; + + } + + // If the newString is null or zero length, just return the string since there's nothing + + // to replace. + + if (newString == null) { + + return string; + + } + + int i = 0; + + // Make sure that oldString appears at least once before doing any processing. + + if ((i = string.indexOf(oldString, i)) >= 0) { + + // Use char []'s, as they are more efficient to deal with. + + char [] string2 = string.toCharArray(); + + char [] newString2 = newString.toCharArray(); + + int oLength = oldString.length(); + + StringBuffer buf = new StringBuffer(string2.length); + + buf.append(string2, 0, i).append(newString2); + + i += oLength; + + int j = i; + + // Replace all remaining instances of oldString with newString. + + while ((i = string.indexOf(oldString, i)) > 0) { + + buf.append(string2, j, i - j).append(newString2); + + i += oLength; + + j = i; + + } + + buf.append(string2, j, string2.length - j); + + return buf.toString(); + + } + + return string; + + } + + + /** + * Replaces all instances of oldString with newString in line with the + *

        + * added feature that matches of newString in oldString ignore case. + * + * @param line the String to search to perform replacements on + * @param oldString the String that should be replaced by newString + * @param newString the String that will replace all instances of oldString + * @return a String will all instances of oldString replaced by newString + */ + + public static final String replaceIgnoreCase(String line, String oldString, String newString) { + + if (line == null) { + + return null; + + } + + String lcLine = line.toLowerCase(); + + String lcOldString = oldString.toLowerCase(); + + int i = 0; + + if ((i = lcLine.indexOf(lcOldString, i)) >= 0) { + + char [] line2 = line.toCharArray(); + + char [] newString2 = newString.toCharArray(); + + int oLength = oldString.length(); + + StringBuffer buf = new StringBuffer(line2.length); + + buf.append(line2, 0, i).append(newString2); + + i += oLength; + + int j = i; + + while ((i = lcLine.indexOf(lcOldString, i)) > 0) { + + buf.append(line2, j, i - j).append(newString2); + + i += oLength; + + j = i; + + } + + buf.append(line2, j, line2.length - j); + + return buf.toString(); + + } + + return line; + + } + + + /** + * Replaces all instances of oldString with newString in line with the + *

        + * added feature that matches of newString in oldString ignore case. + *

        + * The count paramater is set to the number of replaces performed. + * + * @param line the String to search to perform replacements on + * @param oldString the String that should be replaced by newString + * @param newString the String that will replace all instances of oldString + * @param count a value that will be updated with the number of replaces + *

        + * performed. + * @return a String will all instances of oldString replaced by newString + */ + + public static final String replaceIgnoreCase(String line, String oldString, + + String newString, int [] count) + + { + + if (line == null) { + + return null; + + } + + String lcLine = line.toLowerCase(); + + String lcOldString = oldString.toLowerCase(); + + int i = 0; + + if ((i = lcLine.indexOf(lcOldString, i)) >= 0) { + + int counter = 1; + + char [] line2 = line.toCharArray(); + + char [] newString2 = newString.toCharArray(); + + int oLength = oldString.length(); + + StringBuffer buf = new StringBuffer(line2.length); + + buf.append(line2, 0, i).append(newString2); + + i += oLength; + + int j = i; + + while ((i = lcLine.indexOf(lcOldString, i)) > 0) { + + counter++; + + buf.append(line2, j, i - j).append(newString2); + + i += oLength; + + j = i; + + } + + buf.append(line2, j, line2.length - j); + + count[0] = counter; + + return buf.toString(); + + } + + return line; + + } + + + /** + * Replaces all instances of oldString with newString in line. + *

        + * The count Integer is updated with number of replaces. + * + * @param line the String to search to perform replacements on + * @param oldString the String that should be replaced by newString + * @param newString the String that will replace all instances of oldString + * @return a String will all instances of oldString replaced by newString + */ + + public static final String replace(String line, String oldString, + + String newString, int[] count) + + { + + if (line == null) { + + return null; + + } + + int i = 0; + + if ((i = line.indexOf(oldString, i)) >= 0) { + + int counter = 1; + + char [] line2 = line.toCharArray(); + + char [] newString2 = newString.toCharArray(); + + int oLength = oldString.length(); + + StringBuffer buf = new StringBuffer(line2.length); + + buf.append(line2, 0, i).append(newString2); + + i += oLength; + + int j = i; + + while ((i = line.indexOf(oldString, i)) > 0) { + + counter++; + + buf.append(line2, j, i - j).append(newString2); + + i += oLength; + + j = i; + + } + + buf.append(line2, j, line2.length - j); + + count[0] = counter; + + return buf.toString(); + + } + + return line; + + } + + + /** + * This method takes a string and strips out all tags except
        tags while still leaving + *

        + * the tag body intact. + * + * @param in the text to be converted. + * @return the input string with all tags removed. + */ + + public static final String stripTags(String in) { + + if (in == null) { + + return null; + + } + + + return stripTags(in, false); + + } + + + /** + * This method takes a string and strips out all tags while still leaving + *

        + * the tag body intact. + * + * @param in the text to be converted. + * @return the input string with all tags removed. + */ + + public static final String stripTags(String in, boolean stripBRTag) { + + if (in == null) { + + return null; + + } + + char ch; + + int i = 0; + + int last = 0; + + char[] input = in.toCharArray(); + + int len = input.length; + + StringBuffer out = new StringBuffer((int)(len * 1.3)); + + for (; i < len; i++) { + + ch = input[i]; + + if (ch > '>') { + + continue; + + } + + else if (ch == '<') { + + if (!stripBRTag && i + 3 < len && input[i + 1] == 'b' && input[i + 2] == 'r' && input[i + 3] == '>') { + + i += 3; + + continue; + + } + + if (i > last) { + + if (last > 0) { + + out.append(" "); + + } + + out.append(input, last, i - last); + + } + + last = i + 1; + + } + + else if (ch == '>') { + + last = i + 1; + + } + + } + + if (last == 0) { + + return in; + + } + + if (i > last) { + + out.append(input, last, i - last); + + } + + return out.toString(); + + } + + + /** + * This method takes a string which may contain HTML tags (ie, <b>, + *

        + * <table>, etc) and converts the '<'' and '>' characters to + *

        + * their HTML escape sequences. + * + * @param in the text to be converted. + * @return the input string with the characters '<' and '>' replaced + *

        + * with their HTML escape sequences. + */ + + public static final String escapeHTMLTags(String in) { + + if (in == null) { + + return null; + + } + + char ch; + + int i = 0; + + int last = 0; + + char[] input = in.toCharArray(); + + int len = input.length; + + StringBuffer out = new StringBuffer((int)(len * 1.3)); + + for (; i < len; i++) { + + ch = input[i]; + + if (ch > '>') { + + continue; + + } + else if (ch == '<') { + + if (i > last) { + + out.append(input, last, i - last); + + } + + last = i + 1; + + out.append(LT_ENCODE); + + } + else if (ch == '>') { + + if (i > last) { + + out.append(input, last, i - last); + + } + + last = i + 1; + + out.append(GT_ENCODE); + + } + else if (ch == '"') { + + if (i > last) { + + out.append(input, last, i - last); + + } + + last = i + 1; + + out.append(QUOTE_ENCODE); + + } + + } + + if (last == 0) { + + return in; + + } + + if (i > last) { + + out.append(input, last, i - last); + + } + + return out.toString(); + + } + + + /** + * Used by the hash method. + */ + + private static MessageDigest digest = null; + + + /** + * Hashes a String using the Md5 algorithm and returns the result as a + *

        + * String of hexadecimal numbers. This method is synchronized to avoid + *

        + * excessive MessageDigest object creation. If calling this method becomes + *

        + * a bottleneck in your code, you may wish to maintain a pool of + *

        + * MessageDigest objects instead of using this method. + *

        + *

        + *

        + * A hash is a one-way function -- that is, given an + *

        + * input, an output is easily computed. However, given the output, the + *

        + * input is almost impossible to compute. This is useful for passwords + *

        + * since we can store the hash and a hacker will then have a very hard time + *

        + * determining the original password. + *

        + *

        + *

        + * In Jive, every time a user logs in, we simply + *

        + * take their plain text password, compute the hash, and compare the + *

        + * generated hash to the stored hash. Since it is almost impossible that + *

        + * two passwords will generate the same hash, we know if the user gave us + *

        + * the correct password or not. The only negative to this system is that + *

        + * password recovery is basically impossible. Therefore, a reset password + *

        + * method is used instead. + * + * @param data the String to compute the hash of. + * @return a hashed version of the passed-in String + */ + + public synchronized static final String hash(String data) { + + if (digest == null) { + + try { + + digest = MessageDigest.getInstance("MD5"); + + } + + catch (NoSuchAlgorithmException nsae) { + + } + + } + + // Now, compute hash. + + try { + + digest.update(data.getBytes("utf-8")); + + } + + catch (UnsupportedEncodingException e) { + + } + + return encodeHex(digest.digest()); + + } + + public synchronized static final String hash(byte[] data) { + + if (digest == null) { + + try { + + digest = MessageDigest.getInstance("MD5"); + + } + + catch (NoSuchAlgorithmException nsae) { + + } + + } + + // Now, compute hash. + + + digest.update(data); + + return encodeHex(digest.digest()); + + } + + + /** + * Turns an array of bytes into a String representing each byte as an + *

        + * unsigned hex number. + *

        + *

        + *

        + * Method by Santeri Paavolainen, Helsinki Finland 1996
        + *

        + * (c) Santeri Paavolainen, Helsinki Finland 1996
        + *

        + * Distributed under LGPL. + * + * @param bytes an array of bytes to convert to a hex-string + * @return generated hex string + */ + + public static final String encodeHex(byte[] bytes) { + + StringBuffer buf = new StringBuffer(bytes.length * 2); + + int i; + + + for (i = 0; i < bytes.length; i++) { + + if (((int)bytes[i] & 0xff) < 0x10) { + + buf.append("0"); + + } + + buf.append(Long.toString((int)bytes[i] & 0xff, 16)); + + } + + return buf.toString(); + + } + + + /** + * Turns a hex encoded string into a byte array. It is specifically meant + *

        + * to "reverse" the toHex(byte[]) method. + * + * @param hex a hex encoded String to transform into a byte array. + * @return a byte array representing the hex String[ + */ + + public static final byte[] decodeHex(String hex) { + + char [] chars = hex.toCharArray(); + + byte[] bytes = new byte[chars.length / 2]; + + int byteCount = 0; + + for (int i = 0; i < chars.length; i += 2) { + + int newByte = 0x00; + + newByte |= hexCharToByte(chars[i]); + + newByte <<= 4; + + newByte |= hexCharToByte(chars[i + 1]); + + bytes[byteCount] = (byte)newByte; + + byteCount++; + + } + + return bytes; + + } + + + /** + * Returns the the byte value of a hexadecmical char (0-f). It's assumed + *

        + * that the hexidecimal chars are lower case as appropriate. + * + * @param ch a hexedicmal character (0-f) + * @return the byte value of the character (0x00-0x0F) + */ + + private static final byte hexCharToByte(char ch) { + + switch (ch) { + + case '0': + return 0x00; + + case '1': + return 0x01; + + case '2': + return 0x02; + + case '3': + return 0x03; + + case '4': + return 0x04; + + case '5': + return 0x05; + + case '6': + return 0x06; + + case '7': + return 0x07; + + case '8': + return 0x08; + + case '9': + return 0x09; + + case 'a': + return 0x0A; + + case 'b': + return 0x0B; + + case 'c': + return 0x0C; + + case 'd': + return 0x0D; + + case 'e': + return 0x0E; + + case 'f': + return 0x0F; + + } + + return 0x00; + + } + + //********************************************************************* + + //* 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. + + //********************************************************************* + + + /** + * Encodes a String as a base64 String. + * + * @param data a String to encode. + * @return a base64 encoded String. + */ + + public static String encodeBase64(String data) { + + byte [] bytes = null; + + try { + + bytes = data.getBytes("ISO-8859-1"); + + } + + catch (UnsupportedEncodingException uee) { + + } + + return encodeBase64(bytes); + + } + + + /** + * Encodes a byte array into a base64 String. + * + * @param data a byte array to encode. + * @return a base64 encode String. + */ + + public static String encodeBase64(byte[] data) { + + int c; + + int len = data.length; + + StringBuffer ret = new StringBuffer(((len / 3) + 1) * 4); + + for (int i = 0; i < len; ++i) { + + c = (data[i] >> 2) & 0x3f; + + ret.append(cvt.charAt(c)); + + c = (data[i] << 4) & 0x3f; + + if (++i < len) + + c |= (data[i] >> 4) & 0x0f; + + + ret.append(cvt.charAt(c)); + + if (i < len) { + + c = (data[i] << 2) & 0x3f; + + if (++i < len) + + c |= (data[i] >> 6) & 0x03; + + + ret.append(cvt.charAt(c)); + + } + + else { + + ++i; + + ret.append((char)fillchar); + + } + + + if (i < len) { + + c = data[i] & 0x3f; + + ret.append(cvt.charAt(c)); + + } + + else { + + ret.append((char)fillchar); + + } + + } + + return ret.toString(); + + } + + + /** + * Decodes a base64 String. + * + * @param data a base64 encoded String to decode. + * @return the decoded String. + */ + + public static String decodeBase64(String data) { + + byte [] bytes = null; + + try { + + bytes = data.getBytes("ISO-8859-1"); + + } + + catch (UnsupportedEncodingException uee) { + + } + + return decodeBase64(bytes); + + } + + + /** + * Decodes a base64 aray of bytes. + * + * @param data a base64 encode byte array to decode. + * @return the decoded String. + */ + + public static String decodeBase64(byte[] data) { + + int c, c1; + + int len = data.length; + + StringBuffer ret = new StringBuffer((len * 3) / 4); + + for (int i = 0; i < len; ++i) { + + c = cvt.indexOf(data[i]); + + ++i; + + c1 = cvt.indexOf(data[i]); + + c = ((c << 2) | ((c1 >> 4) & 0x3)); + + ret.append((char)c); + + if (++i < len) { + + c = data[i]; + + if (fillchar == c) + + break; + + + c = cvt.indexOf(c); + + c1 = ((c1 << 4) & 0xf0) | ((c >> 2) & 0xf); + + ret.append((char)c1); + + } + + + if (++i < len) { + + c1 = data[i]; + + if (fillchar == c1) + + break; + + + c1 = cvt.indexOf(c1); + + c = ((c << 6) & 0xc0) | c1; + + ret.append((char)c); + + } + + } + + return ret.toString(); + + } + + + /** + * The method below is under the following license + *

        + *

        + *

        + * ==================================================================== + *

        + *

        + *

        + * The Apache Software License, Version 1.1 + *

        + *

        + *

        + * Copyright (c) 2002-2003 The Apache Software Foundation. All rights + *

        + * reserved. + *

        + *

        + *

        + * 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 end-user documentation included with the redistribution, if + *

        + * any, must include the following acknowlegement: + *

        + * "This product includes software developed by the + *

        + * Apache Software Foundation (http://www.apache.org/)." + *

        + * Alternately, this acknowlegement may appear in the software itself, + *

        + * if and wherever such third-party acknowlegements normally appear. + *

        + *

        + *

        + * 4. The names "The Jakarta Project", "Commons", and "Apache Software + *

        + * Foundation" must not be used to endorse or promote products derived + *

        + * from this software without prior written permission. For written + *

        + * permission, please contact apache@apache.org. + *

        + *

        + *

        + * 5. Products derived from this software may not be called "Apache" + *

        + * nor may "Apache" appear in their names without prior written + *

        + * permission of the Apache Group. + *

        + *

        + *

        + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED 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 APACHE SOFTWARE FOUNDATION OR + *

        + * ITS 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. + *

        + * ==================================================================== + *

        + *

        + *

        + * This software consists of voluntary contributions made by many + *

        + * individuals on behalf of the Apache Software Foundation. For more + *

        + * information on the Apache Software Foundation, please see + *

        + * . + */ + + private static final BitSet allowed_query = new BitSet(256); + + + static { + + for (int i = '0'; i <= '9'; i++) { + + allowed_query.set(i); + + } + + + for (int i = 'a'; i <= 'z'; i++) { + + allowed_query.set(i); + + } + + for (int i = 'A'; i <= 'Z'; i++) { + + allowed_query.set(i); + + } + + + allowed_query.set('-'); + + allowed_query.set('_'); + + allowed_query.set('.'); + + allowed_query.set('!'); + + allowed_query.set('~'); + + allowed_query.set('*'); + + allowed_query.set('\''); + + allowed_query.set('('); + + allowed_query.set(')'); + + } + + + /** + * Encodes URI string. This is a replacement for the java.net.URLEncode#encode(String, String) + *

        + * class which is broken under JDK 1.3. + *

        + *

        + * + * @param original the original character sequence + * @param charset the protocol charset + * @return URI character sequence + * @throws UnsupportedEncodingException unsupported character encoding + */ + + public static String URLEncode(String original, String charset) + + throws UnsupportedEncodingException + + { + + // encode original to uri characters. + + if (original == null) { + + return null; + + } + + // escape octet to uri characters. + + byte[] octets; + + + try { + + octets = original.getBytes(charset); + + } + + catch (UnsupportedEncodingException error) { + + throw new UnsupportedEncodingException(); + + } + + + StringBuffer buf = new StringBuffer(octets.length); + + + for (int i = 0; i < octets.length; i++) { + + char c = (char)octets[i]; + + if (allowed_query.get(c)) { + + buf.append(c); + + } + + else { + + buf.append('%'); + + byte b = octets[i]; // use the original byte value + + char hexadecimal = Character.forDigit((b >> 4) & 0xF, 16); + + buf.append(Character.toUpperCase(hexadecimal)); // high + + hexadecimal = Character.forDigit(b & 0xF, 16); + + buf.append(Character.toUpperCase(hexadecimal)); // low + + } + + } + + + return buf.toString(); + + } + + + private static final int fillchar = '='; + + private static final String cvt = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + + + "abcdefghijklmnopqrstuvwxyz" + + + "0123456789+/"; + + + /** + * Converts a line of text into an array of lower case words using a + *

        + * BreakIterator.wordInstance().

        + *

        + *

        + *

        + * This method is under the Jive Open Source Software License and was + *

        + * written by Mark Imbriaco. + * + * @param text a String of text to convert into an array of words + * @return text broken up into an array of words. + */ + + public static final String [] toLowerCaseWordArray(String text) { + + if (text == null || text.length() == 0) { + + return new String[0]; + + } + + + ArrayList wordList = new ArrayList(); + + BreakIterator boundary = BreakIterator.getWordInstance(); + + boundary.setText(text); + + int start = 0; + + + for (int end = boundary.next(); end != BreakIterator.DONE; + + start = end, end = boundary.next()) + + { + + String tmp = text.substring(start, end).trim(); + + // Remove characters that are not needed. + + tmp = replace(tmp, "+", ""); + + tmp = replace(tmp, "/", ""); + + tmp = replace(tmp, "\\", ""); + + tmp = replace(tmp, "#", ""); + + tmp = replace(tmp, "*", ""); + + tmp = replace(tmp, ")", ""); + + tmp = replace(tmp, "(", ""); + + tmp = replace(tmp, "&", ""); + + if (tmp.length() > 0) { + + wordList.add(tmp); + + } + + } + + return (String[])wordList.toArray(new String[wordList.size()]); + + } + + + /** + * Pseudo-random number generator object for use with randomString(). + *

        + * The Random class is not considered to be cryptographically secure, so + *

        + * only use these random Strings for low to medium security applications. + */ + + private static Random randGen = new Random(); + + + /** + * Array of numbers and letters of mixed case. Numbers appear in the list + *

        + * twice so that there is a more equal chance that a number will be picked. + *

        + * We can use the array to get a random number or letter by picking a random + *

        + * array index. + */ + + private static char[] numbersAndLetters = ("0123456789abcdefghijklmnopqrstuvwxyz" + + + "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ").toCharArray(); + + + /** + * Returns a random String of numbers and letters (lower and upper case) + *

        + * of the specified length. The method uses the Random class that is + *

        + * built-in to Java which is suitable for low to medium grade security uses. + *

        + * This means that the output is only pseudo random, i.e., each number is + *

        + * mathematically generated so is not truly random.

        + *

        + *

        + *

        + * The specified length must be at least one. If not, the method will return + *

        + * null. + * + * @param length the desired length of the random String to return. + * @return a random String of numbers and letters of the specified length. + */ + + public static final String randomString(int length) { + + if (length < 1) { + + return null; + + } + + // Create a char buffer to put random letters and numbers in. + + char [] randBuffer = new char[length]; + + for (int i = 0; i < randBuffer.length; i++) { + + randBuffer[i] = numbersAndLetters[randGen.nextInt(71)]; + + } + + return new String(randBuffer); + + } + + + /** + * Intelligently chops a String at a word boundary (whitespace) that occurs + *

        + * at the specified index in the argument or before. However, if there is a + *

        + * newline character before length, the String will be chopped + *

        + * there. If no newline or whitespace is found in string up to + *

        + * the index length, the String will chopped at length. + *

        + *

        + *

        + * For example, chopAtWord("This is a nice String", 10, -1) will return + *

        + * "This is a" which is the first word boundary less than or equal to 10 + *

        + * characters into the original String. + * + * @param string the String to chop. + * @param length the index in string to start looking for a + *

        + * whitespace boundary at. + * @param minLength the minimum length the word should be chopped at. This is helpful + *

        + * for words with no natural boundaries, ie: "thisisareallylonglonglongword". + *

        + * This must be smaller than length and can be -1 if no minLength is wanted + * @return a substring of string whose length is less than or + *

        + * equal to length, and that is chopped at whitespace. + */ + + public static final String chopAtWord(String string, int length, int minLength) { + + // guard clauses + + if (length < 2) { + + throw new IllegalArgumentException("Length specified (" + length + ") must be > 2"); + + } + + else if (minLength >= length) { + + throw new IllegalArgumentException("minLength must be smaller than length"); + + } + + + int sLength = (string == null) ? -1 : string.length(); + + // shortcircuit clauses + + if (sLength < 1) { + + return string; + + } + + // minLength specified, string is smaller than the minLength, return the string + + else if (minLength != -1 && sLength < minLength) { + + return string; + + } + + // no minLength specified, string is smaller than length + + else if (minLength == -1 && sLength < length) { + + return string; + + } + + + char [] charArray = string.toCharArray(); + + // String is longer than the length specified, attempt to find a newline + + // or a space + + if (sLength > length) { + + sLength = length; + + // First check if there is a newline character before length; if so, + + // chop word there. + + for (int i = 0; i < sLength - 1; i++) { + + // Windows + + if (charArray[i] == '\r' && charArray[i + 1] == '\n') { + + return string.substring(0, i + 1); + + } + + // Unix + + else if (charArray[i] == '\n') { + + return string.substring(0, i); + + } + + } + + // Also check boundary case of Unix newline + + if (charArray[sLength - 1] == '\n') { + + return string.substring(0, sLength - 1); + + } + + // No newline, so chop at the first whitespace. + + for (int i = sLength - 1; i > 0; i--) { + + if (charArray[i] == ' ') { + + return string.substring(0, i).trim(); + + } + + } + + } + + // String is shorter than length but longer than minLength, + + // make sure there is a space in the string before minLength + + else if (minLength != -1 && sLength > minLength) { + + for (int i = 0; i < minLength; i++) { + + if (charArray[i] == ' ') { + + return string; + + } + + } + + } + + // Did not find a word boundary, so return a string at the min length, if a min + + // length was specified: + + if (minLength > -1 && minLength <= string.length()) { + + return string.substring(0, minLength); + + } + + // Did not find word boundary or min length so return original String chopped at + + // specified length. + + return string.substring(0, length); + + } + + + /** + * Intelligently chops a String at a word boundary (whitespace) that occurs + *

        + * at the specified index in the argument or before. However, if there is a + *

        + * newline character before length, the String will be chopped + *

        + * there. If no newline or whitespace is found in string up to + *

        + * the index length, the String will chopped at length. + *

        + *

        + *

        + * For example, chopAtWord("This is a nice String", 10) will return + *

        + * "This is a" which is the first word boundary less than or equal to 10 + *

        + * characters into the original String. + * + * @param string the String to chop. + * @param length the index in string to start looking for a + *

        + * whitespace boundary at. + * @return a substring of string whose length is less than or + *

        + * equal to length, and that is chopped at whitespace. + */ + + public static final String chopAtWord(String string, int length) { + + return chopAtWord(string, length, -1); + + } + + + /** + * Returns a substring of the given string which represents the words around the given word. + *

        + * For example, passing in "This is a quick test a test", "{a,test}" and 5 would return a string + *

        + * of "This is a quick" - that's 5 characters (or to the end of the word, whichever + *

        + * is greater) on either side of "a". Also, since {a,test} is passed in a "a" is found + *

        + * first in the string, we base the substring off of the position of "a". The wordList is + *

        + * really just a list of strings to try - the first one found is used.

        + *

        + *

        + *

        + * Note: The wordList passed in should be lowercase. + * + * @param input The string to parse. + * @param wordList The words to look for - the first one found in the string is used. + * @param numChars The number of characters on either side to include in the chop. + * @return a substring of the given string matching the criteria, otherwise "". + */ + + public static String chopAtWordsAround(String input, String[] wordList, int numChars) { + + if (input == null || "".equals(input.trim()) || wordList == null + + || wordList.length == 0 || numChars == 0) + + { + + return ""; + + } + + String lc = input.toLowerCase(); + + for (int i = 0; i < wordList.length; i++) { + + int pos = lc.indexOf(wordList[i]); + + if (pos > -1) { + + int beginIdx = pos - numChars; + + if (beginIdx < 0) { + beginIdx = 0; + } + + int endIdx = pos + numChars; + + if (endIdx > input.length() - 1) { + endIdx = input.length() - 1; + } + + char[] chars = input.toCharArray(); + + while (beginIdx > 0 && chars[beginIdx] != ' ' && chars[beginIdx] != '\n' + + && chars[beginIdx] != '\r') + + { + + beginIdx--; + + } + + while (endIdx < input.length() && chars[endIdx] != ' ' + + && chars[endIdx] != '\n' && chars[endIdx] != '\r') + + { + + endIdx++; + + } + + return input.substring(beginIdx, endIdx); + + } + + } + + return input.substring(0, (input.length() >= 200) ? 200 : input.length()); + + } + + + /** + * Reformats a string where lines that are longer than width + *

        + * are split apart at the earliest wordbreak or at maxLength, whichever is + *

        + * sooner. If the width specified is less than 5 or greater than the input + *

        + * Strings length the string will be returned as is. + *

        + *

        + *

        + * Please note that this method can be lossy - trailing spaces on wrapped + *

        + * lines may be trimmed. + * + * @param input the String to reformat. + * @param width the maximum length of any one line. + * @return a new String with reformatted as needed. + */ + + public static String wordWrap(String input, int width, Locale locale) { + + // protect ourselves + + if (input == null) { + + return ""; + + } + + else if (width < 5) { + + return input; + + } + + else if (width >= input.length()) { + + return input; + + } + + + StringBuffer buf = new StringBuffer(input); + + boolean endOfLine = false; + + int lineStart = 0; + + + for (int i = 0; i < buf.length(); i++) { + + if (buf.charAt(i) == '\n') { + + lineStart = i + 1; + + endOfLine = true; + + } + + // handle splitting at width character + + if (i > lineStart + width - 1) { + + if (!endOfLine) { + + int limit = i - lineStart - 1; + + BreakIterator breaks = BreakIterator.getLineInstance(locale); + + breaks.setText(buf.substring(lineStart, i)); + + int end = breaks.last(); + + // if the last character in the search string isn't a space, + + // we can't split on it (looks bad). Search for a previous + + // break character + + if (end == limit + 1) { + + if (!Character.isWhitespace(buf.charAt(lineStart + end))) { + + end = breaks.preceding(end - 1); + + } + + } + + // if the last character is a space, replace it with a \n + + if (end != BreakIterator.DONE && end == limit + 1) { + + buf.replace(lineStart + end, lineStart + end + 1, "\n"); + + lineStart = lineStart + end; + + } + + // otherwise, just insert a \n + + else if (end != BreakIterator.DONE && end != 0) { + + buf.insert(lineStart + end, '\n'); + + lineStart = lineStart + end + 1; + + } + + else { + + buf.insert(i, '\n'); + + lineStart = i + 1; + + } + + } + + else { + + buf.insert(i, '\n'); + + lineStart = i + 1; + + endOfLine = false; + + } + + } + + } + + + return buf.toString(); + + } + + + /** + * Highlights words in a string. Words matching ignores case. The actual + *

        + * higlighting method is specified with the start and end higlight tags. + *

        + * Those might be beginning and ending HTML bold tags, or anything else.

        + *

        + *

        + *

        + * This method is under the Jive Open Source Software License and was + *

        + * written by Mark Imbriaco. + * + * @param string the String to highlight words in. + * @param words an array of words that should be highlighted in the string. + * @param startHighlight the tag that should be inserted to start highlighting. + * @param endHighlight the tag that should be inserted to end highlighting. + * @return a new String with the specified words highlighted. + */ + + public static final String highlightWords(String string, String[] words, + + String startHighlight, String endHighlight) + + { + + if (string == null || words == null || startHighlight == null || endHighlight == null) { + + return null; + + } + + + StringBuffer regexp = new StringBuffer(); + + regexp.append("(?i)\\b("); + + // Iterate through each word and generate a word list for the regexp. + + for (int x = 0; x < words.length; x++) { + + // Escape "$", "|", ".", "/", and "?" to keep us out of trouble in our regexp. + + words[x] = words[x].replaceAll("([\\$\\?\\|\\/\\.])", "\\\\$1"); + + regexp.append(words[x]); + + + if (x != words.length - 1) { + + regexp.append("|"); + + } + + } + + regexp.append(")"); + + + return string.replaceAll(regexp.toString(), startHighlight + "$1" + endHighlight); + + } + + + /** + * Escapes all necessary characters in the String so that it can be used + *

        + * in an XML doc. + * + * @param string the string to escape. + * @return the string with appropriate characters escaped. + */ + + public static final String escapeForXML(String string) { + + if (string == null) { + + return null; + + } + + char ch; + + int i = 0; + + int last = 0; + + char[] input = string.toCharArray(); + + int len = input.length; + + StringBuffer out = new StringBuffer((int)(len * 1.3)); + + for (; i < len; i++) { + + ch = input[i]; + + + if (ch > '>') { + + continue; + + } + else if (ch == '<') { + + if (i > last) { + + out.append(input, last, i - last); + + } + + last = i + 1; + + out.append(LT_ENCODE); + + } + else if (ch == '&') { + + if (i > last) { + + out.append(input, last, i - last); + + } + + last = i + 1; + + out.append(AMP_ENCODE); + + } + else if (ch == '"') { + + if (i > last) { + + out.append(input, last, i - last); + + } + + last = i + 1; + + out.append(QUOTE_ENCODE); + + } + else if (ch == 10 || ch == 13 || ch == 9) { + + continue; + + } + else if (ch < 32) { + + // Disallow all ASCII control characters, except space, + + // enter characters and tabs: + + if (i > last) { + + out.append(input, last, i - last); + + } + + last = i + 1; + + } + + } + + if (last == 0) { + + return string; + + } + + if (i > last) { + + out.append(input, last, i - last); + + } + + return out.toString(); + + } + + + /** + * Unescapes the String by converting XML escape sequences back into normal + *

        + * characters. + * + * @param string the string to unescape. + * @return the string with appropriate characters unescaped. + */ + + public static final String unescapeFromXML(String string) { + + string = replace(string, "<", "<"); + + string = replace(string, ">", ">"); + + string = replace(string, """, "\""); + + return replace(string, "&", "&"); + + } + + + private static final char[] zeroArray = + + "0000000000000000000000000000000000000000000000000000000000000000".toCharArray(); + + + /** + * Pads the supplied String with 0's to the specified length and returns + *

        + * the result as a new String. For example, if the initial String is + *

        + * "9999" and the desired length is 8, the result would be "00009999". + *

        + * This type of padding is useful for creating numerical values that need + *

        + * to be stored and sorted as character data. Note: the current + *

        + * implementation of this method allows for a maximum length of + *

        + * 64. + * + * @param string the original String to pad. + * @param length the desired length of the new padded String. + * @return a new String padded with the required number of 0's. + */ + + public static final String zeroPadString(String string, int length) { + + if (string == null || string.length() > length) { + + return string; + + } + + StringBuffer buf = new StringBuffer(length); + + buf.append(zeroArray, 0, length - string.length()).append(string); + + return buf.toString(); + + } + + + /** + * Formats a Date as a String. Depending on how Dates are defined in the database + *

        + * (character data or numberic), the format return will either be a fifteen character long String + *

        + * made up of the Date's padded millisecond value, or will simply be the Date's millesecond value. + * + * @return a Date encoded as a String. + */ + + public static final String dateToMillis(Date date) { + + return Long.toString(date.getTime()); + + } + + + /** + * Validate an email address. This isn't 100% perfect but should handle just about everything + *

        + * that is in common use. + * + * @param addr the email address to validate + * @return true if the address is valid, false otherwise + */ + + public static boolean isValidEmailAddress(String addr) { + + if (addr == null) { + + return false; + + } + + + addr = addr.trim(); + + + if (addr.length() == 0) { + + return false; + + } + + // basic address check + + Matcher matcher = basicAddressPattern.matcher(addr); + + if (!matcher.matches()) { + + return false; + + } + + String userPart = matcher.group(1); + + String domainPart = matcher.group(2); + + // user address check + + matcher = validUserPattern.matcher(userPart); + + if (!matcher.matches()) { + + return false; + + } + + // ip domain check + + matcher = ipDomainPattern.matcher(domainPart); + + if (matcher.matches()) { + + // if the pattern matched, check to make sure that the ip range is valid + + for (int i = 1; i < 5; i++) { + + String num = matcher.group(i); + + + if (num == null) { + + return false; + + } + + + if (Integer.parseInt(num) > 254) { + + return false; + + } + + } + + return true; + + } + + // symbolic domain check + + matcher = domainPattern.matcher(domainPart); + + if (matcher.matches()) { + + String tld = matcher.group(matcher.groupCount()); + + // Permit top-level-domains of 3 (includes dot separator) because these could be + + // country codes which we are not going to check for. + + matcher = tldPattern.matcher(tld); + + if (tld.length() != 3 && !matcher.matches()) { + + return false; + + } + + } + + else { + + return false; + + } + + // all tests passed + + return true; + + } + + // Testing method + + /* + + public static void main(String[] args) { + + + + { + + String test1 = "The quick brown fox jumped"; + + int chop11 = test1.length(); + + int chop12 = test1.length() - 1; + + int chop13 = test1.length() - 3; + + int chop14 = test1.length() - "jumped".length(); + + int chop15 = test1.length() - "ox jumped".length(); + + // run test 1 + + String result11 = chopAtWord(test1, chop11); + + String result12 = chopAtWord(test1, chop12); + + String result13 = chopAtWord(test1, chop13); + + String result14 = chopAtWord(test1, chop14); + + String result15 = chopAtWord(test1, chop15); + + // print results + + if (test1.equals(result11)) { System.err.println("Test 1.1 passed, result: " + result11); } + + else { System.err.println("Test 1.1 failed, result: " + result11); } + + + + if ("The quick brown fox".equals(result12)) { System.err.println("Test 1.2 passed, result: " + result12); } + + else { System.err.println("Test 1.2 failed, result: " + result12); } + + + + if ("The quick brown fox".equals(result13)) { System.err.println("Test 1.3 passed, result: " + result13); } + + else { System.err.println("Test 1.3 failed, result: " + result13); } + + + + if ("The quick brown fox".equals(result14)) { System.err.println("Test 1.4 passed, result: " + result14); } + + else { System.err.println("Test 1.4 failed, result: " + result14); } + + + + if ("The quick brown".equals(result15)) { System.err.println("Test 1.5 passed, result: " + result15); } + + else { System.err.println("Test 1.5 failed, result: " + result15); } + + } + + + + System.err.println(""); + + + + { + + String test2 = "The quick brown fox jumped"; + + int chop21 = test2.length(); + + int chop22 = test2.length() - 1; + + int chop23 = test2.length() - 3; + + int chop24 = test2.length() - "jumped".length(); + + int chop25 = test2.length() - "ox jumped".length(); + + // run test 1 + + String result21 = chopAtWord(test2, chop21, 0); + + String result22 = chopAtWord(test2, chop22, 0); + + String result23 = chopAtWord(test2, chop23, 0); + + String result24 = chopAtWord(test2, chop24, 0); + + String result25 = chopAtWord(test2, chop25, 0); + + // print results + + if (test2.equals(result21)) { System.err.println("Test 2.1 passed, result: " + result21); } + + else { System.err.println("Test 2.1 failed, result: " + result21); } + + + + if ("The quick brown fox".equals(result22)) { System.err.println("Test 2.2 passed, result: " + result22); } + + else { System.err.println("Test 2.2 failed, result: " + result22); } + + + + if ("The quick brown fox".equals(result23)) { System.err.println("Test 2.3 passed, result: " + result23); } + + else { System.err.println("Test 2.3 failed, result: " + result23); } + + + + if ("The quick brown fox".equals(result24)) { System.err.println("Test 2.4 passed, result: " + result24); } + + else { System.err.println("Test 2.4 failed, result: " + result24); } + + + + if ("The quick brown".equals(result25)) { System.err.println("Test 2.5 passed, result: " + result25); } + + else { System.err.println("Test 2.5 failed, result: " + result25); } + + } + + + + System.err.println(""); + + + + { + + String test3 = "Thequickbrownfoxjumped"; + + int chop31 = test3.length(); + + int chop32 = test3.length() - 1; + + int chop33 = test3.length() - 3; + + int chop34 = test3.length() - "jumped".length(); + + int chop35 = test3.length() - "ox jumped".length(); + + // run test 1 + + String result31 = chopAtWord(test3, chop31, "Thequickbrownfoxjumped".length()); + + String result32 = chopAtWord(test3, chop32, "Thequick".length()); + + String result33 = chopAtWord(test3, chop33, "Thequick".length()); + + String result34 = chopAtWord(test3, chop34, "Thequick".length()); + + String result35 = chopAtWord(test3, chop35, "Thequick".length()); + + // print results + + if ("Thequick".equals(result31)) { System.err.println("Test 3.1 passed, result: " + result31); } + + else { System.err.println("Test 3.1 failed, result: " + result31); } + + + + if ("Thequick".equals(result32)) { System.err.println("Test 3.2 passed, result: " + result32); } + + else { System.err.println("Test 3.2 failed, result: " + result32); } + + + + if ("Thequick".equals(result33)) { System.err.println("Test 3.3 passed, result: " + result33); } + + else { System.err.println("Test 3.3 failed, result: " + result33); } + + + + if ("Thequick".equals(result34)) { System.err.println("Test 3.4 passed, result: " + result34); } + + else { System.err.println("Test 3.4 failed, result: " + result34); } + + + + if ("Thequick".equals(result35)) { System.err.println("Test 3.5 passed, result: " + result35); } + + else { System.err.println("Test 3.5 failed, result: " + result35); } + + } + + + + System.err.println(""); + + + + { + + String test4 = "Java.Lang.ClassNotFoundException:com.Citrix"; + + int length = test4.length()-3; + + int min = 20; + + String result = chopAtWord(test4, length, min); + + System.err.println("result: " + result); + + } + + } + + */ + public static String keyStroke2String(KeyStroke key) { + StringBuffer s = new StringBuffer(50); + int m = key.getModifiers(); + + if ((m & (InputEvent.SHIFT_DOWN_MASK | InputEvent.SHIFT_MASK)) != 0) { + s.append("shift "); + } + if ((m & (InputEvent.CTRL_DOWN_MASK | InputEvent.CTRL_MASK)) != 0) { + s.append("ctrl "); + } + if ((m & (InputEvent.META_DOWN_MASK | InputEvent.META_MASK)) != 0) { + s.append("meta "); + } + if ((m & (InputEvent.ALT_DOWN_MASK | InputEvent.ALT_MASK)) != 0) { + s.append("alt "); + } + if ((m & (InputEvent.BUTTON1_DOWN_MASK | InputEvent.BUTTON1_MASK)) != 0) { + s.append("button1 "); + } + if ((m & (InputEvent.BUTTON2_DOWN_MASK | InputEvent.BUTTON2_MASK)) != 0) { + s.append("button2 "); + } + if ((m & (InputEvent.BUTTON3_DOWN_MASK | InputEvent.BUTTON3_MASK)) != 0) { + s.append("button3 "); + } + + switch (key.getKeyEventType()) { + case KeyEvent.KEY_TYPED: + s.append("typed "); + s.append(key.getKeyChar() + " "); + break; + case KeyEvent.KEY_PRESSED: + s.append("pressed "); + s.append(getKeyText(key.getKeyCode()) + " "); + break; + case KeyEvent.KEY_RELEASED: + s.append("released "); + s.append(getKeyText(key.getKeyCode()) + " "); + break; + default: + s.append("unknown-event-type "); + break; + } + + return s.toString(); + } + + public static String getKeyText(int keyCode) { + if (keyCode >= KeyEvent.VK_0 && keyCode <= KeyEvent.VK_9 || + keyCode >= KeyEvent.VK_A && keyCode <= KeyEvent.VK_Z) { + return String.valueOf((char)keyCode); + } + + switch (keyCode) { + case KeyEvent.VK_COMMA: + return "COMMA"; + case KeyEvent.VK_PERIOD: + return "PERIOD"; + case KeyEvent.VK_SLASH: + return "SLASH"; + case KeyEvent.VK_SEMICOLON: + return "SEMICOLON"; + case KeyEvent.VK_EQUALS: + return "EQUALS"; + case KeyEvent.VK_OPEN_BRACKET: + return "OPEN_BRACKET"; + case KeyEvent.VK_BACK_SLASH: + return "BACK_SLASH"; + case KeyEvent.VK_CLOSE_BRACKET: + return "CLOSE_BRACKET"; + + case KeyEvent.VK_ENTER: + return "ENTER"; + case KeyEvent.VK_BACK_SPACE: + return "BACK_SPACE"; + case KeyEvent.VK_TAB: + return "TAB"; + case KeyEvent.VK_CANCEL: + return "CANCEL"; + case KeyEvent.VK_CLEAR: + return "CLEAR"; + case KeyEvent.VK_SHIFT: + return "SHIFT"; + case KeyEvent.VK_CONTROL: + return "CONTROL"; + case KeyEvent.VK_ALT: + return "ALT"; + case KeyEvent.VK_PAUSE: + return "PAUSE"; + case KeyEvent.VK_CAPS_LOCK: + return "CAPS_LOCK"; + case KeyEvent.VK_ESCAPE: + return "ESCAPE"; + case KeyEvent.VK_SPACE: + return "SPACE"; + case KeyEvent.VK_PAGE_UP: + return "PAGE_UP"; + case KeyEvent.VK_PAGE_DOWN: + return "PAGE_DOWN"; + case KeyEvent.VK_END: + return "END"; + case KeyEvent.VK_HOME: + return "HOME"; + case KeyEvent.VK_LEFT: + return "LEFT"; + case KeyEvent.VK_UP: + return "UP"; + case KeyEvent.VK_RIGHT: + return "RIGHT"; + case KeyEvent.VK_DOWN: + return "DOWN"; + + // numpad numeric keys handled below + case KeyEvent.VK_MULTIPLY: + return "MULTIPLY"; + case KeyEvent.VK_ADD: + return "ADD"; + case KeyEvent.VK_SEPARATOR: + return "SEPARATOR"; + case KeyEvent.VK_SUBTRACT: + return "SUBTRACT"; + case KeyEvent.VK_DECIMAL: + return "DECIMAL"; + case KeyEvent.VK_DIVIDE: + return "DIVIDE"; + case KeyEvent.VK_DELETE: + return "DELETE"; + case KeyEvent.VK_NUM_LOCK: + return "NUM_LOCK"; + case KeyEvent.VK_SCROLL_LOCK: + return "SCROLL_LOCK"; + + case KeyEvent.VK_F1: + return "F1"; + case KeyEvent.VK_F2: + return "F2"; + case KeyEvent.VK_F3: + return "F3"; + case KeyEvent.VK_F4: + return "F4"; + case KeyEvent.VK_F5: + return "F5"; + case KeyEvent.VK_F6: + return "F6"; + case KeyEvent.VK_F7: + return "F7"; + case KeyEvent.VK_F8: + return "F8"; + case KeyEvent.VK_F9: + return "F9"; + case KeyEvent.VK_F10: + return "F10"; + case KeyEvent.VK_F11: + return "F11"; + case KeyEvent.VK_F12: + return "F12"; + case KeyEvent.VK_F13: + return "F13"; + case KeyEvent.VK_F14: + return "F14"; + case KeyEvent.VK_F15: + return "F15"; + case KeyEvent.VK_F16: + return "F16"; + case KeyEvent.VK_F17: + return "F17"; + case KeyEvent.VK_F18: + return "F18"; + case KeyEvent.VK_F19: + return "F19"; + case KeyEvent.VK_F20: + return "F20"; + case KeyEvent.VK_F21: + return "F21"; + case KeyEvent.VK_F22: + return "F22"; + case KeyEvent.VK_F23: + return "F23"; + case KeyEvent.VK_F24: + return "F24"; + + case KeyEvent.VK_PRINTSCREEN: + return "PRINTSCREEN"; + case KeyEvent.VK_INSERT: + return "INSERT"; + case KeyEvent.VK_HELP: + return "HELP"; + case KeyEvent.VK_META: + return "META"; + case KeyEvent.VK_BACK_QUOTE: + return "BACK_QUOTE"; + case KeyEvent.VK_QUOTE: + return "QUOTE"; + + case KeyEvent.VK_KP_UP: + return "KP_UP"; + case KeyEvent.VK_KP_DOWN: + return "KP_DOWN"; + case KeyEvent.VK_KP_LEFT: + return "KP_LEFT"; + case KeyEvent.VK_KP_RIGHT: + return "KP_RIGHT"; + + case KeyEvent.VK_DEAD_GRAVE: + return "DEAD_GRAVE"; + case KeyEvent.VK_DEAD_ACUTE: + return "DEAD_ACUTE"; + case KeyEvent.VK_DEAD_CIRCUMFLEX: + return "DEAD_CIRCUMFLEX"; + case KeyEvent.VK_DEAD_TILDE: + return "DEAD_TILDE"; + case KeyEvent.VK_DEAD_MACRON: + return "DEAD_MACRON"; + case KeyEvent.VK_DEAD_BREVE: + return "DEAD_BREVE"; + case KeyEvent.VK_DEAD_ABOVEDOT: + return "DEAD_ABOVEDOT"; + case KeyEvent.VK_DEAD_DIAERESIS: + return "DEAD_DIAERESIS"; + case KeyEvent.VK_DEAD_ABOVERING: + return "DEAD_ABOVERING"; + case KeyEvent.VK_DEAD_DOUBLEACUTE: + return "DEAD_DOUBLEACUTE"; + case KeyEvent.VK_DEAD_CARON: + return "DEAD_CARON"; + case KeyEvent.VK_DEAD_CEDILLA: + return "DEAD_CEDILLA"; + case KeyEvent.VK_DEAD_OGONEK: + return "DEAD_OGONEK"; + case KeyEvent.VK_DEAD_IOTA: + return "DEAD_IOTA"; + case KeyEvent.VK_DEAD_VOICED_SOUND: + return "DEAD_VOICED_SOUND"; + case KeyEvent.VK_DEAD_SEMIVOICED_SOUND: + return "DEAD_SEMIVOICED_SOUND"; + + case KeyEvent.VK_AMPERSAND: + return "AMPERSAND"; + case KeyEvent.VK_ASTERISK: + return "ASTERISK"; + case KeyEvent.VK_QUOTEDBL: + return "QUOTEDBL"; + case KeyEvent.VK_LESS: + return "LESS"; + case KeyEvent.VK_GREATER: + return "GREATER"; + case KeyEvent.VK_BRACELEFT: + return "BRACELEFT"; + case KeyEvent.VK_BRACERIGHT: + return "BRACERIGHT"; + case KeyEvent.VK_AT: + return "AT"; + case KeyEvent.VK_COLON: + return "COLON"; + case KeyEvent.VK_CIRCUMFLEX: + return "CIRCUMFLEX"; + case KeyEvent.VK_DOLLAR: + return "DOLLAR"; + case KeyEvent.VK_EURO_SIGN: + return "EURO_SIGN"; + case KeyEvent.VK_EXCLAMATION_MARK: + return "EXCLAMATION_MARK"; + case KeyEvent.VK_INVERTED_EXCLAMATION_MARK: + return "INVERTED_EXCLAMATION_MARK"; + case KeyEvent.VK_LEFT_PARENTHESIS: + return "LEFT_PARENTHESIS"; + case KeyEvent.VK_NUMBER_SIGN: + return "NUMBER_SIGN"; + case KeyEvent.VK_MINUS: + return "MINUS"; + case KeyEvent.VK_PLUS: + return "PLUS"; + case KeyEvent.VK_RIGHT_PARENTHESIS: + return "RIGHT_PARENTHESIS"; + case KeyEvent.VK_UNDERSCORE: + return "UNDERSCORE"; + + case KeyEvent.VK_FINAL: + return "FINAL"; + case KeyEvent.VK_CONVERT: + return "CONVERT"; + case KeyEvent.VK_NONCONVERT: + return "NONCONVERT"; + case KeyEvent.VK_ACCEPT: + return "ACCEPT"; + case KeyEvent.VK_MODECHANGE: + return "MODECHANGE"; + case KeyEvent.VK_KANA: + return "KANA"; + case KeyEvent.VK_KANJI: + return "KANJI"; + case KeyEvent.VK_ALPHANUMERIC: + return "ALPHANUMERIC"; + case KeyEvent.VK_KATAKANA: + return "KATAKANA"; + case KeyEvent.VK_HIRAGANA: + return "HIRAGANA"; + case KeyEvent.VK_FULL_WIDTH: + return "FULL_WIDTH"; + case KeyEvent.VK_HALF_WIDTH: + return "HALF_WIDTH"; + case KeyEvent.VK_ROMAN_CHARACTERS: + return "ROMAN_CHARACTERS"; + case KeyEvent.VK_ALL_CANDIDATES: + return "ALL_CANDIDATES"; + case KeyEvent.VK_PREVIOUS_CANDIDATE: + return "PREVIOUS_CANDIDATE"; + case KeyEvent.VK_CODE_INPUT: + return "CODE_INPUT"; + case KeyEvent.VK_JAPANESE_KATAKANA: + return "JAPANESE_KATAKANA"; + case KeyEvent.VK_JAPANESE_HIRAGANA: + return "JAPANESE_HIRAGANA"; + case KeyEvent.VK_JAPANESE_ROMAN: + return "JAPANESE_ROMAN"; + case KeyEvent.VK_KANA_LOCK: + return "KANA_LOCK"; + case KeyEvent.VK_INPUT_METHOD_ON_OFF: + return "INPUT_METHOD_ON_OFF"; + + case KeyEvent.VK_AGAIN: + return "AGAIN"; + case KeyEvent.VK_UNDO: + return "UNDO"; + case KeyEvent.VK_COPY: + return "COPY"; + case KeyEvent.VK_PASTE: + return "PASTE"; + case KeyEvent.VK_CUT: + return "CUT"; + case KeyEvent.VK_FIND: + return "FIND"; + case KeyEvent.VK_PROPS: + return "PROPS"; + case KeyEvent.VK_STOP: + return "STOP"; + + case KeyEvent.VK_COMPOSE: + return "COMPOSE"; + case KeyEvent.VK_ALT_GRAPH: + return "ALT_GRAPH"; + } + + if (keyCode >= KeyEvent.VK_NUMPAD0 && keyCode <= KeyEvent.VK_NUMPAD9) { + char c = (char)(keyCode - KeyEvent.VK_NUMPAD0 + '0'); + return "NUMPAD" + c; + } + + return "unknown(0x" + Integer.toString(keyCode, 16) + ")"; + } + + public static final String makeFirstWordCaptial(String word) { + if (word.length() < 2) { + return word; + } + + String firstWord = word.substring(0, 1); + String restOfWord = word.substring(1); + + return firstWord.toUpperCase() + restOfWord; + } +} + diff --git a/src/java/org/jivesoftware/spark/util/SwingWorker.java b/src/java/org/jivesoftware/spark/util/SwingWorker.java new file mode 100644 index 00000000..45f701b5 --- /dev/null +++ b/src/java/org/jivesoftware/spark/util/SwingWorker.java @@ -0,0 +1,164 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.util; + + +import org.jivesoftware.spark.plugin.Invokable; + +import javax.swing.SwingUtilities; + +/** + * Improvement version of the javax SwingWorker class to avoid deadlocks. This gives user + * multi-threaded abilities within their swing apps. + * + * @author Derek DeMoro + */ +public abstract class SwingWorker { + private Object value; // see getValue(), setValue() + private Thread thread; + private Invokable invokable; + + /** + * Class to maintain reference to current worker thread + * under separate synchronization control. + */ + private static class ThreadVar { + private Thread thread; + + ThreadVar(Thread t) { + thread = t; + } + + synchronized Thread get() { + return thread; + } + + synchronized void clear() { + thread = null; + } + } + + private ThreadVar threadVar; + + /** + * Get the value produced by the worker thread, or null if it + * hasn't been constructed yet. + */ + protected synchronized Object getValue() { + return value; + } + + /** + * Set the value produced by worker thread + */ + private synchronized void setValue(Object x) { + value = x; + } + + /** + * Compute the value to be returned by the get method. + */ + public abstract Object construct(); + + /** + * Called on the event dispatching thread (not on the worker thread) + * after the construct method has returned. + */ + public void finished() { + if (invokable != null) { + invokable.invoke(null); + } + } + + /** + * A new method that interrupts the worker thread. Call this method + * to force the worker to stop what it's doing. + */ + public void interrupt() { + Thread t = threadVar.get(); + if (t != null) { + t.interrupt(); + } + threadVar.clear(); + } + + + /** + * Return the value created by the construct method. + * Returns null if either the constructing thread or the current + * thread was interrupted before a value was produced. + * + * @return the value created by the construct method + */ + public Object get() { + while (true) { + Thread t = threadVar.get(); + if (t == null) { + return getValue(); + } + try { + t.join(); + } + catch (InterruptedException e) { + Thread.currentThread().interrupt(); // propagate + return null; + } + } + } + + + /** + * Start a thread that will call the construct method + * and then exit. + */ + public SwingWorker() { + final Runnable doFinished = new Runnable() { + public void run() { + finished(); + } + }; + + Runnable doConstruct = new Runnable() { + public void run() { + try { + setValue(construct()); + } + finally { + threadVar.clear(); + } + SwingUtilities.invokeLater(new Runnable() { + public void run() { + finished(); + } + }); + + } + }; + + Thread t = new Thread(doConstruct); + threadVar = new ThreadVar(t); + } + + /** + * Start the worker thread. + */ + public void start() { + Thread t = threadVar.get(); + if (t != null) { + t.start(); + } + } + + public void setInvokable(Invokable invoker) { + invokable = invoker; + } +} + diff --git a/src/java/org/jivesoftware/spark/util/URLFileSystem.java b/src/java/org/jivesoftware/spark/util/URLFileSystem.java new file mode 100644 index 00000000..e20f8b49 --- /dev/null +++ b/src/java/org/jivesoftware/spark/util/URLFileSystem.java @@ -0,0 +1,555 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.util; + +import org.jivesoftware.spark.util.log.Log; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.lang.reflect.Method; +import java.net.URL; +import java.net.URLConnection; + +/** + * URLFileSystem class handles some of the most common + * functionallity when working with URLs. + * + * @version 1.0, 03/12/14 + */ + +public class URLFileSystem { + public static void main(String args[]) { + } + + public static String getContents(URL url) { + try { + return getContents(url.openStream()); + } + catch (IOException e) { + return null; + } + } + + public static String getContents(InputStream is) { + byte[] buffer = new byte[2048]; + int length = -1; + StringBuffer sb = new StringBuffer(); + try { + while ((length = is.read(buffer)) != -1) { + sb.append(new String(buffer, 0, length)); + } + return sb.toString(); + } + catch (IOException e) { + return null; + } + } + + /** + * Copies the contents at src to dst. + */ + public static void copy(URL src, File dst) throws IOException { + InputStream in = null; + OutputStream out = null; + try { + in = src.openStream(); + out = new FileOutputStream(dst); + dst.mkdirs(); + copy(in, out); + } + finally { + try { + if (in != null) in.close(); + } + catch (IOException e) { + } + try { + if (out != null) out.close(); + } + catch (IOException e) { + } + } + } + + /** + * Common code for copy routines. By convention, the streams are + * closed in the same method in which they were opened. Thus, + * this method does not close the streams when the copying is done. + */ + public static void copy(InputStream in, OutputStream out) + throws IOException { + final byte[] buffer = new byte[4096]; + while (true) { + final int bytesRead = in.read(buffer); + if (bytesRead < 0) { + break; + } + out.write(buffer, 0, bytesRead); + } + + out.flush(); + } + + /** + * If a dot ('.') occurs in the path portion of the {@link URL}, then + * all of the text starting at the last dot is returned, including + * the dot. If the last dot is also the last character in the path, + * then the dot by itself is returned. If there is no dot in the + * path, then the empty string is returned. + */ + public static String getSuffix(URL url) { + final String path = url.getPath(); + int lastDot = path.lastIndexOf('.'); + + return (lastDot >= 0) ? path.substring(lastDot) : ""; + } + + //-------------------------------------------------------------------------- + // URLFileSystemHelper public API... + //-------------------------------------------------------------------------- + + /** + * Returns a canonical form of the {@link URL}, if one is available. + *

        + *

        + * The default implementation just returns the specified {@link URL} + * as-is. + */ + public URL canonicalize(URL url) { + return url; + } + + /** + * Tests whether the application can read the resource at the + * specified {@link URL}. + * + * @return true if and only if the specified + * {@link URL} points to a resource that exists and can be + * read by the application; false otherwise. + */ + public boolean canRead(URL url) { + try { + final URLConnection urlConnection = url.openConnection(); + return urlConnection.getDoInput(); + } + catch (Exception e) { + return false; + } + } + + + /** + * Tests whether the application can modify the resource at the + * specified {@link URL}. + * + * @return true if and only if the specified + * {@link URL} points to a file that exists and the + * application is allowed to write to the file; false + * otherwise. + */ + public boolean canWrite(URL url) { + try { + final URLConnection urlConnection = url.openConnection(); + return urlConnection.getDoOutput(); + } + catch (Exception e) { + return false; + } + } + + /** + * Tests whether the application can create the resource at the specified + * {@link URL}. + * + * @return true if the resource at the specified {@link URL} + * exists or can be created; false otherwise. + */ + public boolean canCreate(URL url) { + return true; + } + + /** + * Tests whether the specified {@link URL} is valid. If the resource + * pointed by the {@link URL} exists the method returns true. + * If the resource does not exist, the method tests that all components + * of the path can be created. + * + * @return true if the {@link URL} is valid. + */ + public boolean isValid(URL url) { + if (exists(url)) { + return true; + } + + return canCreate(url); + } + + /** + * Returns true if the specified {@link URL} points to a + * resource that currently exists; returns false + * otherwise.

        + *

        + * The default implementation simply returns false + * without doing anything. + */ + public static boolean exists(URL url) { + return url2File(url).exists(); + } + + public static boolean mkdirs(URL url) { + final File file = url2File(url); + if (!file.exists()) { + return file.mkdirs(); + } + return true; + } + + + /** + * Returns the name of the file contained by the {@link URL}, not + * including any protocol, hostname authentication, directory path, + * anchor, or query. This simply returns the simple filename. For + * example, if you pass in an {@link URL} whose string representation + * is: + *

        + *

        + * protocol://host:1010/dir1/dir2/file.ext#anchor?query + *
        + *

        + * the returned value is "file.ext" (without the + * quotes).

        + *

        + * The returned file name should only be used for display purposes + * and not for opening streams or otherwise trying to locate the + * resource indicated by the {@link URL}. + */ + public static String getFileName(URL url) { + if (url == null) { + return ""; + } + + final String path = url.getPath(); + if (path.equals("/")) { + return "/"; + } + final int lastSep = path.lastIndexOf('/'); + if (lastSep == path.length() - 1) { + final int lastSep2 = path.lastIndexOf('/', lastSep - 1); + return path.substring(lastSep2 + 1, lastSep); + } + else { + return path.substring(lastSep + 1); + } + } + + + /** + * Returns the number of bytes contained in the resource that the + * specified {@link URL} points to. If the length cannot be + * determined, -1 is returned.

        + *

        + * The default implementation attempts to get the content length from + * the {@link URLConnection} associated with the {@link URL}. If that + * fails for some reason (e.g. the resource does not exist, there was + * some other an I/O exception, etc.), -1 is returned. + * + * @see URLConnection + */ + public long getLength(URL url) { + try { + final URLConnection urlConnection = url.openConnection(); + return urlConnection.getContentLength(); + } + catch (Exception e) { + return -1; + } + } + + + /** + * Returns the name of the file contained by the {@link URL}, not + * including any protocol, hostname authentication, directory path, + * anchor, or query. This simply returns the simple filename. For + * example, if you pass in an {@link URL} whose string representation + * is: + *

        + *

        + * protocol://host:1010/dir1/dir2/file.ext1.ext2#anchor?query + *
        + *

        + * the returned value is "file" (without the quotes).

        + *

        + * The returned file name should only be used for display purposes + * and not for opening streams or otherwise trying to locate the + * resource indicated by the {@link URL}.

        + *

        + * The default implementation first calls {@link #getFileName(URL)} to + * get the file name part. Then all characters starting with the + * first occurrence of '.' are removed. The remaining string is then + * returned. + */ + public static String getName(URL url) { + final String fileName = getFileName(url); + final int firstDot = fileName.indexOf('.'); + return firstDot > 0 ? fileName.substring(0, firstDot) : fileName; + } + + + /** + * Returns the path part of the {@link URL}. + *

        + * The default implementation delegates to {@link URL#getPath()}. + */ + public String getPath(URL url) { + return url.getPath(); + } + + + /** + * Returns the path part of the {@link URL} without the last file + * extension. To clarify, the following examples demonstrate the + * different cases that come up: + *

        + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
        Path part of input {@link URL}
        Output {@link String} + *
        /dir/file.ext/dir/file
        /dir/file.ext1.ext2/dir/file.ext1
        /dir1.ext1/dir2.ext2/file.ext1.ext2/dir1.ext1/dir2.ext2/file.ext1
        /file.ext/file
        /dir.ext/file/dir.ext/file
        /dir/file/dir/file
        /file/file
        /.ext/
        + *

        + * The default implementation gets the path from {@link + * #getPath(URL)} and then trims off all of the characters beginning + * with the last "." in the path, if and only if the last "." comes + * after the last "/" in the path. If the last "." comes before + * the last "/" or if there is no "." at all, then the entire path + * is returned. + */ + public String getPathNoExt(URL url) { + final String path = getPath(url); + final int lastSlash = path.lastIndexOf("/"); + final int lastDot = path.lastIndexOf("."); + if (lastDot <= lastSlash) { + // When the lastDot < lastSlash, it means that one of the + // directories has an extension, but the filename itself has + // no extension. In this case, returning the whole path is + // the correct behavior. + // + // The only time that lastDot and lastSlash can be equal occurs + // when both of them are -1. In that case, returning the whole + // path is the correct behavior. + return path; + } + // At this point, we know that lastDot must be non-negative, so + // we can return the whole path string up to the last dot. + return path.substring(0, lastDot); + } + + + /** + * Returns the platform-dependent String representation of the + * {@link URL}; the returned string should be considered acceptable + * for users to read. In general, the returned string should omit + * as many parts of the {@link URL} as possible. For the "file" + * protocol, therefore, the platform pathname should just be the + * pathname alone (no protocol) using the appropriate file separator + * character for the current platform. For other protocols, it may + * be necessary to reformat the {@link URL} string into a more + * human-readable form. That decision is left to each + * URLFileSystemHelper implementor. + *

        + * The default implementation returns url.toString(). + * If the {@link URL} is null, the empty string is + * returned. + * + * @return The path portion of the specified {@link URL} in + * platform-dependent notation. This value should only be used for + * display purposes and not for opening streams or otherwise trying + * to locate the document. + */ + public String getPlatformPathName(URL url) { + return url != null ? url.toString() : ""; + } + + public static URL newFileURL(File file) { + String filePath = file.getPath(); + if (filePath == null) { + return null; + } + final String path = sanitizePath(filePath); + return newURL("file", path); + } + + public static URL newFileURL(String filePath) { + if (filePath == null) { + return null; + } + final String path = sanitizePath(filePath); + return newURL("file", path); + } + + /** + * This "sanitizes" the specified string path by converting all + * {@link File#separatorChar} characters to forward slash ('/'). + * Also, a leading forward slash is prepended if the path does + * not begin with one. + */ + private static String sanitizePath(String path) { + if (File.separatorChar != '/') { + path = path.replace(File.separatorChar, '/'); + } + if (!path.startsWith("/")) { + path = "/" + path; + } + return path; + } + + public static URL newURL(String protocol, String path) { + return newURL(protocol, null, null, -1, path, null, null); + } + + //-------------------------------------------------------------------------- + // direct access factory methods... + //-------------------------------------------------------------------------- + + /** + * Creates a new {@link URL} whose parts have the exact values that + * are specified. In general, you should avoid calling this + * method directly.

        + *

        + * This method is the ultimate place where all of the other + * URLFactory methods end up when creating an + * {@link URL}. + *

        + * Non-sanitizing. + */ + public static URL newURL(String protocol, String userinfo, + String host, int port, + String path, String query, String ref) { + try { + final URL seed = new URL(protocol, "", -1, ""); + final String authority = port < 0 ? host : host + ":" + port; + final Object[] args = new Object[] + { + protocol, host, new Integer(port), + authority, userinfo, + path, query, ref, + }; + + // IMPORTANT -- this *MUST* be the only place in URLFactory where + // the URL.set(...) method is used. --jdijamco + urlSet.invoke(seed, args); + return seed; + } + catch (Exception e) { + Log.error(e); + return null; + } + } + + /** + * This {@link Method} is used to work-around a bug in Sun's + * java.net.URL implementation. The {@link Method} + * allows us to set the parts of an {@link URL} directly. + */ + private static final Method urlSet; + + static { + final Class str = String.class; + try { + urlSet = URL.class.getDeclaredMethod("set", new Class[]{str, str, int.class, str, str, str, str, str}); + + // IMPORTANT: This call to setAccessible effectively overrides + // the "protected" visibility constraint on the URL.set(...) + // method. This is an intentional breaking of encapsulation to + // work-around severe bugs in Sun's java.net.URL implementation + // having to do with: + // * poor handling of special characters like #, ?, and ; + // * poor handling of whitespace + // * no go way to disambiguate UNC paths on Win32 + // + // The use of setAccessible is an implementation detail of the + // URLFactory, and if Sun some day fixes their java.net.URL + // implementation to address the problems above, we may be able + // to change the internal mechanism to use the regular URL + // constructors. For the time being, after having weighed the + // various other alternatives and even tried some of them (and + // encountered other problems), our decision is to force our way + // through to the URL.set(...) method, taking care to invoke + // it method exactly once per URL object with the exactly the + // right arguments. + // + // --jdijamco March 14, 2001 + urlSet.setAccessible(true); + } + catch (NoSuchMethodException e) { + //!jdijamco -- Have some fallback option so that doesn't + //!jdijamco -- just totally barf and prevent the IDE from starting? + throw new IllegalStateException(); + } + } + + public static final File url2File(URL url) { + final String path = url.getPath(); + final File file = new File(path); + return file; + } + + public static URL getParent(URL url) { + final File file = url2File(url); + final File parentFile = file.getParentFile(); + if (parentFile != null && !file.equals(parentFile)) { + try { + return parentFile.toURL(); + } + catch (Exception ex) { + return null; + } + } + return null; + } + + +} diff --git a/src/java/org/jivesoftware/spark/util/WindowsFileSystemView.java b/src/java/org/jivesoftware/spark/util/WindowsFileSystemView.java new file mode 100644 index 00000000..5c27c958 --- /dev/null +++ b/src/java/org/jivesoftware/spark/util/WindowsFileSystemView.java @@ -0,0 +1,115 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.util; + +/******************************************************************** + * Copyright (c) Open Java Extensions, Andrew Selkirk LGPL License * + ********************************************************************/ + +// Imports + +import javax.swing.filechooser.FileSystemView; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Method; + +/** + * WindowsFileSystemView + * + * @author Andrew Selkirk + * @version 1.0 + */ +public class WindowsFileSystemView extends FileSystemView { + + //------------------------------------------------------------- + // Variables -------------------------------------------------- + //------------------------------------------------------------- + + /** + * noArgs + */ + private static final Object[] noArgs = null; // TODO + + /** + * noArgTypes + */ + private static final Class[] noArgTypes = null; // TODO + + /** + * listRootsMethod + */ + private static Method listRootsMethod = null; // TODO + + /** + * listRootsMethodChecked + */ + private static boolean listRootsMethodChecked = false; // TODO + + //------------------------------------------------------------- + // Initialization --------------------------------------------- + //------------------------------------------------------------- + + /** + * Constructor WindowsFileSystemView + */ + public WindowsFileSystemView() { + // TODO + } // WindowsFileSystemView() + + //------------------------------------------------------------- + // Methods ---------------------------------------------------- + //------------------------------------------------------------- + + /** + * isRoot + * + * @param value0 TODO + * @returns boolean + */ + public boolean isRoot(File value0) { + return false; // TODO + } // isRoot() + + /** + * createNewFolder + * + * @param value0 TODO + * @throws IOException TODO + * @returns File + */ + public File createNewFolder(File value0) throws IOException { + return null; // TODO + } // createNewFolder() + + /** + * isHiddenFile + * + * @param value0 TODO + * @returns boolean + */ + public boolean isHiddenFile(File value0) { + return false; // TODO + } // isHiddenFile() + + /** + * getRoots + * + * @returns File[] + */ + public File[] getRoots() { + return null; // TODO + } // getRoots() + + +} // WindowsFileSystemView + + diff --git a/src/java/org/jivesoftware/spark/util/log/Log.java b/src/java/org/jivesoftware/spark/util/log/Log.java new file mode 100644 index 00000000..e71cabfb --- /dev/null +++ b/src/java/org/jivesoftware/spark/util/log/Log.java @@ -0,0 +1,112 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.spark.util.log; + +import org.jivesoftware.Spark; + +import java.io.File; +import java.io.IOException; +import java.util.logging.FileHandler; +import java.util.logging.Level; +import java.util.logging.SimpleFormatter; + +/** + * Creates and writes out messages to a a log file. This should be used for all error handling within + * the Agent application. + */ +public class Log { + private static File LOG_FILE; + private static java.util.logging.Logger LOGGER; + + private Log() { + // Do not allow initialization + } + + static { + if (!Spark.getLogDirectory().exists()) { + Spark.getLogDirectory().mkdirs(); + } + + LOG_FILE = new File(Spark.getLogDirectory(), "spark-error.log"); + + + try { + // Create an appending file handler + boolean append = true; + FileHandler handler = new FileHandler(LOG_FILE.getCanonicalPath(), append); + handler.setFormatter(new SimpleFormatter()); + + // Add to the desired logger + LOGGER = java.util.logging.Logger.getAnonymousLogger(); + LOGGER.addHandler(handler); + } + catch (IOException e) { + Log.error(e); + } + } + + /** + * Logs all error messages to default error logger. + * + * @param message a message to append to log file. + * @param ex the exception being thrown. + */ + public static void error(String message, Throwable ex) { + LOGGER.log(Level.SEVERE, message, ex); + } + + /** + * Logs all error messages to default error logger. + * + * @param ex the exception being thrown. + */ + public static void error(Throwable ex) { + LOGGER.log(Level.SEVERE, "", ex); + } + + /** + * Log a warning message to the default logger. + * + * @param message the message to log. + * @param ex the exception. + */ + public static void warning(String message, Throwable ex) { + ex.printStackTrace(); + } + + public static void warning(String message) { + LOGGER.log(Level.WARNING, message); + } + + /** + * Logs all error messages to default error logger. + * + * @param message a message to append to log file. + */ + public static void error(String message) { + LOGGER.log(Level.SEVERE, message); + } + + /** + * Logs all messages to standard errout for debugging purposes. + * To use, pass in the VM Parameters debug.mode=true. + *

        + * ex. (-Ddebug.mode=true) + * + * @param message the message to print out. + */ + public static void debug(String message) { + if (System.getProperty("debug.mode") != null) { + LOGGER.info(message); + } + } + +} diff --git a/src/java/org/jivesoftware/spark/util/log/package.html b/src/java/org/jivesoftware/spark/util/log/package.html new file mode 100644 index 00000000..ca11d28b --- /dev/null +++ b/src/java/org/jivesoftware/spark/util/log/package.html @@ -0,0 +1 @@ +Provides support to handle error and warning logging within Spark. \ No newline at end of file diff --git a/src/java/org/jivesoftware/spark/util/package.html b/src/java/org/jivesoftware/spark/util/package.html new file mode 100644 index 00000000..22e5f1c4 --- /dev/null +++ b/src/java/org/jivesoftware/spark/util/package.html @@ -0,0 +1 @@ +Provides helpful utilities used for Spark. diff --git a/src/java/org/jivesoftware/sparkimpl/plugin/alerts/BroadcastDialog.java b/src/java/org/jivesoftware/sparkimpl/plugin/alerts/BroadcastDialog.java new file mode 100644 index 00000000..d64cbb77 --- /dev/null +++ b/src/java/org/jivesoftware/sparkimpl/plugin/alerts/BroadcastDialog.java @@ -0,0 +1,170 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.sparkimpl.plugin.alerts; + +import org.jivesoftware.spark.SparkManager; +import org.jivesoftware.spark.component.TitlePanel; + +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.Icon; +import javax.swing.JDialog; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTextArea; +import javax.swing.JTextField; + +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.GridBagLayout; +import java.awt.event.ActionEvent; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; + + +/** + * BroadcastDialog class is used to create broadcast messages to be sent. + * + * @version 1.0, 06/28/2005 + */ +public final class BroadcastDialog implements PropertyChangeListener { + private JTextArea textArea; + private JTextField subjectField; + private JOptionPane optionPane; + private JDialog dialog; + + private String stringValue; + private int width = 400; + private int height = 250; + + /** + * Empty Constructor. + */ + public BroadcastDialog() { + } + + /** + * Returns the input from a user. + * + * @param title the title of the dialog. + * @param description the dialog description. + * @param icon the icon to use. + * @param width the dialog width + * @param height the dialog height + * @return the users input. + */ + public String getInput(String title, String description, Icon icon, int width, int height) { + this.width = width; + this.height = height; + + return getInput(title, description, icon, SparkManager.getMainWindow()); + } + + /** + * Prompt and return input. + * + * @param title the title of the dialog. + * @param description the dialog description. + * @param icon the icon to use. + * @param parent the parent to use. + * @return the user input. + */ + public String getInput(String title, String description, Icon icon, Component parent) { + textArea = new JTextArea(); + subjectField = new JTextField(); + textArea.setLineWrap(true); + + TitlePanel titlePanel = new TitlePanel(title, description, icon, true); + + // Construct main panel w/ layout. + final JPanel mainPanel = new JPanel(); + mainPanel.setLayout(new BorderLayout()); + mainPanel.add(titlePanel, BorderLayout.NORTH); + + + final JPanel centerPanel = new JPanel(new GridBagLayout()); + + // The user should only be able to close this dialog. + final Object[] options = {"Ok", "Cancel"}; + optionPane = new JOptionPane(new JScrollPane(textArea), JOptionPane.PLAIN_MESSAGE, + JOptionPane.OK_CANCEL_OPTION, null, options, options[0]); + + mainPanel.add(optionPane, BorderLayout.CENTER); + + // Let's make sure that the dialog is modal. Cannot risk people + // losing this dialog. + JOptionPane p = new JOptionPane(); + dialog = p.createDialog(parent, title); + dialog.setModal(true); + dialog.pack(); + dialog.setSize(width, height); + dialog.setContentPane(mainPanel); + dialog.setLocationRelativeTo(parent); + optionPane.addPropertyChangeListener(this); + + // Add Key Listener to Send Field + textArea.addKeyListener(new KeyAdapter() { + public void keyPressed(KeyEvent e) { + if (e.getKeyChar() == KeyEvent.VK_TAB) { + optionPane.requestFocus(); + } + else if (e.getKeyChar() == KeyEvent.VK_ESCAPE) { + dialog.dispose(); + } + } + }); + + textArea.requestFocus(); + + + dialog.setVisible(true); + return stringValue; + } + + /** + * Move to focus forward action. + */ + public Action nextFocusAction = new AbstractAction("Move Focus Forwards") { + public void actionPerformed(ActionEvent evt) { + ((Component)evt.getSource()).transferFocus(); + } + }; + + /** + * Moves the focus backwards in the dialog. + */ + public Action prevFocusAction = new AbstractAction("Move Focus Backwards") { + public void actionPerformed(ActionEvent evt) { + ((Component)evt.getSource()).transferFocusBackward(); + } + }; + + public void propertyChange(PropertyChangeEvent e) { + String value = (String)optionPane.getValue(); + if ("Cancel".equals(value)) { + stringValue = null; + dialog.setVisible(false); + } + else if ("Ok".equals(value)) { + stringValue = textArea.getText(); + if (stringValue.trim().length() == 0) { + stringValue = null; + } + else { + stringValue = stringValue.trim(); + } + dialog.setVisible(false); + } + } +} \ No newline at end of file diff --git a/src/java/org/jivesoftware/sparkimpl/plugin/alerts/BroadcastPlugin.java b/src/java/org/jivesoftware/sparkimpl/plugin/alerts/BroadcastPlugin.java new file mode 100644 index 00000000..9aaf7afc --- /dev/null +++ b/src/java/org/jivesoftware/sparkimpl/plugin/alerts/BroadcastPlugin.java @@ -0,0 +1,297 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.sparkimpl.plugin.alerts; + +import org.jivesoftware.resource.SparkRes; +import org.jivesoftware.smack.PacketListener; +import org.jivesoftware.smack.filter.PacketFilter; +import org.jivesoftware.smack.filter.PacketTypeFilter; +import org.jivesoftware.smack.packet.Message; +import org.jivesoftware.smack.packet.Packet; +import org.jivesoftware.smack.util.StringUtils; +import org.jivesoftware.smackx.packet.VCard; +import org.jivesoftware.spark.SparkManager; +import org.jivesoftware.spark.component.InputDialog; +import org.jivesoftware.spark.component.MessageDialog; +import org.jivesoftware.spark.component.RolloverButton; +import org.jivesoftware.spark.plugin.ContextMenuListener; +import org.jivesoftware.spark.plugin.Plugin; +import org.jivesoftware.spark.ui.ChatRoom; +import org.jivesoftware.spark.ui.ContactGroup; +import org.jivesoftware.spark.ui.ContactItem; +import org.jivesoftware.spark.ui.ContactList; +import org.jivesoftware.spark.ui.TranscriptWindow; +import org.jivesoftware.spark.ui.status.StatusBar; +import org.jivesoftware.spark.util.GraphicUtils; +import org.jivesoftware.spark.util.ModelUtil; +import org.jivesoftware.spark.util.ResourceUtils; +import org.jivesoftware.sparkimpl.plugin.manager.Enterprise; + +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.BorderFactory; +import javax.swing.ImageIcon; +import javax.swing.JMenu; +import javax.swing.JMenuItem; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JPopupMenu; +import javax.swing.SwingUtilities; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.MouseEvent; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; + +/** + * Handles broadcasts from server and allows for roster wide broadcasts. + */ +public class BroadcastPlugin implements Plugin, PacketListener { + + public void initialize() { + boolean enabled = Enterprise.containsFeature(Enterprise.BROADCAST_FEATURE); + if (!enabled) { + return; + } + PacketFilter serverFilter = new PacketTypeFilter(Message.class); + SparkManager.getConnection().addPacketListener(this, serverFilter); + + // Register with action menu + final JMenu actionsMenu = SparkManager.getMainWindow().getMenuByName("Actions"); + JMenuItem broadcastMenu = new JMenuItem("Broadcast Message", SparkRes.getImageIcon(SparkRes.MEGAPHONE_16x16)); + ResourceUtils.resButton(broadcastMenu, "&Broadcast Message"); + actionsMenu.add(broadcastMenu); + broadcastMenu.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + broadcastToRoster(); + } + }); + + // Register with action menu + JMenuItem startConversationtMenu = new JMenuItem("", SparkRes.getImageIcon(SparkRes.SMALL_MESSAGE_IMAGE)); + ResourceUtils.resButton(startConversationtMenu, "&Start Conversation"); + actionsMenu.add(startConversationtMenu); + startConversationtMenu.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + ContactList contactList = SparkManager.getWorkspace().getContactList(); + Collection selectedUsers = contactList.getSelectedUsers(); + String selectedUser = ""; + Iterator selectedUsersIterator = selectedUsers.iterator(); + if (selectedUsersIterator.hasNext()) { + ContactItem contactItem = (ContactItem)selectedUsersIterator.next(); + selectedUser = contactItem.getFullJID(); + } + + String jid = (String)JOptionPane.showInputDialog(SparkManager.getMainWindow(), "Enter Address", "Start Conversation", JOptionPane.QUESTION_MESSAGE, null, null, selectedUser); + if (ModelUtil.hasLength(jid) && ModelUtil.hasLength(StringUtils.parseServer(jid))) { + if (ModelUtil.hasLength(jid) && jid.indexOf('@') == -1) { + // Append server address + jid = jid + "@" + SparkManager.getConnection().getServiceName(); + } + + ChatRoom chatRoom = SparkManager.getChatManager().createChatRoom(jid, jid, jid); + SparkManager.getChatManager().getChatContainer().activateChatRoom(chatRoom); + } + } + }); + + // Add send to selected users. + final ContactList contactList = SparkManager.getWorkspace().getContactList(); + contactList.addContextMenuListener(new ContextMenuListener() { + public void poppingUp(Object component, JPopupMenu popup) { + if (component instanceof ContactGroup) { + final ContactGroup group = (ContactGroup)component; + Action broadcastMessageAction = new AbstractAction() { + public void actionPerformed(ActionEvent e) { + broadcastToGroup(group); + } + }; + + broadcastMessageAction.putValue(Action.NAME, "Broadcast message to group"); + broadcastMessageAction.putValue(Action.SMALL_ICON, SparkRes.getImageIcon(SparkRes.MEGAPHONE_16x16)); + popup.add(broadcastMessageAction); + } + } + + public void poppingDown(JPopupMenu popup) { + + } + + public boolean handleDefaultAction(MouseEvent e) { + return false; + } + }); + + // Add Broadcast to roster + StatusBar statusBar = SparkManager.getWorkspace().getStatusBar(); + JPanel commandPanel = statusBar.getCommandPanel(); + + RolloverButton broadcastToRosterButton = new RolloverButton(SparkRes.getImageIcon(SparkRes.MEGAPHONE_16x16)); + broadcastToRosterButton.setToolTipText("Send a broadcast"); + commandPanel.add(broadcastToRosterButton); + statusBar.invalidate(); + statusBar.validate(); + statusBar.repaint(); + + broadcastToRosterButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + broadcastToRoster(); + } + }); + } + + public void shutdown() { + + } + + public boolean canShutDown() { + return false; + } + + public void processPacket(final Packet packet) { + SwingUtilities.invokeLater(new Runnable() { + public void run() { + final Message message = (Message)packet; + boolean broadcast = message.getProperty("broadcast") != null; + + if ((broadcast || message.getType() == Message.Type.NORMAL) && message.getBody() != null) { + showAlert((Message)packet); + } + else { + String host = SparkManager.getSessionManager().getServerAddress(); + String from = packet.getFrom() != null ? packet.getFrom() : ""; + if (host.equalsIgnoreCase(from) || !ModelUtil.hasLength(from)) { + showAlert((Message)packet); + } + } + } + }); + + } + + /** + * Show Server Alert. + * + * @param message the message to show. + */ + private void showAlert(Message message) { + final String body = message.getBody(); + String subject = message.getSubject(); + + StringBuffer buf = new StringBuffer(); + if (subject != null) { + buf.append("Subject: ").append(subject); + buf.append("\n\n"); + } + + buf.append(body); + + String host = SparkManager.getSessionManager().getServerAddress(); + String from = message.getFrom() != null ? message.getFrom() : ""; + + + final TranscriptWindow window = new TranscriptWindow(); + window.insertCustomMessage(null, buf.toString()); + + JPanel p = new JPanel(); + p.setLayout(new BorderLayout()); + p.add(window, BorderLayout.CENTER); + p.setBorder(BorderFactory.createLineBorder(Color.lightGray)); + + if (host.equalsIgnoreCase(from) || message.getFrom() == null) { + SparkToaster toaster = new SparkToaster(); + toaster.setDisplayTime(30000); + toaster.setTitle(host); + toaster.setBorder(BorderFactory.createBevelBorder(0)); + toaster.showToaster(SparkRes.getImageIcon(SparkRes.INFORMATION_IMAGE), buf.toString()); + } + else { + VCard vcard = SparkManager.getVCardManager().getVCard(StringUtils.parseBareAddress(message.getFrom())); + ImageIcon icon = null; + if (vcard != null && vcard.getAvatar() != null) { + icon = new ImageIcon(vcard.getAvatar()); + icon = GraphicUtils.scaleImageIcon(icon, 48, 48); + } + else if (icon == null || icon.getIconWidth() == -1) { + icon = SparkRes.getImageIcon(SparkRes.USER_HEADSET_24x24); + } + String nickname = SparkManager.getUserManager().getUserNicknameFromJID(message.getFrom()); + MessageDialog.showComponent("Broadcast from " + nickname, "", icon, p, SparkManager.getMainWindow(), 400, 400, false); + } + + } + + /** + * Broadcasts a message to all in the roster. + */ + private void broadcastToRoster() { + InputDialog dialog = new InputDialog(); + final String messageText = dialog.getInput("Broadcast Message", "Enter message to broadcast to your entire roster list.", SparkRes.getImageIcon(SparkRes.BLANK_IMAGE), SparkManager.getMainWindow()); + if (ModelUtil.hasLength(messageText)) { + ContactList contactList = SparkManager.getWorkspace().getContactList(); + List list = contactList.getContactGroups(); + Iterator iter = list.iterator(); + while (iter.hasNext()) { + ContactGroup contactGroup = (ContactGroup)iter.next(); + Iterator items = contactGroup.getContactItems().iterator(); + while (items.hasNext()) { + ContactItem item = (ContactItem)items.next(); + if (item != null && item.getFullJID() != null) { + final Message message = new Message(); + message.setTo(item.getFullJID()); + message.setBody(messageText); + message.setProperty("broadcast", true); + SparkManager.getConnection().sendPacket(message); + } + } + + } + JOptionPane.showMessageDialog(SparkManager.getMainWindow(), "The broadcast message has been sent.", "Message Broadcasted", JOptionPane.INFORMATION_MESSAGE); + + } + + } + + /** + * Broadcasts a message to all selected users. + * + * @param group the Contact Group to send the messages to. + */ + private void broadcastToGroup(ContactGroup group) { + StringBuffer buf = new StringBuffer(); + InputDialog dialog = new InputDialog(); + final String messageText = dialog.getInput("Broadcast Message", "Enter message to broadcast to " + group.getGroupName(), SparkRes.getImageIcon(SparkRes.BLANK_IMAGE), SparkManager.getMainWindow()); + if (ModelUtil.hasLength(messageText)) { + Iterator items = group.getContactItems().iterator(); + while (items.hasNext()) { + ContactItem item = (ContactItem)items.next(); + final Message message = new Message(); + message.setTo(item.getFullJID()); + message.setProperty("broadcast", true); + message.setBody(messageText); + buf.append(item.getNickname()).append("\n"); + SparkManager.getConnection().sendPacket(message); + } + + JOptionPane.showMessageDialog(SparkManager.getMainWindow(), "The message has been broadcasted to the following users:\n" + buf.toString(), "Message Broadcasted", JOptionPane.INFORMATION_MESSAGE); + } + } + + public void uninstall() { + // Do nothing. + } + + +} diff --git a/src/java/org/jivesoftware/sparkimpl/plugin/alerts/SparkToaster.java b/src/java/org/jivesoftware/sparkimpl/plugin/alerts/SparkToaster.java new file mode 100644 index 00000000..0f5ad40a --- /dev/null +++ b/src/java/org/jivesoftware/sparkimpl/plugin/alerts/SparkToaster.java @@ -0,0 +1,547 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.sparkimpl.plugin.alerts; + +/** + * SparkToaster is an improvement of Java Toaster which is a java utility class for your swing applications + * that show an animate box coming from the bottom of your screen + * with a notification message and/or an associated image + * (like msn online/offline notifications). + * + * Toaster panel in windows system follow the taskbar; So if + * the taskbar is into the bottom the panel coming from the bottom + * and if the taskbar is on the top then the panel coming from the top. + * + * This is a simple example of utilization: + * + * import com.nitido.utils.toaster.*; + * import javax.swing.*; + * + */ + + +import org.jivesoftware.resource.SparkRes; +import org.jivesoftware.spark.component.BackgroundPanel; +import org.jivesoftware.spark.component.RolloverButton; +import org.jivesoftware.spark.util.log.Log; + +import javax.swing.Icon; +import javax.swing.JLabel; +import javax.swing.JSeparator; +import javax.swing.JTextArea; +import javax.swing.border.Border; + +import java.awt.Color; +import java.awt.Cursor; +import java.awt.Font; +import java.awt.GraphicsEnvironment; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.Rectangle; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; + +/** + * Class to show tosters in multiplatform + * + * @author daniele piras + */ +public class SparkToaster { + + /** + * The default Hand cursor. + */ + public static final Cursor HAND_CURSOR = new Cursor(Cursor.HAND_CURSOR); + + /** + * The default Text Cursor. + */ + public static final Cursor DEFAULT_CURSOR = new Cursor(Cursor.DEFAULT_CURSOR); + + // Width of the toster + private int toasterWidth = 200; + + // Height of the toster + private int toasterHeight = 185; + + // Step for the toaster + private int step = 20; + + // Step time + private int stepTime = 20; + + // Show time + private int displayTime = 3000; + + // Current number of toaster... + private int currentNumberOfToaster = 0; + + // Last opened toaster + private int maxToaster = 0; + + // Max number of toasters for the sceen + private int maxToasterInSceen; + + // Font used to display message + private Font font; + + // Color for border + private Color borderColor; + + // Color for toaster + private Color toasterColor; + + // Set message color + private Color messageColor; + + // Set the margin + int margin; + + // Flag that indicate if use alwaysOnTop or not. + // method always on top start only SINCE JDK 5 ! + boolean useAlwaysOnTop = true; + + private static final long serialVersionUID = 1L; + + private String title; + + private Border border; + + /** + * Constructor to initialized toaster component... + */ + public SparkToaster() { + // Set default font... + font = new Font("Dialog", Font.PLAIN, 11); + // Border color + borderColor = new Color(245, 153, 15); + toasterColor = Color.WHITE; + messageColor = Color.BLACK; + useAlwaysOnTop = true; + } + + /** + * Class that rappresent a single toaster + * + * @author daniele piras + */ + class SingleToaster extends javax.swing.JWindow { + private static final long serialVersionUID = 1L; + + // Label to store Icon + private JLabel iconLabel = new JLabel(); + + // Text area for the message + private JTextArea message = new JTextArea(); + + private JSeparator seperator = new JSeparator(JSeparator.HORIZONTAL); + + /** + * Simple costructor that initialized components... + */ + public SingleToaster() { + initComponents(); + } + + /** + * Function to initialized components + */ + private void initComponents() { + message.setFont(getToasterMessageFont()); + + BackgroundPanel mainPanel = new BackgroundPanel(); + message.setOpaque(false); + mainPanel.setLayout(new GridBagLayout()); + mainPanel.setBackground(getToasterColor()); + message.setBackground(getToasterColor()); + message.setMargin(new Insets(2, 2, 2, 2)); + message.setLineWrap(true); + message.setWrapStyleWord(true); + + message.setForeground(getMessageColor()); + JLabel titleLabel = new JLabel(getTitle()); + titleLabel.setForeground(new Color(87, 166, 211)); + titleLabel.setFont(new Font("Dialog", Font.BOLD, 13)); + + RolloverButton closeButton = new RolloverButton(SparkRes.getImageIcon(SparkRes.CLOSE_IMAGE)); + + seperator.setForeground(Color.lightGray); + mainPanel.add(iconLabel, new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 0, 0, 0), 0, 0)); + mainPanel.add(titleLabel, new GridBagConstraints(1, 0, 2, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 0, 0), 0, 0)); + mainPanel.add(closeButton, new GridBagConstraints(3, 0, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 0, 0, 0), 0, 0)); + + closeButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + setVisible(false); + dispose(); + } + }); + + if (border != null) { + mainPanel.setBorder(border); + } + + mainPanel.add(seperator, new GridBagConstraints(1, 1, 2, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.BOTH, new Insets(0, 5, 0, 0), 0, 0)); + + message.setForeground(Color.BLACK); + + + mainPanel.add(message, new GridBagConstraints(1, 2, 2, 1, 1.0, 1.0, GridBagConstraints.WEST, GridBagConstraints.BOTH, new Insets(2, 5, 2, 5), 0, 0)); + + getContentPane().add(mainPanel); + + + message.addMouseListener(new MouseAdapter() { + public void mouseClicked(MouseEvent e) { + setVisible(false); + dispose(); + } + + public void mouseEntered(MouseEvent e) { + message.setCursor(HAND_CURSOR); + } + + public void mouseExited(MouseEvent e) { + message.setCursor(DEFAULT_CURSOR); + } + }); + + + pack(); + setSize(toasterWidth, toasterHeight); + } + + + /** + * Start toaster animation... + */ + public void animate() { + (new Animation(this)).start(); + } + + } + + /** + * Class that manage the animation + */ + class Animation extends Thread { + SingleToaster toaster; + + public Animation(SingleToaster toaster) { + this.toaster = toaster; + } + + + /** + * Animate vertically the toaster. The toaster could be moved from bottom + * to upper or to upper to bottom + * + * @param posx + * @throws InterruptedException + */ + protected void animateVertically(int posx, int fromY, int toY) throws InterruptedException { + + toaster.setLocation(posx, fromY); + if (toY < fromY) { + for (int i = fromY; i > toY; i -= step) { + toaster.setLocation(posx, i); + Thread.sleep(stepTime); + } + } + else { + for (int i = fromY; i < toY; i += step) { + toaster.setLocation(posx, i); + Thread.sleep(stepTime); + } + } + toaster.setLocation(posx, toY); + } + + public void run() { + try { + boolean animateFromBottom = true; + GraphicsEnvironment ge = GraphicsEnvironment + .getLocalGraphicsEnvironment(); + Rectangle screenRect = ge.getMaximumWindowBounds(); + + int screenHeight = (int)screenRect.height; + + int startYPosition; + int stopYPosition; + + if (screenRect.y > 0) { + animateFromBottom = false; // Animate from top! + } + + maxToasterInSceen = screenHeight / toasterHeight; + + + int posx = (int)screenRect.width - toasterWidth - 1; + + toaster.setLocation(posx, screenHeight); + toaster.setVisible(true); + if (useAlwaysOnTop) { + toaster.setAlwaysOnTop(true); + } + + if (animateFromBottom) { + startYPosition = screenHeight; + stopYPosition = startYPosition - toasterHeight - 1; + if (currentNumberOfToaster > 0) { + stopYPosition = stopYPosition - (maxToaster % maxToasterInSceen * toasterHeight); + } + else { + maxToaster = 0; + } + } + else { + startYPosition = screenRect.y - toasterHeight; + stopYPosition = screenRect.y; + + if (currentNumberOfToaster > 0) { + stopYPosition = stopYPosition + (maxToaster % maxToasterInSceen * toasterHeight); + } + else { + maxToaster = 0; + } + } + + currentNumberOfToaster++; + maxToaster++; + + + animateVertically(posx, startYPosition, stopYPosition); + Thread.sleep(displayTime); + animateVertically(posx, stopYPosition, startYPosition); + + currentNumberOfToaster--; + toaster.setVisible(false); + toaster.dispose(); + } + catch (Exception e) { + Log.error(e); + } + } + } + + + /** + * Show a toaster with the specified message and the associated icon. + */ + public void showToaster(Icon icon, String msg) { + SingleToaster singleToaster = new SingleToaster(); + if (icon != null) { + singleToaster.iconLabel.setIcon(icon); + } + singleToaster.message.setText(msg); + singleToaster.animate(); + } + + /** + * Show a toaster with the specified message. + */ + public void showToaster(String msg) { + showToaster(null, msg); + } + + /** + * @return Returns the font + */ + public Font getToasterMessageFont() { + // TODO Auto-generated method stub + return font; + } + + /** + * Set the font for the message + */ + public void setToasterMessageFont(Font f) { + font = f; + } + + + /** + * @return Returns the borderColor. + */ + public Color getBorderColor() { + return borderColor; + } + + + /** + * @param borderColor The borderColor to set. + */ + public void setBorderColor(Color borderColor) { + this.borderColor = borderColor; + } + + + /** + * @return Returns the displayTime. + */ + public int getDisplayTime() { + return displayTime; + } + + + /** + * @param displayTime The displayTime to set. + */ + public void setDisplayTime(int displayTime) { + this.displayTime = displayTime; + } + + + /** + * @return Returns the margin. + */ + public int getMargin() { + return margin; + } + + + /** + * @param margin The margin to set. + */ + public void setMargin(int margin) { + this.margin = margin; + } + + + /** + * @return Returns the messageColor. + */ + public Color getMessageColor() { + return messageColor; + } + + + /** + * @param messageColor The messageColor to set. + */ + public void setMessageColor(Color messageColor) { + this.messageColor = messageColor; + } + + + /** + * @return Returns the step. + */ + public int getStep() { + return step; + } + + + /** + * @param step The step to set. + */ + public void setStep(int step) { + this.step = step; + } + + + /** + * @return Returns the stepTime. + */ + public int getStepTime() { + return stepTime; + } + + + /** + * @param stepTime The stepTime to set. + */ + public void setStepTime(int stepTime) { + this.stepTime = stepTime; + } + + + /** + * @return Returns the toasterColor. + */ + public Color getToasterColor() { + return toasterColor; + } + + + /** + * @param toasterColor The toasterColor to set. + */ + public void setToasterColor(Color toasterColor) { + this.toasterColor = toasterColor; + } + + + /** + * @return Returns the toasterHeight. + */ + public int getToasterHeight() { + return toasterHeight; + } + + + /** + * @param toasterHeight The toasterHeight to set. + */ + public void setToasterHeight(int toasterHeight) { + this.toasterHeight = toasterHeight; + } + + + /** + * @return Returns the toasterWidth. + */ + public int getToasterWidth() { + return toasterWidth; + } + + + /** + * @param toasterWidth The toasterWidth to set. + */ + public void setToasterWidth(int toasterWidth) { + this.toasterWidth = toasterWidth; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public Border getBorder() { + return border; + } + + public void setBorder(Border border) { + this.border = border; + } + + /** + * Simple Example... + */ + public static void main(String[] args) { + SparkToaster toasterManager = new SparkToaster(); + toasterManager.setTitle("Spark"); + toasterManager.showToaster(SparkRes.getImageIcon(SparkRes.SMALL_MESSAGE_IMAGE), " \t Message sent successfully.\n" + + "\n" + + "Send Administrative Message\n" + + "Use the form below to send an administrative message to all users.\n" + + "To: \tAll Online Users"); + } + +} + diff --git a/src/java/org/jivesoftware/sparkimpl/plugin/bookmarks/BookmarkItem.java b/src/java/org/jivesoftware/sparkimpl/plugin/bookmarks/BookmarkItem.java new file mode 100644 index 00000000..b4c1011f --- /dev/null +++ b/src/java/org/jivesoftware/sparkimpl/plugin/bookmarks/BookmarkItem.java @@ -0,0 +1,114 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.sparkimpl.plugin.bookmarks; + +import org.jivesoftware.resource.SparkRes; +import org.jivesoftware.smackx.bookmark.BookmarkedConference; +import org.jivesoftware.smackx.bookmark.BookmarkedURL; +import org.jivesoftware.spark.ui.conferences.ConferenceUtils; +import org.jivesoftware.spark.util.BrowserLauncher; +import org.jivesoftware.spark.util.SwingWorker; +import org.jivesoftware.spark.util.log.Log; + +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.UIManager; + +import java.awt.Color; +import java.awt.Font; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.event.ActionEvent; +import java.io.IOException; + +/** + * + */ +public class BookmarkItem extends JPanel { + + private JLabel imageLabel; + private JLabel nameLabel; + private JLabel descriptionLabel; + + public Action action; + + public BookmarkItem() { + setLayout(new GridBagLayout()); + + imageLabel = new JLabel(); + nameLabel = new JLabel(); + descriptionLabel = new JLabel(); + + + descriptionLabel.setFont(new Font("Dialog", Font.PLAIN, 11)); + descriptionLabel.setForeground((Color)UIManager.get("ContactItemDescription.foreground")); + descriptionLabel.setHorizontalTextPosition(JLabel.LEFT); + descriptionLabel.setHorizontalAlignment(JLabel.LEFT); + + + this.setOpaque(true); + + add(imageLabel, new GridBagConstraints(0, 0, 1, 2, 0.0, 0.0, GridBagConstraints.NORTH, GridBagConstraints.HORIZONTAL, new Insets(0, 10, 0, 0), 0, 0)); + add(nameLabel, new GridBagConstraints(1, 0, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(0, 5, 0, 0), 0, 0)); + add(descriptionLabel, new GridBagConstraints(1, 1, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(0, 5, 2, 0), 0, 0)); + } + + public void addURL(final BookmarkedURL bookmark) { + imageLabel.setIcon(SparkRes.getImageIcon(SparkRes.LINK_16x16)); + nameLabel.setText(bookmark.getName()); + descriptionLabel.setText(bookmark.getURL()); + + action = new AbstractAction() { + public void actionPerformed(ActionEvent e) { + try { + BrowserLauncher.openURL(bookmark.getURL()); + } + catch (IOException e1) { + Log.error(e1); + } + } + }; + } + + public void addConferenceRoom(final BookmarkedConference bookmark) { + imageLabel.setIcon(SparkRes.getImageIcon(SparkRes.CONFERENCE_IMAGE_16x16)); + nameLabel.setText(bookmark.getName()); + descriptionLabel.setText(bookmark.getJid()); + action = new AbstractAction() { + public void actionPerformed(ActionEvent e) { + SwingWorker worker = new SwingWorker() { + public Object construct() { + try { + Thread.sleep(10); + } + catch (InterruptedException e1) { + Log.error(e1); + } + return "ok"; + } + + public void finished() { + ConferenceUtils.autoJoinConferenceRoom(bookmark.getName(), bookmark.getJid(), bookmark.getPassword()); + } + }; + worker.start(); + } + }; + } + + public void invokeAction() { + action.actionPerformed(null); + } + +} diff --git a/src/java/org/jivesoftware/sparkimpl/plugin/bookmarks/BookmarkPlugin.java b/src/java/org/jivesoftware/sparkimpl/plugin/bookmarks/BookmarkPlugin.java new file mode 100644 index 00000000..1a3d0b17 --- /dev/null +++ b/src/java/org/jivesoftware/sparkimpl/plugin/bookmarks/BookmarkPlugin.java @@ -0,0 +1,88 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.sparkimpl.plugin.bookmarks; + +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smackx.PrivateDataManager; +import org.jivesoftware.smackx.bookmark.BookmarkedConference; +import org.jivesoftware.smackx.bookmark.BookmarkedURL; +import org.jivesoftware.smackx.bookmark.Bookmarks; +import org.jivesoftware.spark.SparkManager; +import org.jivesoftware.spark.plugin.Plugin; +import org.jivesoftware.spark.ui.ContactList; +import org.jivesoftware.spark.util.SwingWorker; +import org.jivesoftware.spark.util.log.Log; + +import java.util.Collection; +import java.util.Iterator; + +/** + * Allows for adding and removal of Bookmarks within Spark. + */ +public class BookmarkPlugin implements Plugin { + + public void initialize() { + final SwingWorker bookmarkThreadWorker = new SwingWorker() { + public Object construct() { + // Register own provider for simpler implementation. + PrivateDataManager.addPrivateDataProvider("storage", "storage:bookmarks", new Bookmarks.Provider()); + PrivateDataManager manager = new PrivateDataManager(SparkManager.getConnection()); + Bookmarks bookmarks = null; + try { + bookmarks = (Bookmarks)manager.getPrivateData("storage", "storage:bookmarks"); + } + catch (XMPPException e) { + Log.error(e); + } + return bookmarks; + + } + + public void finished() { + final Bookmarks bookmarks = (Bookmarks)get(); + if (bookmarks != null) { + Collection bookmarkedConferences = bookmarks.getBookmarkedConferences(); + final Collection bookmarkedLinks = bookmarks.getBookmarkedURLS(); + + BookmarkUI bookmarkUI = new BookmarkUI(); + + Iterator links = bookmarkedLinks.iterator(); + while (links.hasNext()) { + final BookmarkedURL bookmarkedLink = (BookmarkedURL)links.next(); + bookmarkUI.addURL(bookmarkedLink); + } + + ContactList contactList = SparkManager.getWorkspace().getContactList(); + contactList.getMainPanel().add(bookmarkUI); + + Iterator conferences = bookmarkedConferences.iterator(); + while (conferences.hasNext()) { + final BookmarkedConference bookmarkedConference = (BookmarkedConference)conferences.next(); + bookmarkUI.addConference(bookmarkedConference); + } + } + } + }; + + bookmarkThreadWorker.start(); + + } + + public void shutdown() { + } + + public boolean canShutDown() { + return false; + } + + public void uninstall() { + } +} diff --git a/src/java/org/jivesoftware/sparkimpl/plugin/bookmarks/BookmarkUI.java b/src/java/org/jivesoftware/sparkimpl/plugin/bookmarks/BookmarkUI.java new file mode 100644 index 00000000..a3394fd9 --- /dev/null +++ b/src/java/org/jivesoftware/sparkimpl/plugin/bookmarks/BookmarkUI.java @@ -0,0 +1,87 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.sparkimpl.plugin.bookmarks; + +import org.jivesoftware.smackx.bookmark.BookmarkedConference; +import org.jivesoftware.smackx.bookmark.BookmarkedURL; +import org.jivesoftware.spark.component.panes.CollapsiblePane; +import org.jivesoftware.spark.component.renderer.JPanelRenderer; +import org.jivesoftware.spark.util.GraphicUtils; + +import javax.swing.DefaultListModel; +import javax.swing.JList; +import javax.swing.JPanel; + +import java.awt.BorderLayout; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; + +/** + * + */ +public class BookmarkUI extends JPanel { + + private CollapsiblePane pane; + private DefaultListModel model; + private JList list; + + + public BookmarkUI() { + setLayout(new BorderLayout()); + pane = new CollapsiblePane(); + pane.setTitle("Bookmarks"); + + model = new DefaultListModel(); + list = new JList(model); + + add(pane, BorderLayout.CENTER); + pane.setContentPane(list); + list.setCellRenderer(new JPanelRenderer()); + + list.addMouseListener(new MouseAdapter() { + public void mouseClicked(MouseEvent mouseEvent) { + if (mouseEvent.getClickCount() == 2) { + BookmarkItem item = (BookmarkItem)list.getSelectedValue(); + item.invokeAction(); + } + } + }); + + + pane.addMouseListener(new MouseAdapter() { + public void mouseEntered(MouseEvent e) { + list.setCursor(GraphicUtils.HAND_CURSOR); + } + + public void mouseExited(MouseEvent e) { + list.setCursor(GraphicUtils.DEFAULT_CURSOR); + } + }); + } + + public void addURL(BookmarkedURL url) { + BookmarkItem item = new BookmarkItem(); + item.addURL(url); + model.addElement(item); + } + + public void addConference(BookmarkedConference conference) { + BookmarkItem item = new BookmarkItem(); + item.addConferenceRoom(conference); + + model.addElement(item); + if (conference.isAutoJoin()) { + item.invokeAction(); + } + } + + +} diff --git a/src/java/org/jivesoftware/sparkimpl/plugin/chat/ChatArgumentsPlugin.java b/src/java/org/jivesoftware/sparkimpl/plugin/chat/ChatArgumentsPlugin.java new file mode 100644 index 00000000..850ff402 --- /dev/null +++ b/src/java/org/jivesoftware/sparkimpl/plugin/chat/ChatArgumentsPlugin.java @@ -0,0 +1,47 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.sparkimpl.plugin.chat; + +import org.jivesoftware.Spark; +import org.jivesoftware.smack.util.StringUtils; +import org.jivesoftware.spark.SparkManager; +import org.jivesoftware.spark.plugin.Plugin; +import org.jivesoftware.spark.ui.conferences.ConferenceUtils; + +public class ChatArgumentsPlugin implements Plugin { + + public void initialize() { + String start_chat_jid = Spark.getArgumentValue("start_chat_jid"); + String start_chat_muc = Spark.getArgumentValue("start_chat_muc"); + + if (start_chat_jid != null) { + String nickname = StringUtils.parseName(start_chat_jid); + SparkManager.getChatManager().createChatRoom(start_chat_jid, nickname, start_chat_jid); + } + + if (start_chat_muc != null) { + ConferenceUtils.autoJoinConferenceRoom(start_chat_muc, start_chat_muc, null); + } + + } + + public void shutdown() { + + } + + public boolean canShutDown() { + return false; + } + + public void uninstall() { + // Do nothing. + } +} diff --git a/src/java/org/jivesoftware/sparkimpl/plugin/chat/PresenceChangePlugin.java b/src/java/org/jivesoftware/sparkimpl/plugin/chat/PresenceChangePlugin.java new file mode 100644 index 00000000..c5393a16 --- /dev/null +++ b/src/java/org/jivesoftware/sparkimpl/plugin/chat/PresenceChangePlugin.java @@ -0,0 +1,135 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.sparkimpl.plugin.chat; + +import org.jivesoftware.resource.SparkRes; +import org.jivesoftware.smack.PacketListener; +import org.jivesoftware.smack.filter.PacketTypeFilter; +import org.jivesoftware.smack.packet.Message; +import org.jivesoftware.smack.packet.Packet; +import org.jivesoftware.smack.packet.Presence; +import org.jivesoftware.smack.util.StringUtils; +import org.jivesoftware.spark.ChatManager; +import org.jivesoftware.spark.SparkManager; +import org.jivesoftware.spark.plugin.ContextMenuListener; +import org.jivesoftware.spark.plugin.Plugin; +import org.jivesoftware.spark.ui.ChatRoom; +import org.jivesoftware.spark.ui.ContactItem; +import org.jivesoftware.spark.ui.ContactList; + +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.JPopupMenu; + +import java.awt.event.ActionEvent; +import java.awt.event.MouseEvent; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +public class PresenceChangePlugin implements Plugin { + + private final Set contacts = new HashSet(); + + + public void initialize() { + // Listen for right-clicks on ContactItem + final ContactList contactList = SparkManager.getWorkspace().getContactList(); + + final Action listenAction = new AbstractAction() { + public void actionPerformed(ActionEvent e) { + ContactItem item = (ContactItem)contactList.getSelectedUsers().iterator().next(); + contacts.add(item); + } + }; + + listenAction.putValue(Action.NAME, "Notify when available"); + listenAction.putValue(Action.SMALL_ICON, SparkRes.getImageIcon(SparkRes.SMALL_ALARM_CLOCK)); + + final Action removeAction = new AbstractAction() { + public void actionPerformed(ActionEvent e) { + ContactItem item = (ContactItem)contactList.getSelectedUsers().iterator().next(); + contacts.remove(item); + } + }; + + removeAction.putValue(Action.NAME, "Remove notification when available"); + removeAction.putValue(Action.SMALL_ICON, SparkRes.getImageIcon(SparkRes.SMALL_DELETE)); + + + contactList.addContextMenuListener(new ContextMenuListener() { + public void poppingUp(Object object, JPopupMenu popup) { + if (object instanceof ContactItem) { + ContactItem item = (ContactItem)object; + if (item.getPresence() == null || (item.getPresence().getMode() != Presence.Mode.AVAILABLE && item.getPresence().getMode() != Presence.Mode.CHAT)) { + if (contacts.contains(item)) { + popup.add(removeAction); + } + else { + popup.add(listenAction); + } + } + } + } + + public void poppingDown(JPopupMenu popup) { + + } + + public boolean handleDefaultAction(MouseEvent e) { + return false; + } + }); + + // Check presence changes + SparkManager.getConnection().addPacketListener(new PacketListener() { + public void processPacket(Packet packet) { + Presence presence = (Presence)packet; + if (presence == null || (presence.getMode() != Presence.Mode.AVAILABLE && presence.getMode() != Presence.Mode.CHAT)) { + return; + } + String from = presence.getFrom(); + + final Iterator contactItems = new ArrayList(contacts).iterator(); + while (contactItems.hasNext()) { + ContactItem item = (ContactItem)contactItems.next(); + if (item.getFullJID().equals(StringUtils.parseBareAddress(from))) { + contacts.remove(item); + + ChatManager chatManager = SparkManager.getChatManager(); + ChatRoom chatRoom = chatManager.createChatRoom(item.getFullJID(), item.getNickname(), item.getFullJID()); + + String infoText = "**" + item.getNickname() + " is now available to chat. **"; + chatRoom.getTranscriptWindow().insertNotificationMessage(infoText); + Message message = new Message(); + message.setFrom(item.getFullJID()); + message.setBody(infoText); + chatManager.getChatContainer().messageReceived(chatRoom, message); + } + } + + } + }, new PacketTypeFilter(Presence.class)); + } + + public void shutdown() { + + } + + public boolean canShutDown() { + return true; + } + + public void uninstall() { + // Do nothing. + } +} diff --git a/src/java/org/jivesoftware/sparkimpl/plugin/emoticons/EmoticonPlugin.java b/src/java/org/jivesoftware/sparkimpl/plugin/emoticons/EmoticonPlugin.java new file mode 100644 index 00000000..6a53b380 --- /dev/null +++ b/src/java/org/jivesoftware/sparkimpl/plugin/emoticons/EmoticonPlugin.java @@ -0,0 +1,95 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.sparkimpl.plugin.emoticons; + +import org.jivesoftware.resource.EmotionRes; +import org.jivesoftware.spark.ChatManager; +import org.jivesoftware.spark.SparkManager; +import org.jivesoftware.spark.component.RolloverButton; +import org.jivesoftware.spark.plugin.Plugin; +import org.jivesoftware.spark.ui.ChatInputEditor; +import org.jivesoftware.spark.ui.ChatRoom; +import org.jivesoftware.spark.ui.ChatRoomListenerAdapter; +import org.jivesoftware.spark.util.log.Log; +import org.jivesoftware.sparkimpl.plugin.emoticons.EmoticonUI.EmoticonPickListener; + +import javax.swing.ImageIcon; +import javax.swing.JPopupMenu; +import javax.swing.text.BadLocationException; + +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; + +/** + * Adds an EmoticonPickList to each ChatRoom. + */ +public class EmoticonPlugin implements Plugin { + + public void initialize() { + final ChatManager chatManager = SparkManager.getChatManager(); + chatManager.addChatRoomListener(new ChatRoomListenerAdapter() { + public void chatRoomOpened(final ChatRoom room) { + // Add Emoticon button + ImageIcon icon = EmotionRes.getImageIcon(":)"); + final RolloverButton button = new RolloverButton(icon); + room.getEditorBar().add(button); + + button.addMouseListener(new MouseAdapter() { + public void mouseClicked(MouseEvent e) { + // Show popup + final JPopupMenu popup = new JPopupMenu(); + EmoticonUI ui = new EmoticonUI(); + ui.setEmoticonPickListener(new EmoticonPickListener() { + public void emoticonPicked(String emoticon) { + try { + popup.setVisible(false); + final ChatInputEditor editor = room.getChatInputEditor(); + String currentText = editor.getText(); + if (currentText.length() == 0 || currentText.endsWith(" ")) { + room.getChatInputEditor().insertText(emoticon + " "); + } + else { + room.getChatInputEditor().insertText(" " + emoticon + " "); + } + room.getChatInputEditor().requestFocus(); + } + catch (BadLocationException e1) { + Log.error(e1); + } + + } + }); + + + popup.add(ui); + popup.show(button, e.getX(), e.getY()); + } + }); + } + + public void chatRoomClosed(ChatRoom room) { + } + }); + } + + public void shutdown() { + + } + + public boolean canShutDown() { + return false; + } + + public void uninstall() { + // Do nothing. + } + +} diff --git a/src/java/org/jivesoftware/sparkimpl/plugin/emoticons/EmoticonUI.java b/src/java/org/jivesoftware/sparkimpl/plugin/emoticons/EmoticonUI.java new file mode 100644 index 00000000..0932b2ff --- /dev/null +++ b/src/java/org/jivesoftware/sparkimpl/plugin/emoticons/EmoticonUI.java @@ -0,0 +1,66 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.sparkimpl.plugin.emoticons; + +import org.jivesoftware.resource.EmotionRes; +import org.jivesoftware.spark.component.RolloverButton; + +import javax.swing.ImageIcon; +import javax.swing.JPanel; + +import java.awt.Color; +import java.awt.GridLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.Iterator; +import java.util.Map; + +public class EmoticonUI extends JPanel { + private Map emoticons; + private EmoticonPickListener listener; + + public EmoticonUI() { + setBackground(Color.white); + + emoticons = EmotionRes.getEmoticonMap(); + + int no = emoticons.size(); + + int rows = no / 5; + + setLayout(new GridLayout(rows, 5)); + + // Add Emoticons + Iterator iter = emoticons.keySet().iterator(); + while (iter.hasNext()) { + final String text = (String)iter.next(); + ImageIcon icon = EmotionRes.getImageIcon(text); + + RolloverButton emotButton = new RolloverButton(); + emotButton.setIcon(icon); + emotButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + listener.emoticonPicked(text); + } + }); + add(emotButton); + } + } + + public void setEmoticonPickListener(EmoticonPickListener listener) { + this.listener = listener; + } + + public interface EmoticonPickListener { + + void emoticonPicked(String emoticon); + } +} diff --git a/src/java/org/jivesoftware/sparkimpl/plugin/filetransfer/transfer/Downloads.java b/src/java/org/jivesoftware/sparkimpl/plugin/filetransfer/transfer/Downloads.java new file mode 100644 index 00000000..8a3b8292 --- /dev/null +++ b/src/java/org/jivesoftware/sparkimpl/plugin/filetransfer/transfer/Downloads.java @@ -0,0 +1,189 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.sparkimpl.plugin.filetransfer.transfer; + +import org.jdesktop.jdic.desktop.Desktop; +import org.jdesktop.jdic.desktop.DesktopException; +import org.jivesoftware.Spark; +import org.jivesoftware.resource.SparkRes; +import org.jivesoftware.spark.SparkManager; +import org.jivesoftware.spark.component.RolloverButton; +import org.jivesoftware.spark.component.VerticalFlowLayout; +import org.jivesoftware.spark.ui.ChatFrame; +import org.jivesoftware.spark.util.WindowsFileSystemView; +import org.jivesoftware.spark.util.log.Log; +import org.jivesoftware.sparkimpl.settings.local.LocalPreferences; +import org.jivesoftware.sparkimpl.settings.local.SettingsManager; + +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.JButton; +import javax.swing.JDialog; +import javax.swing.JFileChooser; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; + +import java.awt.Color; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.File; +import java.io.IOException; + +public class Downloads { + final JPanel mainPanel = new JPanel(); + File downloadedDir; + private JPanel list = new JPanel(); + + private static Downloads singleton; + private static final Object LOCK = new Object(); + private JDialog dlg; + private JFileChooser chooser; + + /** + * Returns the singleton instance of Downloads, + * creating it if necessary. + *

        + * + * @return the singleton instance of Downloads + */ + public static Downloads getInstance() { + // Synchronize on LOCK to ensure that we don't end up creating + // two singletons. + synchronized (LOCK) { + if (null == singleton) { + Downloads controller = new Downloads(); + singleton = controller; + return controller; + } + } + return singleton; + } + + + private Downloads() { + ChatFrame frame = SparkManager.getChatManager().getChatContainer().getChatFrame(); + dlg = new JDialog(SparkManager.getMainWindow(), "Downloads", false); + dlg.setContentPane(mainPanel); + dlg.pack(); + dlg.setSize(400, 400); + dlg.setResizable(true); + + dlg.setLocationRelativeTo(frame); + + LocalPreferences pref = SettingsManager.getLocalPreferences(); + downloadedDir = new File(SparkManager.getUserDirectory(), "downloads"); + downloadedDir.mkdirs(); + pref.setDownloadDir(downloadedDir.getAbsolutePath()); + + + list.setLayout(new VerticalFlowLayout(VerticalFlowLayout.TOP, 0, 1, true, false)); + list.setBackground(Color.white); + + mainPanel.setLayout(new GridBagLayout()); + + + mainPanel.add(new JScrollPane(list), new GridBagConstraints(0, 0, 3, 1, 1.0, 1.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(5, 5, 5, 5), 0, 0)); + + + JButton cleanUpButton = new JButton("Clean Up", SparkRes.getImageIcon(SparkRes.SMALL_DELETE)); + + JLabel locationLabel = new JLabel(); + locationLabel.setText("All Files Downloaded To: "); + + RolloverButton userHomeButton = new RolloverButton("Downloads", null); + + Action openFolderAction = new AbstractAction() { + public void actionPerformed(ActionEvent e) { + downloadedDir = new File(SparkManager.getUserDirectory(), "downloads"); + if (!downloadedDir.exists()) { + downloadedDir.mkdirs(); + } + openFile(downloadedDir); + } + }; + userHomeButton.addActionListener(openFolderAction); + + mainPanel.add(locationLabel, new GridBagConstraints(0, 1, 1, 1, 0.0, 0.0, GridBagConstraints.EAST, GridBagConstraints.BOTH, new Insets(5, 5, 5, 5), 0, 0)); + mainPanel.add(userHomeButton, new GridBagConstraints(1, 1, 1, 1, 0.0, 0.0, GridBagConstraints.EAST, GridBagConstraints.BOTH, new Insets(5, 5, 5, 5), 0, 0)); + + mainPanel.add(cleanUpButton, new GridBagConstraints(2, 1, 1, 1, 0.0, 0.0, GridBagConstraints.EAST, GridBagConstraints.BOTH, new Insets(5, 5, 5, 5), 0, 0)); + + // Remove all download panels + cleanUpButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + list.removeAll(); + list.validate(); + list.repaint(); + } + }); + + } + + public JFileChooser getFileChooser() { + if (chooser == null) { + downloadedDir = new File(SparkManager.getUserDirectory(), "downloads"); + if (!downloadedDir.exists()) { + downloadedDir.mkdirs(); + } + chooser = new JFileChooser(downloadedDir); + if (Spark.isWindows()) { + chooser.setFileSystemView(new WindowsFileSystemView()); + } + } + return chooser; + } + + private void openFile(File downloadedFile) { + try { + if (!Spark.isMac()) { + try { + Desktop.open(downloadedFile); + } + catch (DesktopException e) { + Log.error(e); + } + } + else if (Spark.isMac()) { + Process child = Runtime.getRuntime().exec("open " + downloadedFile.getCanonicalPath()); + } + } + catch (IOException e1) { + Log.error(e1); + } + } + + + public File getDownloadDirectory() { + return downloadedDir; + } + + public void addDownloadPanel(JPanel panel) { + list.add(panel); + } + + public void removeDownloadPanel(JPanel panel) { + list.remove(panel); + list.validate(); + list.repaint(); + } + + public void showDownloadsDirectory() { + downloadedDir = new File(SparkManager.getUserDirectory(), "downloads"); + if (!downloadedDir.exists()) { + downloadedDir.mkdirs(); + } + openFile(downloadedDir); + } +} diff --git a/src/java/org/jivesoftware/sparkimpl/plugin/filetransfer/transfer/ui/DocumentShare.java b/src/java/org/jivesoftware/sparkimpl/plugin/filetransfer/transfer/ui/DocumentShare.java new file mode 100644 index 00000000..2fc6ea5a --- /dev/null +++ b/src/java/org/jivesoftware/sparkimpl/plugin/filetransfer/transfer/ui/DocumentShare.java @@ -0,0 +1,44 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.sparkimpl.plugin.filetransfer.transfer.ui; + +import javax.swing.JButton; +import javax.swing.JPanel; + +import java.awt.BorderLayout; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; + +public class DocumentShare extends JPanel { + private String jid; + + private JPanel viewer = new JPanel(); + private JPanel documentList = new JPanel(); + private JPanel toolbar = new JPanel(); + + public DocumentShare(String jid) { + this.jid = jid; + + setLayout(new GridBagLayout()); + + final JPanel mainPanel = new JPanel(); + mainPanel.setLayout(new BorderLayout()); + + mainPanel.add(viewer, BorderLayout.CENTER); + mainPanel.add(documentList, BorderLayout.SOUTH); + add(mainPanel, new GridBagConstraints(0, 0, 1, 1, 1.0, 1.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(5, 5, 5, 5), 0, 0)); + + add(toolbar, new GridBagConstraints(0, 1, 1, 1, 1.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); + + final JButton shareDesktop = new JButton("Show Desktop"); + } +} diff --git a/src/java/org/jivesoftware/sparkimpl/plugin/filetransfer/transfer/ui/ReceiveMessage.java b/src/java/org/jivesoftware/sparkimpl/plugin/filetransfer/transfer/ui/ReceiveMessage.java new file mode 100644 index 00000000..1123cfdc --- /dev/null +++ b/src/java/org/jivesoftware/sparkimpl/plugin/filetransfer/transfer/ui/ReceiveMessage.java @@ -0,0 +1,637 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.sparkimpl.plugin.filetransfer.transfer.ui; + +import org.jdesktop.jdic.desktop.Desktop; +import org.jdesktop.jdic.desktop.DesktopException; +import org.jivesoftware.Spark; +import org.jivesoftware.resource.SparkRes; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.util.StringUtils; +import org.jivesoftware.smackx.filetransfer.FileTransfer; +import org.jivesoftware.smackx.filetransfer.FileTransferRequest; +import org.jivesoftware.smackx.filetransfer.IncomingFileTransfer; +import org.jivesoftware.spark.SparkManager; +import org.jivesoftware.spark.ui.ContactItem; +import org.jivesoftware.spark.ui.ContactList; +import org.jivesoftware.spark.util.ByteFormat; +import org.jivesoftware.spark.util.GraphicUtils; +import org.jivesoftware.spark.util.ResourceUtils; +import org.jivesoftware.spark.util.SwingWorker; +import org.jivesoftware.spark.util.URLFileSystem; +import org.jivesoftware.spark.util.log.Log; +import org.jivesoftware.sparkimpl.plugin.filetransfer.transfer.Downloads; + +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.BorderFactory; +import javax.swing.Icon; +import javax.swing.ImageIcon; +import javax.swing.JButton; +import javax.swing.JFileChooser; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JPopupMenu; +import javax.swing.JProgressBar; + +import java.awt.Color; +import java.awt.Cursor; +import java.awt.Font; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; + +public class ReceiveMessage extends JPanel { + private JLabel imageLabel = new JLabel(); + private JLabel titleLabel = new JLabel(); + private JLabel fileLabel = new JLabel(); + + private TransferButton acceptLabel = new TransferButton(); + private TransferButton declineLabel = new TransferButton(); + private JProgressBar progressBar = new JProgressBar(); + private IncomingFileTransfer transfer; + private TransferButton cancelButton = new TransferButton(); + + + public ReceiveMessage() { + setLayout(new GridBagLayout()); + + setBackground(new Color(250, 249, 242)); + add(imageLabel, new GridBagConstraints(0, 0, 1, 3, 0.0, 0.0, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + + add(titleLabel, new GridBagConstraints(1, 0, 2, 1, 1.0, 0.0, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + titleLabel.setFont(new Font("Verdana", Font.BOLD, 11)); + titleLabel.setForeground(new Color(211, 174, 102)); + add(fileLabel, new GridBagConstraints(1, 1, 2, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, 5, 5, 5), 0, 0)); + + add(acceptLabel, new GridBagConstraints(1, 2, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, 5, 0, 5), 0, 0)); + + add(declineLabel, new GridBagConstraints(2, 2, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, 5, 0, 5), 0, 0)); + + + ResourceUtils.resButton(acceptLabel, "Accept"); + ResourceUtils.resButton(declineLabel, "Reject"); + + // Decorate Cancel Button + decorateCancelButton(); + + + acceptLabel.setForeground(new Color(73, 113, 196)); + declineLabel.setForeground(new Color(73, 113, 196)); + declineLabel.setFont(new Font("Verdana", Font.BOLD, 10)); + acceptLabel.setFont(new Font("Verdana", Font.BOLD, 10)); + + acceptLabel.setBorder(BorderFactory.createMatteBorder(0, 0, 1, 0, new Color(73, 113, 196))); + declineLabel.setBorder(BorderFactory.createMatteBorder(0, 0, 1, 0, new Color(73, 113, 196))); + + + setBorder(BorderFactory.createMatteBorder(0, 0, 1, 0, Color.white)); + + acceptLabel.addMouseListener(new MouseAdapter() { + public void mouseEntered(MouseEvent e) { + acceptLabel.setCursor(new Cursor(Cursor.HAND_CURSOR)); + + } + + public void mouseExited(MouseEvent e) { + acceptLabel.setCursor(new Cursor(Cursor.DEFAULT_CURSOR)); + } + }); + + declineLabel.addMouseListener(new MouseAdapter() { + public void mouseEntered(MouseEvent e) { + declineLabel.setCursor(new Cursor(Cursor.HAND_CURSOR)); + } + + public void mouseExited(MouseEvent e) { + declineLabel.setCursor(new Cursor(Cursor.DEFAULT_CURSOR)); + } + }); + } + + public void acceptFileTransfer(final FileTransferRequest request) { + String fileName = request.getFileName(); + long fileSize = request.getFileSize(); + String requestor = request.getRequestor(); + String bareJID = StringUtils.parseBareAddress(requestor); + + ByteFormat format = new ByteFormat(); + String text = format.format(fileSize); + + fileLabel.setText(fileName + " (" + text + ")"); + + ContactList contactList = SparkManager.getWorkspace().getContactList(); + ContactItem contactItem = contactList.getContactItemByJID(bareJID); + + titleLabel.setText(contactItem.getNickname() + " is sending you a file."); + + File tempFile = new File(Spark.getUserHome(), "Spark/tmp"); + try { + tempFile.mkdirs(); + + File file = new File(tempFile, fileName); + file.delete(); + BufferedWriter out = new BufferedWriter(new FileWriter(file)); + out.write("a"); + out.close(); + + imageLabel.setIcon(GraphicUtils.getIcon(file)); + + // Delete temp file when program exits. + file.delete(); + } + catch (IOException e) { + imageLabel.setIcon(SparkRes.getImageIcon(SparkRes.DOCUMENT_INFO_32x32)); + Log.error(e); + } + + + acceptLabel.addMouseListener(new MouseAdapter() { + public void mouseClicked(MouseEvent mouseEvent) { + acceptRequest(request); + } + + + }); + + declineLabel.addMouseListener(new MouseAdapter() { + public void mouseClicked(MouseEvent mouseEvent) { + rejectRequest(request); + } + }); + + } + + private void rejectRequest(FileTransferRequest request) { + request.reject(); + + setBackground(new Color(239, 245, 250)); + acceptLabel.setText(""); + declineLabel.setText(""); + fileLabel.setText(""); + titleLabel.setText("You have cancelled the file transfer."); + titleLabel.setForeground(new Color(65, 139, 179)); + + invalidate(); + validate(); + repaint(); + } + + private void acceptRequest(final FileTransferRequest request) { + String requestor = request.getRequestor(); + String bareJID = StringUtils.parseBareAddress(requestor); + + ContactList contactList = SparkManager.getWorkspace().getContactList(); + final ContactItem contactItem = contactList.getContactItemByJID(bareJID); + + setBackground(new Color(239, 245, 250)); + acceptLabel.setText(""); + declineLabel.setText(""); + titleLabel.setText("Negotiating file transfer. Please wait..."); + titleLabel.setForeground(new Color(65, 139, 179)); + + + add(progressBar, new GridBagConstraints(1, 2, 2, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, 5, 0, 5), 150, 0)); + add(cancelButton, new GridBagConstraints(1, 3, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, 5, 5, 5), 0, 0)); + cancelButton.setVisible(true); + transfer = request.accept(); + try { + + + Downloads downloads = Downloads.getInstance(); + final File downloadedFile = new File(downloads.getDownloadDirectory(), request.getFileName()); + + + progressBar.setMaximum((int)request.getFileSize()); + progressBar.setStringPainted(true); + + SwingWorker worker = new SwingWorker() { + public Object construct() { + try { + transfer.recieveFile(downloadedFile); + } + catch (XMPPException e) { + Log.error(e); + } + + while (true) { + + try { + Thread.sleep(10); + } + catch (InterruptedException e) { + Log.error(e); + } + + long bytesRead = transfer.getAmountWritten(); + if (bytesRead == -1) { + bytesRead = 0; + } + ByteFormat format = new ByteFormat(); + String text = format.format(bytesRead); + progressBar.setString(text + " received"); + + progressBar.setValue((int)bytesRead); + FileTransfer.Status status = transfer.getStatus(); + if (status == FileTransfer.Status.ERROR || + status == FileTransfer.Status.COMPLETE || status == FileTransfer.Status.CANCLED || + status == FileTransfer.Status.REFUSED) { + break; + } + else if (status == FileTransfer.Status.NEGOTIATING_STREAM) { + titleLabel.setText("Negotiating connection stream. Please wait..."); + } + else if (status == FileTransfer.Status.IN_PROGRESS) { + titleLabel.setText("You are receiving a file from " + contactItem.getNickname()); + } + } + + return "ok"; + } + + public void finished() { + if (transfer.getAmountWritten() >= request.getFileSize()) { + transferDone(request, transfer); + + imageLabel.setToolTipText("Click to open"); + titleLabel.setToolTipText("Click to open"); + + imageLabel.addMouseListener(new MouseAdapter() { + public void mouseClicked(MouseEvent e) { + if (e.getClickCount() == 2) { + openFile(downloadedFile); + } + } + }); + + imageLabel.addMouseListener(new MouseAdapter() { + public void mouseEntered(MouseEvent e) { + imageLabel.setCursor(new Cursor(Cursor.HAND_CURSOR)); + + } + + public void mouseExited(MouseEvent e) { + imageLabel.setCursor(new Cursor(Cursor.DEFAULT_CURSOR)); + } + }); + + titleLabel.addMouseListener(new MouseAdapter() { + public void mouseClicked(MouseEvent e) { + if (e.getClickCount() == 2) { + openFile(downloadedFile); + } + } + }); + + titleLabel.addMouseListener(new MouseAdapter() { + public void mouseEntered(MouseEvent e) { + titleLabel.setCursor(new Cursor(Cursor.HAND_CURSOR)); + + } + + public void mouseExited(MouseEvent e) { + titleLabel.setCursor(new Cursor(Cursor.DEFAULT_CURSOR)); + + } + }); + + + invalidate(); + validate(); + repaint(); + return; + } + + String transferMessage = ""; + if (transfer.getStatus() == FileTransfer.Status.ERROR) { + if (transfer.getException() != null) { + Log.error("There was an error during file transfer.", transfer.getException()); + } + transferMessage = "There was an error during file transfer."; + } + else if (transfer.getStatus() == FileTransfer.Status.REFUSED) { + transferMessage = "The file transfer was refused."; + } + else if (transfer.getStatus() == FileTransfer.Status.CANCLED || + transfer.getAmountWritten() < request.getFileSize()) { + transferMessage = "The file transfer was cancelled."; + } + + setFinishedText(transferMessage); + showAlert(true); + } + }; + + worker.start(); + + } + catch (Exception e) { + Log.error(e); + } + + + } + + private void setFinishedText(String text) { + acceptLabel.setText(""); + declineLabel.setText(""); + fileLabel.setText(""); + titleLabel.setText(text); + titleLabel.setForeground(new Color(65, 139, 179)); + progressBar.setVisible(false); + cancelButton.setVisible(false); + invalidate(); + validate(); + repaint(); + } + + private void transferDone(final FileTransferRequest request, FileTransfer transfer) { + cancelButton.setVisible(false); + + showAlert(true); + + String bareJID = StringUtils.parseBareAddress(request.getRequestor()); + + ContactList contactList = SparkManager.getWorkspace().getContactList(); + ContactItem contactItem = contactList.getContactItemByJID(bareJID); + + titleLabel.setText("You have received a file from " + contactItem.getNickname() + "."); + fileLabel.setText(request.getFileName()); + + remove(acceptLabel); + remove(declineLabel); + remove(progressBar); + + + final TransferButton openFileButton = new TransferButton(); + final TransferButton openFolderButton = new TransferButton(); + add(openFileButton, new GridBagConstraints(1, 2, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, 5, 0, 5), 0, 0)); + add(openFolderButton, new GridBagConstraints(2, 2, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, 5, 0, 5), 0, 0)); + + Downloads downloads = Downloads.getInstance(); + final File downloadedFile = new File(downloads.getDownloadDirectory(), request.getFileName()); + + openFileButton.addMouseListener(new MouseAdapter() { + public void mouseEntered(MouseEvent e) { + openFileButton.setCursor(new Cursor(Cursor.HAND_CURSOR)); + + } + + public void mouseExited(MouseEvent e) { + openFileButton.setCursor(new Cursor(Cursor.DEFAULT_CURSOR)); + } + }); + + openFolderButton.addMouseListener(new MouseAdapter() { + public void mouseEntered(MouseEvent e) { + openFolderButton.setCursor(new Cursor(Cursor.HAND_CURSOR)); + + } + + public void mouseExited(MouseEvent e) { + openFolderButton.setCursor(new Cursor(Cursor.DEFAULT_CURSOR)); + } + }); + + + add(fileLabel, new GridBagConstraints(1, 1, 2, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, 5, 5, 5), 0, 0)); + + ResourceUtils.resButton(openFileButton, "Open"); + ResourceUtils.resButton(openFolderButton, "Open Folder"); + + openFileButton.setBorder(BorderFactory.createMatteBorder(0, 0, 1, 0, new Color(73, 113, 196))); + openFileButton.setForeground(new Color(73, 113, 196)); + openFileButton.setFont(new Font("Verdana", Font.BOLD, 10)); + + openFolderButton.setBorder(BorderFactory.createMatteBorder(0, 0, 1, 0, new Color(73, 113, 196))); + openFolderButton.setForeground(new Color(73, 113, 196)); + openFolderButton.setFont(new Font("Verdana", Font.BOLD, 10)); + + + imageLabel.setIcon(GraphicUtils.getIcon(downloadedFile)); + imageLabel.addMouseListener(new MouseAdapter() { + public void mousePressed(MouseEvent e) { + showPopup(e, downloadedFile); + } + + public void mouseReleased(MouseEvent e) { + showPopup(e, downloadedFile); + } + }); + + openFileButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent actionEvent) { + openFile(downloadedFile); + } + }); + + openFolderButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent actionEvent) { + try { + Downloads downloads = Downloads.getInstance(); + if (!Spark.isMac()) { + try { + Desktop.open(downloads.getDownloadDirectory()); + } + catch (DesktopException e) { + Log.error(e); + } + } + else if (Spark.isMac()) { + Runtime.getRuntime().exec("open " + downloads.getDownloadDirectory().getCanonicalPath()); + } + } + catch (IOException e1) { + Log.error(e1); + } + } + }); + + if (isImage(downloadedFile.getName())) { + try { + URL imageURL = downloadedFile.toURL(); + ImageIcon image = new ImageIcon(imageURL); + image = GraphicUtils.scaleImageIcon(image, 64, 64); + imageLabel.setIcon(image); + } + catch (MalformedURLException e) { + Log.error("Could not locate image.", e); + imageLabel.setIcon(SparkRes.getImageIcon(SparkRes.DOCUMENT_INFO_32x32)); + } + } + + invalidate(); + validate(); + repaint(); + } + + private void openFile(File downloadedFile) { + try { + if (!Spark.isMac()) { + try { + Desktop.open(downloadedFile); + } + catch (DesktopException e) { + Log.error(e); + } + } + else if (Spark.isMac()) { + Process child = Runtime.getRuntime().exec("open " + downloadedFile.getCanonicalPath()); + } + } + catch (IOException e1) { + Log.error(e1); + } + } + + private class TransferButton extends JButton { + + public TransferButton() { + decorate(); + } + + /** + * Create a new RolloverButton. + * + * @param text the button text. + * @param icon the button icon. + */ + public TransferButton(String text, Icon icon) { + super(text, icon); + decorate(); + } + + + /** + * Decorates the button with the approriate UI configurations. + */ + private void decorate() { + setBorderPainted(false); + setOpaque(true); + + setContentAreaFilled(false); + setMargin(new Insets(1, 1, 1, 1)); + } + + } + + private boolean isImage(String fileName) { + fileName = fileName.toLowerCase(); + + String[] imageTypes = {"jpeg", "gif", "jpg", "png"}; + for (int i = 0; i < imageTypes.length; i++) { + if (fileName.endsWith(imageTypes[i])) { + return true; + } + } + + return false; + } + + private void showAlert(boolean alert) { + if (alert) { + titleLabel.setForeground(new Color(211, 174, 102)); + setBackground(new Color(250, 249, 242)); + } + else { + setBackground(new Color(239, 245, 250)); + titleLabel.setForeground(new Color(65, 139, 179)); + } + } + + public void cancelTransfer() { + if (transfer != null) { + transfer.cancel(); + } + } + + /** + * Handle the UI for the Cancel Button + */ + private void decorateCancelButton() { + cancelButton.setVisible(false); + ResourceUtils.resButton(cancelButton, "Cancel"); + cancelButton.setBorder(BorderFactory.createMatteBorder(0, 0, 1, 0, new Color(73, 113, 196))); + cancelButton.setForeground(new Color(73, 113, 196)); + cancelButton.setFont(new Font("Verdana", Font.BOLD, 10)); + + cancelButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + cancelTransfer(); + } + }); + + cancelButton.addMouseListener(new MouseAdapter() { + public void mouseEntered(MouseEvent e) { + cancelButton.setCursor(new Cursor(Cursor.HAND_CURSOR)); + + } + + public void mouseExited(MouseEvent e) { + cancelButton.setCursor(new Cursor(Cursor.DEFAULT_CURSOR)); + } + }); + + } + + private void showPopup(MouseEvent e, final File downloadedFile) { + if (e.isPopupTrigger()) { + final JPopupMenu popup = new JPopupMenu(); + + final ReceiveMessage ui = this; + Action saveAsAction = new AbstractAction() { + public void actionPerformed(ActionEvent e) { + final JFileChooser chooser = Downloads.getInstance().getFileChooser(); + File selectedFile = chooser.getSelectedFile(); + if (selectedFile != null) { + selectedFile = new File(selectedFile.getParent(), downloadedFile.getName()); + } + else { + selectedFile = downloadedFile; + } + chooser.setSelectedFile(selectedFile); + + int ok = chooser.showSaveDialog(ui); + if (ok == JFileChooser.APPROVE_OPTION) { + File file = chooser.getSelectedFile(); + try { + if (file.exists()) { + int confirm = JOptionPane.showConfirmDialog(ui, "The file already exists. Overwrite?", "File Exists", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE); + if (confirm == JOptionPane.NO_OPTION) { + return; + } + } + URLFileSystem.copy(downloadedFile.toURL(), file); + } + catch (IOException e1) { + Log.error(e1); + } + } + } + }; + + saveAsAction.putValue(Action.NAME, "Save As..."); + popup.add(saveAsAction); + popup.show(this, e.getX(), e.getY()); + } + } +} diff --git a/src/java/org/jivesoftware/sparkimpl/plugin/filetransfer/transfer/ui/SendMessage.java b/src/java/org/jivesoftware/sparkimpl/plugin/filetransfer/transfer/ui/SendMessage.java new file mode 100644 index 00000000..456e7ab6 --- /dev/null +++ b/src/java/org/jivesoftware/sparkimpl/plugin/filetransfer/transfer/ui/SendMessage.java @@ -0,0 +1,321 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.sparkimpl.plugin.filetransfer.transfer.ui; + +import org.jdesktop.jdic.desktop.Desktop; +import org.jdesktop.jdic.desktop.DesktopException; +import org.jivesoftware.Spark; +import org.jivesoftware.resource.SparkRes; +import org.jivesoftware.smackx.filetransfer.FileTransfer; +import org.jivesoftware.smackx.filetransfer.FileTransfer.Status; +import org.jivesoftware.smackx.filetransfer.OutgoingFileTransfer; +import org.jivesoftware.spark.SparkManager; +import org.jivesoftware.spark.ui.ContactItem; +import org.jivesoftware.spark.ui.ContactList; +import org.jivesoftware.spark.util.ByteFormat; +import org.jivesoftware.spark.util.GraphicUtils; +import org.jivesoftware.spark.util.SwingWorker; +import org.jivesoftware.spark.util.log.Log; + +import javax.swing.BorderFactory; +import javax.swing.Icon; +import javax.swing.ImageIcon; +import javax.swing.JButton; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JProgressBar; + +import java.awt.Color; +import java.awt.Cursor; +import java.awt.Font; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.io.File; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; + +public class SendMessage extends JPanel { + private JLabel imageLabel = new JLabel(); + private JLabel titleLabel = new JLabel(); + private JLabel fileLabel = new JLabel(); + + private TransferButton cancelButton = new TransferButton(); + private JProgressBar progressBar = new JProgressBar(); + private File fileToSend; + private OutgoingFileTransfer transfer; + + + public SendMessage() { + setLayout(new GridBagLayout()); + + setBackground(new Color(250, 249, 242)); + add(imageLabel, new GridBagConstraints(0, 0, 1, 3, 0.0, 0.0, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + + add(titleLabel, new GridBagConstraints(1, 0, 2, 1, 1.0, 0.0, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + titleLabel.setFont(new Font("Verdana", Font.BOLD, 11)); + titleLabel.setForeground(new Color(211, 174, 102)); + add(fileLabel, new GridBagConstraints(1, 1, 2, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, 5, 5, 5), 0, 0)); + + cancelButton.setText("Cancel"); + + add(cancelButton, new GridBagConstraints(1, 3, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, 5, 0, 5), 0, 0)); + + + cancelButton.setForeground(new Color(73, 113, 196)); + cancelButton.setFont(new Font("Verdana", Font.BOLD, 10)); + + cancelButton.setBorder(BorderFactory.createMatteBorder(0, 0, 1, 0, new Color(73, 113, 196))); + + + setBorder(BorderFactory.createMatteBorder(0, 0, 1, 0, Color.white)); + } + + public void sendFile(final OutgoingFileTransfer transfer, final String jid, final String nickname) { + this.transfer = transfer; + String fileName = transfer.getFileName(); + long fileSize = transfer.getFileSize(); + ByteFormat format = new ByteFormat(); + String text = format.format(fileSize); + + fileToSend = new File(transfer.getFilePath()); + + fileLabel.setText(fileName + " (" + text + ")"); + + ContactList contactList = SparkManager.getWorkspace().getContactList(); + ContactItem contactItem = contactList.getContactItemByJID(jid); + + titleLabel.setText("Waiting for " + contactItem.getNickname() + " to accept file transfer."); + + if (isImage(fileName)) { + try { + URL imageURL = new File(transfer.getFilePath()).toURL(); + ImageIcon image = new ImageIcon(imageURL); + image = GraphicUtils.scaleImageIcon(image, 64, 64); + imageLabel.setIcon(image); + } + catch (MalformedURLException e) { + Log.error("Could not locate image.", e); + imageLabel.setIcon(SparkRes.getImageIcon(SparkRes.DOCUMENT_INFO_32x32)); + } + } + else { + File file = new File(transfer.getFilePath()); + Icon icon = GraphicUtils.getIcon(file); + imageLabel.setIcon(icon); + } + cancelButton.addMouseListener(new MouseAdapter() { + public void mouseClicked(MouseEvent mouseEvent) { + transfer.cancel(); + } + }); + + cancelButton.addMouseListener(new MouseAdapter() { + public void mouseEntered(MouseEvent e) { + cancelButton.setCursor(new Cursor(Cursor.HAND_CURSOR)); + + } + + public void mouseExited(MouseEvent e) { + cancelButton.setCursor(new Cursor(Cursor.DEFAULT_CURSOR)); + } + }); + + + progressBar.setMaximum((int)fileSize); + progressBar.setVisible(false); + progressBar.setStringPainted(true); + add(progressBar, new GridBagConstraints(1, 2, 2, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, 5, 0, 5), 150, 0)); + + + SwingWorker worker = new SwingWorker() { + public Object construct() { + while (true) { + try { + Thread.sleep(10); + FileTransfer.Status status = transfer.getStatus(); + if (status == Status.ERROR || + status == Status.COMPLETE || status == Status.CANCLED || + status == Status.REFUSED) { + break; + } + updateBar(transfer, nickname); + } + catch (InterruptedException e) { + Log.error("Unable to sleep thread.", e); + } + + } + return ""; + } + + public void finished() { + updateBar(transfer, nickname); + } + }; + + worker.start(); + + makeClickable(imageLabel); + makeClickable(titleLabel); + } + + private void makeClickable(final JLabel label) { + label.setToolTipText("Click to open"); + + label.addMouseListener(new MouseAdapter() { + public void mouseClicked(MouseEvent e) { + openFile(fileToSend); + } + + public void mouseEntered(MouseEvent e) { + label.setCursor(new Cursor(Cursor.HAND_CURSOR)); + + } + + public void mouseExited(MouseEvent e) { + label.setCursor(new Cursor(Cursor.DEFAULT_CURSOR)); + } + }); + } + + private void openFile(File downloadedFile) { + try { + if (!Spark.isMac()) { + try { + Desktop.open(downloadedFile); + } + catch (DesktopException e) { + Log.error(e); + } + } + else if (Spark.isMac()) { + Process child = Runtime.getRuntime().exec("open " + downloadedFile.getCanonicalPath()); + } + } + catch (IOException e1) { + Log.error("Exception occured while opening file.", e1); + } + } + + private void updateBar(final OutgoingFileTransfer transfer, String nickname) { + FileTransfer.Status status = transfer.getStatus(); + if (status == Status.NEGOTIATING_STREAM) { + titleLabel.setText("Negotiating file transfer with " + nickname + ". Please wait..."); + } + else if (status == Status.ERROR) { + if (transfer.getException() != null) { + Log.error("Error occured during file transfer.", transfer.getException()); + } + progressBar.setVisible(false); + titleLabel.setText("You were unable to send the file to " + nickname); + cancelButton.setVisible(false); + showAlert(true); + } + else if (status == Status.IN_PROGRESS) { + titleLabel.setText("Sending a file to " + nickname); + showAlert(false); + if (!progressBar.isVisible()) { + progressBar.setVisible(true); + } + progressBar.setValue((int)transfer.getBytesSent()); + + ByteFormat format = new ByteFormat(); + String bytesSent = format.format(transfer.getBytesSent()); + progressBar.setString(bytesSent + " sent"); + } + else if (status == Status.COMPLETE) { + progressBar.setVisible(false); + titleLabel.setText("You have sent a file to " + nickname + "."); + cancelButton.setVisible(false); + showAlert(true); + } + else if (status == Status.CANCLED) { + progressBar.setVisible(false); + titleLabel.setText("You have cancelled the file transfer."); + cancelButton.setVisible(false); + showAlert(true); + } + else if (status == Status.REFUSED) { + progressBar.setVisible(false); + titleLabel.setText(nickname + " did not accept the file transfer."); + cancelButton.setVisible(false); + showAlert(true); + } + + } + + private class TransferButton extends JButton { + + public TransferButton() { + decorate(); + } + + /** + * Create a new RolloverButton. + * + * @param text the button text. + * @param icon the button icon. + */ + public TransferButton(String text, Icon icon) { + super(text, icon); + decorate(); + } + + + /** + * Decorates the button with the approriate UI configurations. + */ + private void decorate() { + setBorderPainted(false); + setOpaque(true); + + setContentAreaFilled(false); + setMargin(new Insets(1, 1, 1, 1)); + } + + } + + + private boolean isImage(String fileName) { + fileName = fileName.toLowerCase(); + + String[] imageTypes = {"jpeg", "gif", "jpg", "png"}; + for (int i = 0; i < imageTypes.length; i++) { + if (fileName.endsWith(imageTypes[i])) { + return true; + } + } + + return false; + } + + private void showAlert(boolean alert) { + if (alert) { + titleLabel.setForeground(new Color(211, 174, 102)); + setBackground(new Color(250, 249, 242)); + } + else { + setBackground(new Color(239, 245, 250)); + titleLabel.setForeground(new Color(65, 139, 179)); + } + } + + public void cancelTransfer() { + if (transfer != null) { + transfer.cancel(); + } + } + +} diff --git a/src/java/org/jivesoftware/sparkimpl/plugin/jabber/JabberBrowser.java b/src/java/org/jivesoftware/sparkimpl/plugin/jabber/JabberBrowser.java new file mode 100644 index 00000000..97803e52 --- /dev/null +++ b/src/java/org/jivesoftware/sparkimpl/plugin/jabber/JabberBrowser.java @@ -0,0 +1,230 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.sparkimpl.plugin.jabber; + +import org.jivesoftware.resource.SparkRes; +import org.jivesoftware.smack.XMPPConnection; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smackx.ServiceDiscoveryManager; +import org.jivesoftware.smackx.packet.DiscoverItems; +import org.jivesoftware.spark.SparkManager; +import org.jivesoftware.spark.component.RolloverButton; +import org.jivesoftware.spark.plugin.Plugin; +import org.jivesoftware.spark.util.GraphicUtils; +import org.jivesoftware.spark.util.ModelUtil; +import org.jivesoftware.spark.util.ResourceUtils; +import org.jivesoftware.spark.util.log.Log; + +import javax.swing.AbstractAction; +import javax.swing.JButton; +import javax.swing.JComboBox; +import javax.swing.JComponent; +import javax.swing.JDialog; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.KeyStroke; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.FlowLayout; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +public class JabberBrowser implements Plugin { + private JLabel addressLabel = new JLabel(); + private JComboBox addressField = new JComboBox(); + private XMPPConnection con; + private JPanel browsePanel; + + public JabberBrowser() { + this.con = SparkManager.getConnection(); + + addressField.setEditable(true); + addressField.addItem(con.getHost()); + } + + public void display() { + final JPanel mainPanel = new JPanel(); + mainPanel.setLayout(new GridBagLayout()); + + // Setup resource + ResourceUtils.resLabel(addressLabel, addressField, "&Jabber Address:"); + + RolloverButton backButton = new RolloverButton(); + backButton.setIcon(SparkRes.getImageIcon(SparkRes.LEFT_ARROW_IMAGE)); + backButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + int selectedItem = addressField.getSelectedIndex(); + if (selectedItem > 0) { + Object historyItem = addressField.getItemAt(selectedItem - 1); + browse((String)historyItem); + } + } + }); + + mainPanel.add(backButton, new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + mainPanel.add(addressLabel, new GridBagConstraints(1, 0, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + mainPanel.add(addressField, new GridBagConstraints(2, 0, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); + + + JButton browseButton = new JButton("Browse"); + browseButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + String serviceName = (String)addressField.getSelectedItem(); + if (!ModelUtil.hasLength(serviceName)) { + return; + } + browse(serviceName); + } + }); + mainPanel.add(addressField, new GridBagConstraints(2, 0, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); + mainPanel.add(browseButton, new GridBagConstraints(3, 0, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + + browsePanel = new JPanel(); + browsePanel.setLayout(new FlowLayout(FlowLayout.LEFT)); + browsePanel.setBackground(Color.white); + + JScrollPane pane = new JScrollPane(browsePanel, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); + browsePanel.setPreferredSize(new Dimension(0, 0)); + mainPanel.add(pane, new GridBagConstraints(0, 1, 4, 1, 1.0, 1.0, GridBagConstraints.WEST, GridBagConstraints.BOTH, new Insets(5, 5, 5, 5), 0, 0)); + + JFrame frame = new JFrame(); + frame.setIconImage(SparkRes.getImageIcon(SparkRes.FIND_IMAGE).getImage()); + + JDialog dialog = new JDialog(frame, "Jabber Browser"); + dialog.getContentPane().setLayout(new BorderLayout()); + dialog.getContentPane().add(mainPanel, BorderLayout.CENTER); + dialog.pack(); + dialog.setSize(600, 400); + dialog.setLocationRelativeTo(SparkManager.getMainWindow()); + dialog.setVisible(true); + } + + private void browse(String serviceName) { + browsePanel.removeAll(); + + ServiceDiscoveryManager discoManager = ServiceDiscoveryManager.getInstanceFor(con); + DiscoverItems result = null; + try { + result = discoManager.discoverItems(serviceName); + } + catch (XMPPException e) { + Log.error(e); + return; + } + + addAddress(serviceName); + + + Iterator discoverItems = result.getItems(); + while (discoverItems.hasNext()) { + DiscoverItems.Item item = (DiscoverItems.Item)discoverItems.next(); + Entity entity = new Entity(item); + browsePanel.add(entity); + } + + browsePanel.invalidate(); + browsePanel.validate(); + browsePanel.repaint(); + } + + private void browseItem(DiscoverItems.Item discoveredItem) { + addAddress(discoveredItem.getEntityID()); + browsePanel.removeAll(); + ServiceDiscoveryManager discoManager = ServiceDiscoveryManager.getInstanceFor(con); + DiscoverItems result = null; + try { + result = discoManager.discoverItems(discoveredItem.getEntityID()); + } + catch (XMPPException e) { + browsePanel.invalidate(); + browsePanel.validate(); + browsePanel.repaint(); + return; + } + + Iterator discoverItems = result.getItems(); + List list = new ArrayList(); + + while (discoverItems.hasNext()) { + DiscoverItems.Item item = (DiscoverItems.Item)discoverItems.next(); + Entity entity = new Entity(item); + browsePanel.add(entity); + list.add(entity); + } + + GraphicUtils.makeSameSize((JComponent[])list.toArray(new JComponent[list.size()])); + + browsePanel.invalidate(); + browsePanel.validate(); + browsePanel.repaint(); + } + + public class Entity extends RolloverButton { + private DiscoverItems.Item item; + + public Entity(final DiscoverItems.Item item) { + this.item = item; + setVerticalTextPosition(JLabel.BOTTOM); + setHorizontalTextPosition(JLabel.CENTER); + setText(item.getName()); + setIcon(SparkRes.getImageIcon(SparkRes.USER1_MESSAGE_24x24)); + + addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + browseItem(item); + } + }); + + } + + public DiscoverItems.Item getItem() { + return item; + } + } + + private void addAddress(String address) { + addressField.addItem(address); + addressField.setSelectedItem(address); + } + + public void initialize() { + SparkManager.getWorkspace().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("F8"), "showBrowser"); + SparkManager.getWorkspace().getActionMap().put("showBrowser", new AbstractAction("showBrowser") { + public void actionPerformed(ActionEvent evt) { + display(); + } + }); + } + + public void shutdown() { + + } + + public boolean canShutDown() { + return false; + } + + public void uninstall() { + // Do nothing. + } + + +} diff --git a/src/java/org/jivesoftware/sparkimpl/plugin/jabber/JabberVersion.java b/src/java/org/jivesoftware/sparkimpl/plugin/jabber/JabberVersion.java new file mode 100644 index 00000000..d237d037 --- /dev/null +++ b/src/java/org/jivesoftware/sparkimpl/plugin/jabber/JabberVersion.java @@ -0,0 +1,154 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.sparkimpl.plugin.jabber; + +import org.jivesoftware.smack.PacketListener; +import org.jivesoftware.smack.filter.PacketFilter; +import org.jivesoftware.smack.filter.PacketTypeFilter; +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.packet.Packet; +import org.jivesoftware.smack.packet.Presence; +import org.jivesoftware.smackx.packet.Time; +import org.jivesoftware.smackx.packet.Version; +import org.jivesoftware.spark.SparkManager; +import org.jivesoftware.spark.plugin.ContextMenuListener; +import org.jivesoftware.spark.plugin.Plugin; +import org.jivesoftware.spark.ui.ContactItem; +import org.jivesoftware.spark.ui.ContactList; +import org.jivesoftware.spark.util.SwingWorker; +import org.jivesoftware.sparkimpl.settings.JiveInfo; + +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.JComponent; +import javax.swing.JPopupMenu; +import javax.swing.KeyStroke; + +import java.awt.event.ActionEvent; +import java.awt.event.MouseEvent; +import java.util.Collection; +import java.util.Date; + + +public class JabberVersion implements Plugin { + + public void initialize() { + // Create IQ Filter + PacketFilter packetFilter = new PacketTypeFilter(IQ.class); + SparkManager.getConnection().addPacketListener(new PacketListener() { + public void processPacket(Packet packet) { + IQ iq = (IQ)packet; + + // Handle Version Request + if (iq instanceof Version && iq.getType() == IQ.Type.GET) { + // Send Version + Version version = new Version(); + version.setName("Spark IM Client"); + + version.setOs(JiveInfo.getOS()); + version.setVersion(JiveInfo.getVersion()); + + // Send back as a reply + version.setPacketID(iq.getPacketID()); + version.setType(IQ.Type.RESULT); + version.setTo(iq.getFrom()); + version.setFrom(iq.getTo()); + SparkManager.getConnection().sendPacket(version); + } + // Send time + else if (iq instanceof Time && iq.getType() == IQ.Type.GET) { + Time time = new Time(); + time.setPacketID(iq.getPacketID()); + time.setFrom(iq.getTo()); + time.setTo(iq.getFrom()); + time.setTime(new Date()); + time.setType(IQ.Type.RESULT); + + // Send Time + SparkManager.getConnection().sendPacket(time); + } + } + }, packetFilter); + + final ContactList contactList = SparkManager.getWorkspace().getContactList(); + contactList.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("control F11"), "viewClient"); + contactList.addContextMenuListener(new ContextMenuListener() { + public void poppingUp(final Object component, JPopupMenu popup) { + if (!(component instanceof ContactItem)) { + return; + } + + Action versionRequest = new AbstractAction() { + public void actionPerformed(ActionEvent e) { + viewClient(); + } + }; + + versionRequest.putValue(Action.NAME, "View Client Version"); + popup.add(versionRequest); + } + + public void poppingDown(JPopupMenu popup) { + + } + + public boolean handleDefaultAction(MouseEvent e) { + return false; + } + }); + + + contactList.getActionMap().put("viewClient", new AbstractAction("viewClient") { + public void actionPerformed(ActionEvent evt) { + viewClient(); + } + }); + + + } + + private void viewClient() { + final ContactList contactList = SparkManager.getWorkspace().getContactList(); + Collection selectedUsers = contactList.getSelectedUsers(); + if (selectedUsers.size() == 1) { + ContactItem item = (ContactItem)selectedUsers.toArray()[0]; + Presence presence = item.getPresence(); + final String jid = presence.getFrom(); + SwingWorker worker = new SwingWorker() { + public Object construct() { + try { + Thread.sleep(10); + } + catch (InterruptedException e1) { + } + return jid; + } + + public void finished() { + VersionViewer.viewVersion(jid); + } + }; + worker.start(); + } + } + + public void shutdown() { + + } + + public boolean canShutDown() { + return false; + } + + public void uninstall() { + // Do nothing. + } +} diff --git a/src/java/org/jivesoftware/sparkimpl/plugin/jabber/VersionViewer.java b/src/java/org/jivesoftware/sparkimpl/plugin/jabber/VersionViewer.java new file mode 100644 index 00000000..f025f1ad --- /dev/null +++ b/src/java/org/jivesoftware/sparkimpl/plugin/jabber/VersionViewer.java @@ -0,0 +1,136 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.sparkimpl.plugin.jabber; + +import org.jivesoftware.resource.SparkRes; +import org.jivesoftware.smack.PacketCollector; +import org.jivesoftware.smack.filter.PacketIDFilter; +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smackx.packet.Time; +import org.jivesoftware.smackx.packet.Version; +import org.jivesoftware.spark.SparkManager; +import org.jivesoftware.spark.component.MessageDialog; +import org.jivesoftware.spark.util.ResourceUtils; +import org.jivesoftware.spark.util.SwingWorker; + +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextField; + +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; + +public class VersionViewer { + + private VersionViewer() { + + } + + public static void viewVersion(String jid) { + final JPanel panel = new JPanel(); + panel.setLayout(new GridBagLayout()); + + JLabel timeLabel = new JLabel(); + JLabel softwareLabel = new JLabel(); + JLabel versionLabel = new JLabel(); + JLabel osLabel = new JLabel(); + + final JTextField timeField = new JTextField(); + final JTextField softwareField = new JTextField(); + final JTextField versionField = new JTextField(); + final JTextField osField = new JTextField(); + + // Add resources + ResourceUtils.resLabel(timeLabel, timeField, "&Local Time:"); + ResourceUtils.resLabel(softwareLabel, softwareField, "&Software:"); + ResourceUtils.resLabel(versionLabel, versionField, "&Version:"); + ResourceUtils.resLabel(osLabel, osField, "&Operating System:"); + + // Add Time Label + panel.add(timeLabel, new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + panel.add(timeField, new GridBagConstraints(1, 0, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); + + panel.add(softwareLabel, new GridBagConstraints(0, 1, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + panel.add(softwareField, new GridBagConstraints(1, 1, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); + + + panel.add(versionLabel, new GridBagConstraints(0, 2, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + panel.add(versionField, new GridBagConstraints(1, 2, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); + + + panel.add(osLabel, new GridBagConstraints(0, 3, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + panel.add(osField, new GridBagConstraints(1, 3, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); + + // Load Version + final Version versionRequest = new Version(); + versionRequest.setType(IQ.Type.GET); + versionRequest.setTo(jid); + final PacketCollector collector = SparkManager.getConnection().createPacketCollector(new PacketIDFilter(versionRequest.getPacketID())); + + SwingWorker versionThread = new SwingWorker() { + IQ result; + + public Object construct() { + SparkManager.getConnection().sendPacket(versionRequest); + result = (IQ)collector.nextResult(5000); + return result; + } + + public void finished() { + // Wait up to 5 seconds for a result. + if (result != null && result.getType() == IQ.Type.RESULT) { + Version versionResult = (Version)result; + softwareField.setText(versionResult.getName()); + versionField.setText(versionResult.getVersion()); + osField.setText(versionResult.getOs()); + } + } + }; + + versionThread.start(); + + + final Time time = new Time(); + time.setType(IQ.Type.GET); + time.setTo(jid); + final PacketCollector collector2 = SparkManager.getConnection().createPacketCollector(new PacketIDFilter(time.getPacketID())); + + SwingWorker timeThread = new SwingWorker() { + IQ timeResult = null; + + public Object construct() { + SparkManager.getConnection().sendPacket(time); + timeResult = (IQ)collector2.nextResult(); + return timeResult; + } + + public void finished() { + // Wait up to 5 seconds for a result. + + if (timeResult != null && timeResult.getType() == IQ.Type.RESULT) { + Time t = (Time)timeResult; + timeField.setText(t.getDisplay()); + } + } + }; + + timeThread.start(); + + osField.setEditable(false); + versionField.setEditable(false); + softwareField.setEditable(false); + timeField.setEditable(false); + MessageDialog.showComponent("Version and Time", "Client information for " + jid, SparkRes.getImageIcon(SparkRes.PROFILE_IMAGE_24x24), panel, SparkManager.getMainWindow(), 400, 300, false); + + } + +} diff --git a/src/java/org/jivesoftware/sparkimpl/plugin/layout/LayoutPlugin.java b/src/java/org/jivesoftware/sparkimpl/plugin/layout/LayoutPlugin.java new file mode 100644 index 00000000..ea42c24c --- /dev/null +++ b/src/java/org/jivesoftware/sparkimpl/plugin/layout/LayoutPlugin.java @@ -0,0 +1,59 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.sparkimpl.plugin.layout; + +import org.jivesoftware.MainWindow; +import org.jivesoftware.MainWindowListener; +import org.jivesoftware.spark.SparkManager; +import org.jivesoftware.spark.plugin.Plugin; + +public class LayoutPlugin implements Plugin { + + public void initialize() { + final MainWindow mainWindow = SparkManager.getMainWindow(); + + SparkManager.getMainWindow().addMainWindowListener(new MainWindowListener() { + public void shutdown() { + int x = mainWindow.getX(); + int y = mainWindow.getY(); + int width = mainWindow.getWidth(); + int height = mainWindow.getHeight(); + + LayoutSettings settings = LayoutSettingsManager.getLayoutSettings(); + settings.setMainWindowHeight(height); + settings.setMainWindowWidth(width); + settings.setMainWindowX(x); + settings.setMainWindowY(y); + LayoutSettingsManager.saveLayoutSettings(); + } + + public void mainWindowActivated() { + + } + + public void mainWindowDeactivated() { + + } + }); + } + + public void shutdown() { + + } + + public boolean canShutDown() { + return true; + } + + public void uninstall() { + // Do nothing. + } +} diff --git a/src/java/org/jivesoftware/sparkimpl/plugin/layout/LayoutSettings.java b/src/java/org/jivesoftware/sparkimpl/plugin/layout/LayoutSettings.java new file mode 100644 index 00000000..a9b38177 --- /dev/null +++ b/src/java/org/jivesoftware/sparkimpl/plugin/layout/LayoutSettings.java @@ -0,0 +1,88 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.sparkimpl.plugin.layout; + +public class LayoutSettings { + + private int mainWindowX; + private int mainWindowY; + private int mainWindowWidth; + private int mainWindowHeight; + + private int chatFrameX; + private int chatFrameY; + private int chatFrameWidth; + private int chatFrameHeight; + + public int getMainWindowX() { + return mainWindowX; + } + + public void setMainWindowX(int mainWindowX) { + this.mainWindowX = mainWindowX; + } + + public int getMainWindowY() { + return mainWindowY; + } + + public void setMainWindowY(int mainWindowY) { + this.mainWindowY = mainWindowY; + } + + public int getMainWindowWidth() { + return mainWindowWidth; + } + + public void setMainWindowWidth(int mainWindowWidth) { + this.mainWindowWidth = mainWindowWidth; + } + + public int getMainWindowHeight() { + return mainWindowHeight; + } + + public void setMainWindowHeight(int mainWindowHeight) { + this.mainWindowHeight = mainWindowHeight; + } + + public int getChatFrameX() { + return chatFrameX; + } + + public void setChatFrameX(int chatFrameX) { + this.chatFrameX = chatFrameX; + } + + public int getChatFrameY() { + return chatFrameY; + } + + public void setChatFrameY(int chatFrameY) { + this.chatFrameY = chatFrameY; + } + + public int getChatFrameWidth() { + return chatFrameWidth; + } + + public void setChatFrameWidth(int chatFrameWidth) { + this.chatFrameWidth = chatFrameWidth; + } + + public int getChatFrameHeight() { + return chatFrameHeight; + } + + public void setChatFrameHeight(int chatFrameHeight) { + this.chatFrameHeight = chatFrameHeight; + } +} diff --git a/src/java/org/jivesoftware/sparkimpl/plugin/layout/LayoutSettingsManager.java b/src/java/org/jivesoftware/sparkimpl/plugin/layout/LayoutSettingsManager.java new file mode 100644 index 00000000..05d9ea17 --- /dev/null +++ b/src/java/org/jivesoftware/sparkimpl/plugin/layout/LayoutSettingsManager.java @@ -0,0 +1,104 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.sparkimpl.plugin.layout; + +import com.thoughtworks.xstream.XStream; +import org.jivesoftware.Spark; +import org.jivesoftware.spark.util.log.Log; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.FileWriter; + +/** + * Responsbile for the loading and persisting of LocalSettings. + */ +public class LayoutSettingsManager { + private static LayoutSettings layoutSettings; + private static XStream xstream = new XStream(); + + private LayoutSettingsManager() { + } + + static { + xstream.alias("layout", LayoutSettings.class); + } + + /** + * Returns the LayoutSettings for this agent. + * + * @return the LayoutSettings for this agent. + */ + public static LayoutSettings getLayoutSettings() { + if (!exists() && layoutSettings == null) { + layoutSettings = new LayoutSettings(); + } + + if (layoutSettings == null) { + // Do Initial Load from FileSystem. + File settingsFile = getSettingsFile(); + try { + FileReader reader = new FileReader(settingsFile); + layoutSettings = (LayoutSettings)xstream.fromXML(reader); + } + catch (Exception e) { + xstream.alias("com.jivesoftware.settings.layout.LayoutSettings", LayoutSettings.class); + try { + FileReader reader = new FileReader(settingsFile); + layoutSettings = (LayoutSettings)xstream.fromXML(reader); + } + catch (FileNotFoundException e1) { + Log.error("Error loading LayoutSettings.", e); + layoutSettings = new LayoutSettings(); + } + } + xstream.alias("layout", LayoutSettings.class); + } + return layoutSettings; + } + + /** + * Persists the settings to the local file system. + */ + public static void saveLayoutSettings() { + + try { + FileWriter writer = new FileWriter(getSettingsFile()); + xstream.toXML(layoutSettings, writer); + } + catch (Exception e) { + Log.error("Error saving settings.", e); + } + } + + /** + * Return true if the settings file exists. + * + * @return true if the settings file exists.('settings.xml') + */ + public static boolean exists() { + return getSettingsFile().exists(); + } + + /** + * Returns the settings file. + * + * @return the settings file. + */ + public static File getSettingsFile() { + File file = new File(Spark.getUserHome(), "Spark"); + if (!file.exists()) { + file.mkdirs(); + } + return new File(file, "layout-settings.xml"); + } +} diff --git a/src/java/org/jivesoftware/sparkimpl/plugin/manager/Enterprise.java b/src/java/org/jivesoftware/sparkimpl/plugin/manager/Enterprise.java new file mode 100644 index 00000000..0be75893 --- /dev/null +++ b/src/java/org/jivesoftware/sparkimpl/plugin/manager/Enterprise.java @@ -0,0 +1,90 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.sparkimpl.plugin.manager; + +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smackx.ServiceDiscoveryManager; +import org.jivesoftware.smackx.packet.DiscoverInfo; +import org.jivesoftware.smackx.packet.DiscoverItems; +import org.jivesoftware.spark.SparkManager; +import org.jivesoftware.spark.util.log.Log; + +import java.util.Iterator; + +/** + * EnterpriseSparkManager is responsible for the detecting of features on the server. This allows for fine-grain control of + * feature sets to enable/disable within Spark. + * + * @author Derek DeMoro + */ +public class Enterprise { + + public static final String BROADCAST_FEATURE = "broadcast"; + public static final String MUC_FEATURE = "muc"; + public static final String VCARD_FEATURE = "vcard"; + public static final String FILE_TRANSFER_FEATURE = "file-transfer"; + + private static DiscoverInfo featureInfo; + + private boolean sparkManagerInstalled; + + public Enterprise() { + // Retrieve feature list. + populateFeatureSet(); + } + + /** + * Returns true if the Enterprise Spark Manager module is installed on the server we are currently connected to. + * + * @return true if Enterprise Spark Manager exists. + */ + public boolean isSparkManagerInstalled() { + return sparkManagerInstalled; + } + + /** + * Returns true if the feature is available. + * + * @param feature the name of the feature to detect. + * @return true if the feature is available on the server, otherwise false. + */ + public static boolean containsFeature(String feature) { + if (featureInfo == null) { + return true; + } + + return featureInfo.containsFeature(feature); + } + + private void populateFeatureSet() { + final ServiceDiscoveryManager disco = ServiceDiscoveryManager.getInstanceFor(SparkManager.getConnection()); + final DiscoverItems items = SparkManager.getSessionManager().getDiscoveredItems(); + Iterator iter = items.getItems(); + while (iter.hasNext()) { + DiscoverItems.Item item = (DiscoverItems.Item)iter.next(); + String entity = item.getEntityID(); + if (entity != null) { + if (entity.startsWith("manager.")) { + sparkManagerInstalled = true; + + // Populate with feature sets. + try { + featureInfo = disco.discoverInfo(item.getEntityID()); + } + catch (XMPPException e) { + Log.error("Error while retrieving feature list for SparkManager.", e); + } + + } + } + } + } +} diff --git a/src/java/org/jivesoftware/sparkimpl/plugin/manager/Features.java b/src/java/org/jivesoftware/sparkimpl/plugin/manager/Features.java new file mode 100644 index 00000000..de3a6e6b --- /dev/null +++ b/src/java/org/jivesoftware/sparkimpl/plugin/manager/Features.java @@ -0,0 +1,84 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.sparkimpl.plugin.manager; + +import org.jivesoftware.smack.packet.PacketExtension; +import org.jivesoftware.smack.provider.PacketExtensionProvider; +import org.xmlpull.v1.XmlPullParser; + +import java.util.ArrayList; +import java.util.List; + + +public class Features implements PacketExtension { + + private List availableFeatures = new ArrayList(); + + + public List getAvailableFeatures() { + return availableFeatures; + } + + public void addFeature(String feature) { + availableFeatures.add(feature); + } + + /** + * Element name of the packet extension. + */ + public static final String ELEMENT_NAME = "event"; + + /** + * Namespace of the packet extension. + */ + public static final String NAMESPACE = "http://jabber.org/protocol/disco#info"; + + + public String getElementName() { + return ELEMENT_NAME; + } + + public String getNamespace() { + return NAMESPACE; + } + + public String toXML() { + StringBuffer buf = new StringBuffer(); + buf.append(""); + return buf.toString(); + } + + public static class Provider implements PacketExtensionProvider { + + public PacketExtension parseExtension(XmlPullParser parser) throws Exception { + + Features features = new Features(); + boolean done = false; + while (!done) { + int eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG && "event".equals(parser.getName())) { + parser.nextText(); + } + if (eventType == XmlPullParser.START_TAG && "feature".equals(parser.getName())) { + String feature = parser.getAttributeValue("", "var"); + features.addFeature(feature); + } + else if (eventType == XmlPullParser.END_TAG) { + if ("event".equals(parser.getName())) { + done = true; + } + } + } + + return features; + } + } +} diff --git a/src/java/org/jivesoftware/sparkimpl/plugin/phone/DialPanel.java b/src/java/org/jivesoftware/sparkimpl/plugin/phone/DialPanel.java new file mode 100644 index 00000000..2f75d2be --- /dev/null +++ b/src/java/org/jivesoftware/sparkimpl/plugin/phone/DialPanel.java @@ -0,0 +1,95 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.sparkimpl.plugin.phone; + +import org.jivesoftware.resource.SparkRes; +import org.jivesoftware.spark.util.ResourceUtils; + +import javax.swing.JButton; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextField; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Font; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; + +public class DialPanel extends JPanel { + private JButton dialButton; + private JTextField dialField; + final JLabel iconLabel; + final JPanel dialPanel = new JPanel(); + + public DialPanel() { + setLayout(new GridBagLayout()); + + JPanel imagePanel = new JPanel(); + imagePanel.setLayout(new BorderLayout()); + + + imagePanel.setBackground(Color.white); + + iconLabel = new JLabel(SparkRes.getImageIcon(SparkRes.TELEPHONE_24x24)); + iconLabel.setHorizontalAlignment(JLabel.CENTER); + iconLabel.setVerticalTextPosition(JLabel.BOTTOM); + iconLabel.setHorizontalTextPosition(JLabel.CENTER); + imagePanel.add(iconLabel, BorderLayout.CENTER); + iconLabel.setFont(new Font("Verdana", Font.BOLD, 14)); + + + dialPanel.setLayout(new GridBagLayout()); + + JLabel dialLabel = new JLabel("Number:"); + dialPanel.add(dialLabel, new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.WEST, new Insets(5, 5, 5, 5), 0, 0)); + + dialField = new JTextField(); + dialPanel.add(dialField, new GridBagConstraints(1, 0, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); + + dialButton = new JButton("Dial"); + dialPanel.add(dialButton, new GridBagConstraints(2, 0, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.WEST, new Insets(5, 5, 5, 5), 0, 0)); + + add(imagePanel, new GridBagConstraints(0, 0, 1, 1, 1.0, 1.0, GridBagConstraints.WEST, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0)); + add(dialPanel, new GridBagConstraints(0, 1, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); + + iconLabel.setText("Waiting to place call"); + + ResourceUtils.resButton(dialButton, "&Dial"); + ResourceUtils.resLabel(dialLabel, dialField, "&Number:"); + } + + public void changeToRinging() { + remove(dialPanel); + dialPanel.setVisible(false); + } + + public void showDialog(String number) { + iconLabel.setText("Calling " + number); + } + + public void setText(String text) { + iconLabel.setText(text); + } + + public JButton getDialButton() { + return dialButton; + } + + public String getNumberToDial() { + return dialField.getText(); + } + + public JTextField getDialField() { + return dialField; + } +} diff --git a/src/java/org/jivesoftware/sparkimpl/plugin/phone/IncomingCall.java b/src/java/org/jivesoftware/sparkimpl/plugin/phone/IncomingCall.java new file mode 100644 index 00000000..a7f783fb --- /dev/null +++ b/src/java/org/jivesoftware/sparkimpl/plugin/phone/IncomingCall.java @@ -0,0 +1,99 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.sparkimpl.plugin.phone; + +import org.jivesoftware.resource.SparkRes; + +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JPanel; + +import java.awt.Color; +import java.awt.Font; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; + +public class IncomingCall extends JPanel { + private JLabel callerNameLabel; + private JLabel callerNumberLabel; + + + public IncomingCall() { + setLayout(new GridBagLayout()); + setBackground(Color.white); + + callerNameLabel = new JLabel(); + callerNameLabel.setFont(new Font("Dialog", Font.BOLD, 13)); + callerNameLabel.setHorizontalAlignment(JLabel.CENTER); + + callerNumberLabel = new JLabel(); + callerNumberLabel.setFont(new Font("Dialog", Font.PLAIN, 12)); + callerNumberLabel.setHorizontalAlignment(JLabel.CENTER); + + + final JLabel phoneImage = new JLabel(SparkRes.getImageIcon(SparkRes.TELEPHONE_24x24)); + phoneImage.setHorizontalAlignment(JLabel.CENTER); + phoneImage.setVerticalTextPosition(JLabel.BOTTOM); + phoneImage.setHorizontalTextPosition(JLabel.CENTER); + phoneImage.setText("Incoming Call"); + phoneImage.setFont(new Font("Dialog", Font.BOLD, 16)); + + + add(phoneImage, new GridBagConstraints(0, 0, 1, 1, 1.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, new Insets(5, 10, 0, 10), 0, 0)); + add(callerNameLabel, new GridBagConstraints(0, 1, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(2, 0, 0, 0), 0, 0)); + add(callerNumberLabel, new GridBagConstraints(0, 2, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(2, 0, 10, 0), 0, 0)); + + } + + public void setCallerName(String user) { + callerNameLabel.setText(user); + } + + public void setCallerNumber(String number) { + final StringBuffer buf = new StringBuffer(); + if (number == null) { + return; + } + + if (number.trim().length() == 10) { + buf.append("("); + String areaCode = number.substring(0, 3); + buf.append(areaCode); + buf.append(") "); + + String nextThree = number.substring(3, 6); + buf.append(" "); + buf.append(nextThree); + buf.append("-"); + + String lastThree = number.substring(6, 10); + buf.append(lastThree); + } + + callerNumberLabel.setText(buf.toString()); + } + + public static void main(String args[]) { + final JFrame frame = new JFrame(); + IncomingCall call = new IncomingCall(); + + + call.setCallerName("Matt Tucker"); + call.setCallerNumber("5036356383"); + + frame.getContentPane().add(call); + + frame.pack(); + frame.setVisible(true); + } + +} diff --git a/src/java/org/jivesoftware/sparkimpl/plugin/phone/OnPhone.java b/src/java/org/jivesoftware/sparkimpl/plugin/phone/OnPhone.java new file mode 100644 index 00000000..bc2af43d --- /dev/null +++ b/src/java/org/jivesoftware/sparkimpl/plugin/phone/OnPhone.java @@ -0,0 +1,73 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.sparkimpl.plugin.phone; + +import org.jivesoftware.resource.SparkRes; +import org.jivesoftware.spark.component.TimeTrackingLabel; + +import javax.swing.JLabel; +import javax.swing.JPanel; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Font; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.util.Date; + +public class OnPhone extends JPanel { + private JLabel iconLabel; + private TimeTrackingLabel timeLabel; + + public OnPhone() { + setLayout(new BorderLayout()); + + final JPanel imagePanel = new JPanel(); + imagePanel.setLayout(new GridBagLayout()); + + + imagePanel.setBackground(Color.white); + + // Handle Icon Label + iconLabel = new JLabel(SparkRes.getImageIcon(SparkRes.TELEPHONE_24x24)); + iconLabel.setHorizontalAlignment(JLabel.CENTER); + iconLabel.setVerticalTextPosition(JLabel.BOTTOM); + iconLabel.setHorizontalTextPosition(JLabel.CENTER); + iconLabel.setFont(new Font("Verdana", Font.BOLD, 14)); + iconLabel.setText("On the phone"); + + // Handle Time Tracker + timeLabel = new TimeTrackingLabel(new Date(), this); + timeLabel.setOpaque(false); + timeLabel.setHorizontalAlignment(JLabel.CENTER); + timeLabel.setHorizontalTextPosition(JLabel.CENTER); + timeLabel.setFont(new Font("Verdana", Font.BOLD, 14)); + + // Add Icon Label + imagePanel.add(iconLabel, new GridBagConstraints(0, 0, 1, 1, 1.0, 0.7, GridBagConstraints.SOUTH, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0)); + + // Add Time Label + imagePanel.add(timeLabel, new GridBagConstraints(0, 1, 1, 1, 1.0, 0.3, GridBagConstraints.NORTH, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0)); + + add(imagePanel, BorderLayout.CENTER); + } + + public void changeText(String text) { + iconLabel.setText(text); + } + + public void stopTimer() { + timeLabel.stopTimer(); + } + +} + diff --git a/src/java/org/jivesoftware/sparkimpl/plugin/phone/PhoneDialog.java b/src/java/org/jivesoftware/sparkimpl/plugin/phone/PhoneDialog.java new file mode 100644 index 00000000..62b30cfe --- /dev/null +++ b/src/java/org/jivesoftware/sparkimpl/plugin/phone/PhoneDialog.java @@ -0,0 +1,43 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.sparkimpl.plugin.phone; + +import org.jivesoftware.resource.SparkRes; +import org.jivesoftware.spark.util.GraphicUtils; + +import javax.swing.ImageIcon; +import javax.swing.JComponent; +import javax.swing.JFrame; + +import java.awt.BorderLayout; + +public class PhoneDialog { + + public static JFrame invoke(JComponent comp, String title, String description, ImageIcon icon) { + final JFrame frame = new JFrame(); + + frame.setIconImage(SparkRes.getImageIcon(SparkRes.TELEPHONE_24x24).getImage()); + frame.setTitle(title); + + frame.getContentPane().setLayout(new BorderLayout()); + + frame.getContentPane().add(comp, BorderLayout.CENTER); + frame.pack(); + frame.setSize(300, 200); + + // Center panel on screen + GraphicUtils.centerWindowOnScreen(frame); + + frame.setVisible(true); + + return frame; + } +} diff --git a/src/java/org/jivesoftware/sparkimpl/plugin/phone/PhonePlugin.java b/src/java/org/jivesoftware/sparkimpl/plugin/phone/PhonePlugin.java new file mode 100644 index 00000000..0ffe2833 --- /dev/null +++ b/src/java/org/jivesoftware/sparkimpl/plugin/phone/PhonePlugin.java @@ -0,0 +1,295 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.sparkimpl.plugin.phone; + +import org.jivesoftware.phone.client.BasePhoneEventListener; +import org.jivesoftware.phone.client.HangUpEvent; +import org.jivesoftware.phone.client.OnPhoneEvent; +import org.jivesoftware.phone.client.PhoneActionException; +import org.jivesoftware.phone.client.PhoneClient; +import org.jivesoftware.phone.client.RingEvent; +import org.jivesoftware.phone.client.action.PhoneActionIQProvider; +import org.jivesoftware.phone.client.event.PhoneEventPacketExtensionProvider; +import org.jivesoftware.resource.SparkRes; +import org.jivesoftware.smack.XMPPConnection; +import org.jivesoftware.smack.provider.ProviderManager; +import org.jivesoftware.smack.util.StringUtils; +import org.jivesoftware.spark.SparkManager; +import org.jivesoftware.spark.plugin.ContextMenuListener; +import org.jivesoftware.spark.plugin.Plugin; +import org.jivesoftware.spark.ui.ChatRoom; +import org.jivesoftware.spark.ui.ChatRoomButton; +import org.jivesoftware.spark.ui.ChatRoomListenerAdapter; +import org.jivesoftware.spark.ui.ContactItem; +import org.jivesoftware.spark.ui.ContactList; +import org.jivesoftware.spark.ui.rooms.ChatRoomImpl; +import org.jivesoftware.spark.util.ModelUtil; +import org.jivesoftware.spark.util.ResourceUtils; +import org.jivesoftware.spark.util.SwingWorker; +import org.jivesoftware.spark.util.log.Log; + +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.JFrame; +import javax.swing.JMenu; +import javax.swing.JMenuItem; +import javax.swing.JPopupMenu; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; +import java.awt.event.MouseEvent; + +public class PhonePlugin implements Plugin { + public static PhoneClient phoneClient; + private DialPanel dialPanel; +// private Alert incomingDialog; + private JFrame dialDialog; + + public void initialize() { + ProviderManager.addExtensionProvider("phone-event", "http://jivesoftware.com/xmlns/phone", new PhoneEventPacketExtensionProvider()); + ProviderManager.addIQProvider("phone-action", "http://jivesoftware.com/xmlns/phone", new PhoneActionIQProvider()); + + final XMPPConnection con = SparkManager.getConnection(); + + SwingWorker worker = new SwingWorker() { + public Object construct() { + try { + phoneClient = new PhoneClient(con); + + // Add BaseListener + phoneClient.addEventListener(new PhoneListener()); + } + catch (Exception e) { + // Ignore because the user does not have support. + //Log.error(e); + } + return phoneClient; + } + + public void finished() { + if (phoneClient != null) { + setupPhoneSystem(); + } + } + }; + + worker.start(); + } + + private void setupPhoneSystem() { + // Add Dial Menu + final JMenu viewMenu = SparkManager.getMainWindow().getMenuByName("Actions"); + JMenuItem dialNumberMenu = new JMenuItem("Dial Number", SparkRes.getImageIcon(SparkRes.ON_PHONE_IMAGE)); + ResourceUtils.resButton(dialNumberMenu, "&Dial Number"); + + // Add Listener + dialNumberMenu.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + dialPanel = new DialPanel(); + dialPanel.getDialButton().addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + String number = dialPanel.getNumberToDial(); + if (ModelUtil.hasLength(number)) { + dialPanel.setText("Calling " + number); + dialPanel.changeToRinging(); + callExtension(number); + + } + + } + }); + + dialDialog = PhoneDialog.invoke(dialPanel, "Dial Phone", "Insert number to call", null); + dialPanel.getDialField().requestFocusInWindow(); + + dialPanel.getDialField().addKeyListener(new KeyAdapter() { + public void keyPressed(KeyEvent e) { + if (e.getKeyChar() == KeyEvent.VK_ENTER) { + try { + String number = dialPanel.getNumberToDial(); + if (ModelUtil.hasLength(number)) { + dialPanel.setText("Calling " + number); + dialPanel.changeToRinging(); + callExtension(number); + + } + e.consume(); + } + catch (Exception ex) { + Log.error("Error performing search.", ex); + } + } + } + }); + } + + }); + viewMenu.add(dialNumberMenu); + + // Add ChatRoomListener to call users based on JID + SparkManager.getChatManager().addChatRoomListener(new ChatRoomListenerAdapter() { + public void chatRoomOpened(final ChatRoom room) { + if (room instanceof ChatRoomImpl) { + final ChatRoomButton callButton = new ChatRoomButton("", SparkRes.getImageIcon(SparkRes.TELEPHONE_24x24)); + callButton.setToolTipText("Make a call."); + final ChatRoomImpl chatRoom = (ChatRoomImpl)room; + boolean phoneEnabled = false; + try { + phoneEnabled = phoneClient.isPhoneEnabled(StringUtils.parseBareAddress(chatRoom.getParticipantJID())); + } + catch (Exception e) { + Log.error(e); + } + + if (phoneEnabled) { + room.getToolBar().addChatRoomButton(callButton); + callButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + callJID(chatRoom.getParticipantJID()); + } + }); + } + } + } + }); + + ContactList contactList = SparkManager.getWorkspace().getContactList(); + contactList.addContextMenuListener(new ContextMenuListener() { + public void poppingUp(Object object, final JPopupMenu popup) { + if (object instanceof ContactItem) { + final ContactItem item = (ContactItem)object; + + boolean phoneEnabled = false; + + + try { + phoneEnabled = phoneClient.isPhoneEnabled(item.getFullJID()); + } + catch (Exception e) { + Log.error("There was an error retrieving phone information.", e); + } + + if (phoneEnabled) { + Action callAction = new AbstractAction() { + public void actionPerformed(ActionEvent e) { + callJID(item.getFullJID()); + } + }; + + callAction.putValue(Action.NAME, "Call"); + callAction.putValue(Action.SMALL_ICON, SparkRes.getImageIcon(SparkRes.ON_PHONE_IMAGE)); + popup.add(callAction); + } + } + } + + public void poppingDown(JPopupMenu popup) { + + } + + public boolean handleDefaultAction(MouseEvent e) { + return false; + } + }); + } + + private class PhoneListener extends BasePhoneEventListener { + + public void handleOnPhone(OnPhoneEvent event) { + if (dialDialog != null) { + dialDialog.setVisible(false); + } + /* + if (incomingDialog != null) { + incomingDialog.hidePopupImmediately(); + } + */ + } + + public void handleHangUp(HangUpEvent event) { + if (dialDialog != null) { + dialDialog.setVisible(false); + } + /* + if (incomingDialog != null) { + incomingDialog.hidePopupImmediately(); + } + + */ + + } + + public void handleRing(RingEvent event) { + IncomingCall incomingCall = new IncomingCall(); + boolean idExists = false; + if (ModelUtil.hasLength(event.getCallerIDName())) { + incomingCall.setCallerName(event.getCallerIDName()); + idExists = true; + } + + if (ModelUtil.hasLength(event.getCallerID())) { + incomingCall.setCallerNumber(event.getCallerID()); + idExists = true; + } + + if (!idExists) { + incomingCall.setCallerName("No caller ID available."); + } + + //incomingDialog = SparkManager.getNotificationsEngine().showWindow(incomingCall, 500000000); + } + } + + public void callExtension(final String number) { + Thread thread = new Thread(new Runnable() { + public void run() { + try { + phoneClient.dialByExtension(number); + } + catch (PhoneActionException e) { + Log.error(e); + } + } + }); + + thread.start(); + } + + public void callJID(String jid) { + try { + phoneClient.dialByJID(jid); + } + catch (PhoneActionException e) { + Log.error(e); + } + } + + + public static PhoneClient getPhoneClient() { + return phoneClient; + } + + public void shutdown() { + + } + + public boolean canShutDown() { + return true; + } + + public void uninstall() { + // Do nothing. + } + + +} diff --git a/src/java/org/jivesoftware/sparkimpl/plugin/scratchpad/PrivateNotes.java b/src/java/org/jivesoftware/sparkimpl/plugin/scratchpad/PrivateNotes.java new file mode 100644 index 00000000..5de107c2 --- /dev/null +++ b/src/java/org/jivesoftware/sparkimpl/plugin/scratchpad/PrivateNotes.java @@ -0,0 +1,142 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.sparkimpl.plugin.scratchpad; + +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smackx.PrivateDataManager; +import org.jivesoftware.smackx.packet.PrivateData; +import org.jivesoftware.smackx.provider.PrivateDataProvider; +import org.jivesoftware.spark.SparkManager; +import org.jivesoftware.spark.util.log.Log; +import org.xmlpull.v1.XmlPullParser; + +/** + * @author Derek DeMoro + */ +public class PrivateNotes implements PrivateData { + + private String notes; + + /** + * Required Empty Constructor to use Bookmarks. + */ + public PrivateNotes() { + } + + + public String getNotes() { + return notes; + } + + public void setNotes(String notes) { + this.notes = notes; + } + + + /** + * Returns the root element name. + * + * @return the element name. + */ + public String getElementName() { + return "scratchpad"; + } + + /** + * Returns the root element XML namespace. + * + * @return the namespace. + */ + public String getNamespace() { + return "scratchpad:notes"; + } + + /** + * Returns the XML reppresentation of the PrivateData. + * + * @return the private data as XML. + */ + public String toXML() { + StringBuffer buf = new StringBuffer(); + buf.append(""); + + if (getNotes() != null) { + buf.append("" + getNotes() + ""); + } + + buf.append(""); + return buf.toString(); + } + + /** + * The IQ Provider for BookmarkStorage. + * + * @author Derek DeMoro + */ + public static class Provider implements PrivateDataProvider { + + PrivateNotes notes = new PrivateNotes(); + + /** + * Empty Constructor for PrivateDataProvider. + */ + public Provider() { + super(); + } + + public PrivateData parsePrivateData(XmlPullParser parser) throws Exception { + boolean done = false; + while (!done) { + int eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG && "text".equals(parser.getName())) { + notes.setNotes(parser.nextText()); + } + else if (eventType == XmlPullParser.END_TAG) { + if ("scratchpad".equals(parser.getName())) { + done = true; + } + } + } + + + return notes; + } + } + + public static void savePrivateNotes(PrivateNotes notes) { + PrivateDataManager manager = new PrivateDataManager(SparkManager.getConnection()); + + PrivateDataManager.addPrivateDataProvider("scratchpad", "scratchpad:notes", new PrivateNotes.Provider()); + try { + manager.setPrivateData(notes); + } + catch (XMPPException e) { + Log.error(e); + } + } + + public static PrivateNotes getPrivateNotes() { + PrivateDataManager manager = new PrivateDataManager(SparkManager.getConnection()); + + PrivateDataManager.addPrivateDataProvider("scratchpad", "scratchpad:notes", new PrivateNotes.Provider()); + + PrivateNotes notes = null; + + try { + notes = (PrivateNotes)manager.getPrivateData("scratchpad", "scratchpad:notes"); + } + catch (XMPPException e) { + Log.error(e); + } + + return notes; + } +} diff --git a/src/java/org/jivesoftware/sparkimpl/plugin/scratchpad/ScratchPadPlugin.java b/src/java/org/jivesoftware/sparkimpl/plugin/scratchpad/ScratchPadPlugin.java new file mode 100644 index 00000000..4f28e925 --- /dev/null +++ b/src/java/org/jivesoftware/sparkimpl/plugin/scratchpad/ScratchPadPlugin.java @@ -0,0 +1,345 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.sparkimpl.plugin.scratchpad; + +import org.jivesoftware.resource.SparkRes; +import org.jivesoftware.spark.SparkManager; +import org.jivesoftware.spark.component.RolloverButton; +import org.jivesoftware.spark.component.VerticalFlowLayout; +import org.jivesoftware.spark.plugin.Plugin; +import org.jivesoftware.spark.ui.ContactList; +import org.jivesoftware.spark.util.GraphicUtils; +import org.jivesoftware.spark.util.ModelUtil; + +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.BorderFactory; +import javax.swing.JCheckBox; +import javax.swing.JComponent; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTextPane; +import javax.swing.KeyStroke; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Font; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Image; +import java.awt.Insets; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.event.MouseMotionListener; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.awt.geom.AffineTransform; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +/** + * + */ +public class ScratchPadPlugin implements Plugin { + private ContactList contactList; + + + public void initialize() { + contactList = SparkManager.getWorkspace().getContactList(); + contactList.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("control F6"), "viewNotes"); + + contactList.getActionMap().put("viewNotes", new AbstractAction("viewNotes") { + public void actionPerformed(ActionEvent evt) { + // Retrieve notes and dispaly in editor. + retrieveNotes(); + } + }); + + contactList.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("control F5"), "viewTasks"); + + contactList.getActionMap().put("viewTasks", new AbstractAction("viewTasks") { + public void actionPerformed(ActionEvent evt) { + // Retrieve notes and dispaly in editor. + showTaskList(); + } + }); + } + + private void showTaskList() { + final JFrame frame = new JFrame("Tasks"); + frame.setIconImage(SparkManager.getMainWindow().getIconImage()); + + final Map map = new HashMap(); + final JPanel mainPanel = new JPanel(); + mainPanel.setLayout(new VerticalFlowLayout(VerticalFlowLayout.TOP, 0, 0, true, false)); + mainPanel.setBackground(Color.white); + + final RolloverButton addButton = new RolloverButton("Add Task", SparkRes.getImageIcon(SparkRes.ADD_IMAGE_24x24)); + mainPanel.add(addButton); + addButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + String taskTitle = JOptionPane.showInputDialog(frame, "Enter Task:", "Create Task", JOptionPane.INFORMATION_MESSAGE); + if (!ModelUtil.hasLength(taskTitle)) { + return; + } + Task task = new Task(); + task.setTitle(taskTitle); + final JCheckBox box = new JCheckBox(); + box.setOpaque(false); + box.setText(task.getTitle()); + mainPanel.add(box); + map.put(box, task); + mainPanel.invalidate(); + mainPanel.validate(); + mainPanel.repaint(); + + box.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + mainPanel.remove(box); + map.remove(box); + mainPanel.invalidate(); + mainPanel.validate(); + mainPanel.repaint(); + } + }); + + } + }); + + Tasks tasks = Tasks.getTaskList(SparkManager.getConnection()); + Iterator taskIter = tasks.getTasks().iterator(); + while (taskIter.hasNext()) { + Task task = (Task)taskIter.next(); + final JCheckBox box = new JCheckBox(); + box.setOpaque(false); + box.setText(task.getTitle()); + mainPanel.add(box); + map.put(box, task); + mainPanel.invalidate(); + mainPanel.validate(); + mainPanel.repaint(); + + box.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + mainPanel.remove(box); + map.remove(box); + mainPanel.invalidate(); + mainPanel.validate(); + mainPanel.repaint(); + } + }); + } + + + final JScrollPane pane = new JScrollPane(mainPanel); + + frame.getContentPane().setLayout(new BorderLayout()); + frame.getContentPane().add(pane, BorderLayout.CENTER); + frame.pack(); + frame.setSize(400, 400); + + final Action saveAction = new AbstractAction() { + public void actionPerformed(ActionEvent actionEvent) { + // Save it. + Tasks tasks = new Tasks(); + Iterator iter = map.values().iterator(); + while (iter.hasNext()) { + Task task = (Task)iter.next(); + tasks.addTask(task); + } + + Tasks.saveTasks(tasks, SparkManager.getConnection()); + } + }; + + addButton.addKeyListener(new KeyAdapter() { + public void keyReleased(KeyEvent e) { + if (e.getKeyChar() == KeyEvent.VK_ESCAPE) { + frame.dispose(); + + saveAction.actionPerformed(null); + } + } + }); + + + frame.addWindowListener(new WindowAdapter() { + public void windowClosing(WindowEvent windowEvent) { + saveAction.actionPerformed(null); + } + }); + + + GraphicUtils.centerWindowOnComponent(frame, SparkManager.getMainWindow()); + frame.setVisible(true); + } + + private void retrieveNotes() { + final PrivateNotes privateNotes = PrivateNotes.getPrivateNotes(); + String text = privateNotes.getNotes(); + + final JLabel titleLabel = new JLabel("Notepad"); + titleLabel.setBorder(BorderFactory.createMatteBorder(0, 0, 1, 0, Color.LIGHT_GRAY)); + titleLabel.setFont(new Font("Dialog", Font.BOLD, 13)); + titleLabel.setHorizontalAlignment(JLabel.CENTER); + + final Image backgroundImage = SparkRes.getImageIcon(SparkRes.STICKY_NOTE_IMAGE).getImage(); + final JTextPane pane = new JTextPane(); + + pane.setOpaque(false); + + final JScrollPane scrollPane = new JScrollPane(pane, JScrollPane.VERTICAL_SCROLLBAR_NEVER, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); + scrollPane.setOpaque(false); + scrollPane.getViewport().setOpaque(false); + scrollPane.setBorder(null); + pane.setText(text); + final RolloverButton button = new RolloverButton("Save", null); + final RolloverButton cancelButton = new RolloverButton("Cancel", null); + + final JFrame frame = new JFrame("Notes"); + frame.setUndecorated(true); + titleLabel.addMouseMotionListener(new DragWindowAdapter(frame)); + final JPanel mainPanel = new JPanel() { + public void paintComponent(Graphics g) { + double scaleX = getWidth() / (double)backgroundImage.getWidth(null); + double scaleY = getHeight() / (double)backgroundImage.getHeight(null); + AffineTransform xform = AffineTransform.getScaleInstance(scaleX, scaleY); + ((Graphics2D)g).drawImage(backgroundImage, xform, this); + } + }; + + pane.addKeyListener(new KeyAdapter() { + public void keyReleased(KeyEvent e) { + if (e.getKeyChar() == KeyEvent.VK_ESCAPE) { + frame.dispose(); + + // Save it. + String text = pane.getText(); + privateNotes.setNotes(text); + PrivateNotes.savePrivateNotes(privateNotes); + } + } + }); + + mainPanel.setBackground(Color.white); + mainPanel.setLayout(new GridBagLayout()); + frame.setIconImage(SparkManager.getMainWindow().getIconImage()); + frame.getContentPane().add(mainPanel); + + mainPanel.add(titleLabel, new GridBagConstraints(0, 0, 3, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0)); + + mainPanel.add(scrollPane, new GridBagConstraints(0, 1, 3, 1, 1.0, 1.0, GridBagConstraints.WEST, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0)); + mainPanel.add(button, new GridBagConstraints(1, 2, 1, 1, 1.0, 0.0, GridBagConstraints.EAST, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0)); + mainPanel.add(cancelButton, new GridBagConstraints(2, 2, 1, 1, 0.0, 0.0, GridBagConstraints.EAST, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0)); + + frame.pack(); + frame.setSize(400, 400); + + + GraphicUtils.centerWindowOnComponent(frame, SparkManager.getMainWindow()); + frame.setVisible(true); + + + button.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent actionEvent) { + frame.dispose(); + + // Save it. + String text = pane.getText(); + privateNotes.setNotes(text); + PrivateNotes.savePrivateNotes(privateNotes); + } + }); + + cancelButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent actionEvent) { + frame.dispose(); + } + }); + } + + public void shutdown() { + } + + public boolean canShutDown() { + return true; + } + + public void uninstall() { + } + + private class DragWindowAdapter extends MouseAdapter + implements MouseMotionListener { + private JFrame m_msgWnd; + private int m_mousePrevX, + m_mousePrevY; + private int m_frameX, + m_frameY; + + public DragWindowAdapter(JFrame mw) { + m_msgWnd = mw; + } + + public void mousePressed(MouseEvent e) { + super.mousePressed(e); + m_mousePrevX = e.getX(); + m_mousePrevY = e.getY(); + m_frameX = 0; + m_frameY = 0; + } + + public void mouseDragged(MouseEvent e) { + int X = e.getX(); + int Y = e.getY(); + int MsgX = m_msgWnd.getX(); + int MsgY = m_msgWnd.getY(); + + int moveX = X - m_mousePrevX; // Negative if move left + int moveY = Y - m_mousePrevY; // Negative if move down + if (moveX == 0 && moveY == 0) return; + m_mousePrevX = X - moveX; + m_mousePrevY = Y - moveY; + + //System.out.println("mouseDragged x,y = (" + X + "," + Y + + // ") diff (" + moveX + "," + moveY + + // ") MsgX/MsgY = " + MsgX + "," + MsgY); + + // mouseDragged caused by setLocation() on frame. + if (m_frameX == MsgX && m_frameY == MsgY) { + m_frameX = 0; + m_frameY = 0; + return; + } + + // '-' would cause wrong direction for movement. + int newFrameX = MsgX + moveX; + // '-' would cause wrong + int newFrameY = MsgY + moveY; + + m_frameX = newFrameX; + m_frameY = newFrameY; + m_msgWnd.setLocation(newFrameX, newFrameY); + } + + public void mouseMoved(MouseEvent e) { + } + } +} diff --git a/src/java/org/jivesoftware/sparkimpl/plugin/scratchpad/Task.java b/src/java/org/jivesoftware/sparkimpl/plugin/scratchpad/Task.java new file mode 100644 index 00000000..8365350b --- /dev/null +++ b/src/java/org/jivesoftware/sparkimpl/plugin/scratchpad/Task.java @@ -0,0 +1,29 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.sparkimpl.plugin.scratchpad; + +/** + * + */ +public class Task { + + private String title; + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + +} diff --git a/src/java/org/jivesoftware/sparkimpl/plugin/scratchpad/Tasks.java b/src/java/org/jivesoftware/sparkimpl/plugin/scratchpad/Tasks.java new file mode 100644 index 00000000..cb45a0d6 --- /dev/null +++ b/src/java/org/jivesoftware/sparkimpl/plugin/scratchpad/Tasks.java @@ -0,0 +1,197 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.sparkimpl.plugin.scratchpad; + +import org.jivesoftware.smack.XMPPConnection; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smackx.PrivateDataManager; +import org.jivesoftware.smackx.packet.PrivateData; +import org.jivesoftware.smackx.provider.PrivateDataProvider; +import org.jivesoftware.spark.util.log.Log; +import org.xmlpull.v1.XmlPullParser; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * @author Derek DeMoro + */ +public class Tasks implements PrivateData { + + private List tasks = new ArrayList(); + + /** + * Required Empty Constructor to use Tasks. + */ + public Tasks() { + } + + + public List getTasks() { + return tasks; + } + + public void setTasks(List tasks) { + this.tasks = tasks; + } + + public void addTask(Task task) { + tasks.add(task); + } + + + /** + * Returns the root element name. + * + * @return the element name. + */ + public String getElementName() { + return "scratchpad"; + } + + /** + * Returns the root element XML namespace. + * + * @return the namespace. + */ + public String getNamespace() { + return "scratchpad:tasks"; + } + + /** + * Returns the XML reppresentation of the PrivateData. + * + * @return the private data as XML. + */ + public String toXML() { + StringBuffer buf = new StringBuffer(); + buf.append(""); + buf.append(""); + + Iterator iter = getTasks().iterator(); + while (iter.hasNext()) { + Task task = (Task)iter.next(); + buf.append(""); + buf.append("").append(task.getTitle()).append(""); + buf.append(""); + } + + buf.append(""); + + buf.append(""); + return buf.toString(); + } + + /** + * The IQ Provider for Tasks + * + * @author Derek DeMoro + */ + public static class Provider implements PrivateDataProvider { + + Tasks tasks = new Tasks(); + + /** + * Empty Constructor for PrivateDataProvider. + */ + public Provider() { + super(); + } + + public PrivateData parsePrivateData(XmlPullParser parser) throws Exception { + boolean done = false; + while (!done) { + int eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG && "task".equals(parser.getName())) { + tasks.addTask(getTask(parser)); + } + else if (eventType == XmlPullParser.END_TAG) { + if ("scratchpad".equals(parser.getName())) { + done = true; + } + } + } + + + return tasks; + } + } + + public static Task getTask(XmlPullParser parser) throws Exception { + final Task task = new Task(); + + boolean done = false; + while (!done) { + int eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG && "title".equals(parser.getName())) { + task.setTitle(parser.nextText()); + } + + else if (eventType == XmlPullParser.END_TAG) { + if ("task".equals(parser.getName())) { + done = true; + } + } + } + + + return task; + } + + + public static void saveTasks(Tasks tasks, XMPPConnection con) { + PrivateDataManager manager = new PrivateDataManager(con); + + PrivateDataManager.addPrivateDataProvider("scratchpad", "scratchpad:tasks", new Tasks.Provider()); + try { + manager.setPrivateData(tasks); + } + catch (XMPPException e) { + Log.error(e); + } + } + + public static Tasks getTaskList(XMPPConnection con) { + PrivateDataManager manager = new PrivateDataManager(con); + + PrivateDataManager.addPrivateDataProvider("scratchpad", "scratchpad:tasks", new Tasks.Provider()); + + + Tasks tasks = null; + + try { + tasks = (Tasks)manager.getPrivateData("scratchpad", "scratchpad:tasks"); + } + catch (XMPPException e) { + Log.error(e); + } + + return tasks; + } + + public static void main(String args[]) throws Exception { + XMPPConnection.DEBUG_ENABLED = true; + XMPPConnection con = new XMPPConnection("derek", 5222); + con.login("derek", "test"); + + Tasks tasks = getTaskList(con); + + Task task = new Task(); + task.setTitle("Hi"); + tasks.addTask(task); + + saveTasks(tasks, con); + System.out.println(tasks); + + + } +} diff --git a/src/java/org/jivesoftware/sparkimpl/plugin/sparklers/SparklersPlugin.java b/src/java/org/jivesoftware/sparkimpl/plugin/sparklers/SparklersPlugin.java new file mode 100644 index 00000000..1e516e01 --- /dev/null +++ b/src/java/org/jivesoftware/sparkimpl/plugin/sparklers/SparklersPlugin.java @@ -0,0 +1,39 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.sparkimpl.plugin.sparklers; + +import org.jivesoftware.spark.plugin.Plugin; +import org.jivesoftware.spark.ui.Sparkler; +import org.jivesoftware.spark.ui.SparklerDecorator; + +public class SparklersPlugin implements Plugin { + + public void initialize() { + Sparkler sparkler = new Sparkler() { + public void decorateMessage(String message, SparklerDecorator decorator) { + decorator.setURL("Spark", "http://www.test.com"); + } + }; + + } + + public void shutdown() { + + } + + public boolean canShutDown() { + return false; + } + + public void uninstall() { + + } +} diff --git a/src/java/org/jivesoftware/sparkimpl/plugin/transcripts/ChatTranscript.java b/src/java/org/jivesoftware/sparkimpl/plugin/transcripts/ChatTranscript.java new file mode 100644 index 00000000..98cef7a2 --- /dev/null +++ b/src/java/org/jivesoftware/sparkimpl/plugin/transcripts/ChatTranscript.java @@ -0,0 +1,40 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.sparkimpl.plugin.transcripts; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +public class ChatTranscript { + + private final List messages = new ArrayList(); + + public void addHistoryMessage(HistoryMessage entry) { + messages.add(entry); + } + + public Collection getMessages() { + return messages; + } + + public Collection getNumberOfEntries(int number) { + int listSize = messages.size(); + + if (messages.size() <= number) { + return messages; + } + else { + int start = listSize - number; + return messages.subList(start, listSize); + } + } +} diff --git a/src/java/org/jivesoftware/sparkimpl/plugin/transcripts/ChatTranscriptPlugin.java b/src/java/org/jivesoftware/sparkimpl/plugin/transcripts/ChatTranscriptPlugin.java new file mode 100644 index 00000000..22a11270 --- /dev/null +++ b/src/java/org/jivesoftware/sparkimpl/plugin/transcripts/ChatTranscriptPlugin.java @@ -0,0 +1,501 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.sparkimpl.plugin.transcripts; + +import org.jivesoftware.MainWindowListener; +import org.jivesoftware.resource.SparkRes; +import org.jivesoftware.smack.ConnectionListener; +import org.jivesoftware.smack.packet.Message; +import org.jivesoftware.smack.util.StringUtils; +import org.jivesoftware.spark.SparkManager; +import org.jivesoftware.spark.component.VerticalFlowLayout; +import org.jivesoftware.spark.component.panes.CollapsiblePane; +import org.jivesoftware.spark.component.panes.CollapsiblePaneListener; +import org.jivesoftware.spark.plugin.ContextMenuListener; +import org.jivesoftware.spark.ui.ChatContainer; +import org.jivesoftware.spark.ui.ChatRoom; +import org.jivesoftware.spark.ui.ChatRoomButton; +import org.jivesoftware.spark.ui.ChatRoomListener; +import org.jivesoftware.spark.ui.ContactItem; +import org.jivesoftware.spark.ui.ContactList; +import org.jivesoftware.spark.ui.TranscriptWindow; +import org.jivesoftware.spark.ui.rooms.ChatRoomImpl; +import org.jivesoftware.spark.util.GraphicUtils; +import org.jivesoftware.spark.util.ModelUtil; +import org.jivesoftware.spark.util.SwingWorker; +import org.jivesoftware.spark.util.log.Log; +import org.jivesoftware.sparkimpl.settings.local.LocalPreferences; +import org.jivesoftware.sparkimpl.settings.local.SettingsManager; + +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.JFrame; +import javax.swing.JPanel; +import javax.swing.JPopupMenu; +import javax.swing.JScrollPane; +import javax.swing.text.BadLocationException; +import javax.swing.text.SimpleAttributeSet; +import javax.swing.text.StyleConstants; +import javax.swing.text.StyledDocument; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.io.File; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collection; +import java.util.Date; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * The ChatTranscriptPlugin is responsible for transcript handling within Spark. + */ +public class ChatTranscriptPlugin implements ChatRoomListener { + private String LAST_YEAR = "LAST_YEAR"; + private String LAST_MONTH = "LAST_MONTH"; + private String LAST_WEEK = "LAST_WEEK"; + private String LAST_TWO_MONTHS = "LAST_TWO_MONTHS"; + private String LAST_SIX_MONTHS = "LAST_SIX_MONTHS"; + private String BEFORE = "BEFORE"; + + /** + * Register the listeners for transcript persistence. + */ + public void initialize() { + SparkManager.getChatManager().addChatRoomListener(this); + + final ContactList contactList = SparkManager.getWorkspace().getContactList(); + + + final Action viewHistoryAction = new AbstractAction() { + public void actionPerformed(ActionEvent actionEvent) { + ContactItem item = (ContactItem)contactList.getSelectedUsers().iterator().next(); + final String jid = item.getFullJID(); + + showHistory(jid); + } + }; + + viewHistoryAction.putValue(Action.NAME, "View Contact History"); + viewHistoryAction.putValue(Action.SMALL_ICON, SparkRes.getImageIcon(SparkRes.HISTORY_16x16)); + + contactList.addContextMenuListener(new ContextMenuListener() { + public void poppingUp(Object object, JPopupMenu popup) { + if (object instanceof ContactItem) { + popup.add(viewHistoryAction); + } + } + + public void poppingDown(JPopupMenu popup) { + + } + + public boolean handleDefaultAction(MouseEvent e) { + return false; + } + }); + + SparkManager.getMainWindow().addMainWindowListener(new MainWindowListener() { + public void shutdown() { + persistConversations(); + } + + public void mainWindowActivated() { + + } + + public void mainWindowDeactivated() { + + } + }); + + + SparkManager.getConnection().addConnectionListener(new ConnectionListener() { + public void connectionClosed() { + } + + public void connectionClosedOnError(Exception e) { + persistConversations(); + } + }); + } + + public void persistConversations() { + ChatContainer chatRooms = SparkManager.getChatManager().getChatContainer(); + Collection rooms = chatRooms.getChatRooms(); + Iterator iter = rooms.iterator(); + while (iter.hasNext()) { + ChatRoom room = (ChatRoom)iter.next(); + if (room instanceof ChatRoomImpl) { + ChatRoomImpl roomImpl = (ChatRoomImpl)room; + if (roomImpl.isActive()) { + persistChatRoom(roomImpl); + } + } + } + } + + public boolean canShutDown() { + return true; + } + + public void chatRoomOpened(final ChatRoom room) { + LocalPreferences pref = SettingsManager.getLocalPreferences(); + if (pref.isHideChatHistory()) { + return; + } + + final String jid = room.getRoomname(); + File transcriptFile = ChatTranscripts.getTranscriptFile(jid); + if (!transcriptFile.exists()) { + return; + } + + final TranscriptWindow roomWindow = room.getTranscriptWindow(); + + final TranscriptWindow window = new TranscriptWindow(); + window.setEditable(false); + window.addMouseListener(new MouseAdapter() { + public void mouseClicked(MouseEvent e) { + room.getChatInputEditor().requestFocusInWindow(); + } + }); + insertHistory(room, roomWindow); + + if (room instanceof ChatRoomImpl) { + // Add History Button + ChatRoomButton chatRoomButton = new ChatRoomButton(SparkRes.getImageIcon(SparkRes.HISTORY_24x24_IMAGE)); + room.getToolBar().addChatRoomButton(chatRoomButton); + chatRoomButton.setToolTipText("View history"); + chatRoomButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent actionEvent) { + ChatRoomImpl roomImpl = (ChatRoomImpl)room; + showHistory(roomImpl.getParticipantJID()); + } + }); + } + } + + private void insertHistory(final ChatRoom room, final TranscriptWindow roomWindow) { + final StringBuffer buf = new StringBuffer(); + + + if (room.getChatType() == Message.Type.CHAT) { + SwingWorker worker = new SwingWorker() { + public Object construct() { + try { + Thread.sleep(10); + } + catch (InterruptedException e) { + Log.error("Exception in Chat Transcript Loading.", e); + } + final String jid = room.getRoomname(); + + ChatTranscript transcript = ChatTranscripts.getChatTranscript(jid); + + final Iterator messages = transcript.getNumberOfEntries(20).iterator(); + boolean isNew = false; + while (messages.hasNext()) { + while (messages != null && messages.hasNext()) { + isNew = true; + try { + HistoryMessage message = (HistoryMessage)messages.next(); + String from = StringUtils.parseName(message.getFrom()); + Date date = message.getDate(); + final SimpleDateFormat formatter = new SimpleDateFormat("MM/dd/yyyy h:mm a"); + String dateValue = "[" + formatter.format(date) + "] "; + buf.append(dateValue); + buf.append(" "); + buf.append(from); + buf.append(": "); + buf.append(message.getBody()); + buf.append("\n"); + } + catch (Exception e) { + break; + } + } + + } + return new Boolean(isNew); + } + + public void finished() { + Boolean boo = (Boolean)get(); + if (boo.booleanValue()) { + StyledDocument doc = (StyledDocument)roomWindow.getDocument(); + final SimpleAttributeSet styles = new SimpleAttributeSet(); + StyleConstants.setFontSize(styles, 12); + StyleConstants.setFontFamily(styles, "Dialog"); + StyleConstants.setForeground(styles, Color.LIGHT_GRAY); + + // Insert the image at the end of the text + try { + doc.insertString(0, buf.toString(), styles); + } + catch (BadLocationException e) { + Log.error(e); + } + } + + room.scrollToBottom(); + } + }; + worker.start(); + } + } + + public void chatRoomLeft(ChatRoom room) { + + } + + public void chatRoomClosed(final ChatRoom room) { + // Persist only agent to agent chat rooms. + if (room.getChatType() == Message.Type.CHAT) { + + SwingWorker persistMessageWorker = new SwingWorker() { + public Object construct() { + persistChatRoom(room); + return "ok"; + } + }; + persistMessageWorker.start(); + } + } + + private void persistChatRoom(final ChatRoom room) { + LocalPreferences pref = SettingsManager.getLocalPreferences(); + if (pref.isHideChatHistory()) { + return; + } + + final String jid = room.getRoomname(); + + List transcripts = room.getTranscripts(); + + Iterator messages = transcripts.iterator(); + + ChatTranscript transcript = ChatTranscripts.getChatTranscript(jid); + while (messages.hasNext()) { + Message message = (Message)messages.next(); + HistoryMessage history = new HistoryMessage(); + history.setTo(message.getTo()); + history.setFrom(message.getFrom()); + history.setBody(message.getBody()); + Date date = (Date)message.getProperty("date"); + if (date != null) { + history.setDate(date); + } + else { + history.setDate(new Date()); + } + transcript.addHistoryMessage(history); + } + + ChatTranscripts.saveTranscript(jid); + } + + public void chatRoomActivated(ChatRoom room) { + + } + + public void userHasJoined(ChatRoom room, String userid) { + + } + + public void userHasLeft(ChatRoom room, String userid) { + + } + + public void uninstall() { + // Do nothing. + } + + private void showHistory(final String jid) { + final Map dateMap = new LinkedHashMap(); + SwingWorker worker = new SwingWorker() { + final TranscriptWindow pane = new TranscriptWindow(); + + public Object construct() { + ChatTranscript transcript = ChatTranscripts.getChatTranscript(jid); + Iterator messages = transcript.getMessages().iterator(); + + Calendar cal = Calendar.getInstance(); + Date now = new Date(); + cal.setTime(now); + + cal.add(Calendar.DATE, -7); + + Date lastWeek = cal.getTime(); + + cal.setTime(now); + cal.add(Calendar.MONTH, -1); + + Date lastMonth = cal.getTime(); + + cal.setTime(now); + cal.add(Calendar.MONTH, -2); + + Date twoMonths = cal.getTime(); + + cal.setTime(now); + + cal.add(Calendar.MONTH, -6); + + Date sixMonths = cal.getTime(); + + cal.setTime(now); + + cal.add(Calendar.YEAR, -1); + + Date lastYear = cal.getTime(); + + + while (messages.hasNext()) { + HistoryMessage message = (HistoryMessage)messages.next(); + Date datePosted = message.getDate(); + + String key = ""; + + if (datePosted.getTime() > lastWeek.getTime()) { + // Add to last week + key = "This Week"; + } + else if (datePosted.getTime() > lastMonth.getTime()) { + // Add to last month + key = "One Month"; + } + else if (datePosted.getTime() > twoMonths.getTime()) { + // Add to two months + key = "Two Months"; + } + else if (datePosted.getTime() > sixMonths.getTime()) { + // Add to six months + key = "Six Months"; + } + else if (datePosted.getTime() > lastYear.getTime()) { + // Add to last year. + key = "Year Ago"; + } + else { + // Add to before. + key = "More than a year ago"; + } + + + if (!dateMap.containsKey(key)) { + final List list = new ArrayList(); + list.add(message); + dateMap.put(key, list); + } + else { + final List list = (ArrayList)dateMap.get(key); + list.add(message); + } + } + + return pane; + } + + public void finished() { + final JPanel mainPanel = new JPanel(); + mainPanel.setLayout(new VerticalFlowLayout(VerticalFlowLayout.TOP, 0, 0, true, false)); + mainPanel.setBackground(Color.white); + JScrollPane scrollPane = new JScrollPane(mainPanel); + + // Iterate through and print dates + final Iterator d = dateMap.keySet().iterator(); + List list = new ArrayList(); + while (d.hasNext()) { + list.add(d.next()); + } + + Iterator dates = ModelUtil.reverseListIterator(list.listIterator()); + while (dates.hasNext()) { + final String date = (String)dates.next(); + + // Create collapsible pane + final CollapsiblePane pane = new CollapsiblePane(date); + + pane.setCollapsed(true); + + mainPanel.add(pane); + pane.addCollapsiblePaneListener(new CollapsiblePaneListener() { + boolean expanded = false; + + public void paneExpanded() { + if (!expanded) { + List messages = (List)dateMap.get(date); + final TranscriptWindow window = new TranscriptWindow() { + public Dimension getPreferredSize() { + final Dimension size = super.getPreferredSize(); + size.width = 0; + return size; + } + }; + + Iterator messageIter = messages.iterator(); + while (messageIter.hasNext()) { + HistoryMessage m = (HistoryMessage)messageIter.next(); + String from = m.getFrom(); + String nickname = StringUtils.parseName(from); + final SimpleDateFormat formatter = new SimpleDateFormat("MM/dd/yyyy h:mm a"); + final String date = formatter.format(m.getDate()); + + String prefix = nickname + " [" + date + "]"; + + if (from.equals(SparkManager.getSessionManager().getJID())) { + window.insertCustomMessage(prefix, m.getBody()); + } + else { + window.insertCustomOtherMessage(prefix, m.getBody()); + } + } + pane.setContentPane(window); + expanded = true; + } + } + + public void paneCollapsed() { + } + }); + + + } + + final JFrame frame = new JFrame("History For " + jid); + frame.setIconImage(SparkRes.getImageIcon(SparkRes.HISTORY_16x16).getImage()); + frame.getContentPane().setLayout(new BorderLayout()); + + scrollPane.getVerticalScrollBar().setBlockIncrement(50); + scrollPane.getVerticalScrollBar().setUnitIncrement(20); + + frame.getContentPane().add(scrollPane, BorderLayout.CENTER); + frame.pack(); + frame.setSize(600, 400); + GraphicUtils.centerWindowOnScreen(frame); + frame.setVisible(true); + + } + }; + + worker.start(); + } +} diff --git a/src/java/org/jivesoftware/sparkimpl/plugin/transcripts/ChatTranscripts.java b/src/java/org/jivesoftware/sparkimpl/plugin/transcripts/ChatTranscripts.java new file mode 100644 index 00000000..369a79af --- /dev/null +++ b/src/java/org/jivesoftware/sparkimpl/plugin/transcripts/ChatTranscripts.java @@ -0,0 +1,96 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.sparkimpl.plugin.transcripts; + +import com.thoughtworks.xstream.XStream; +import org.jivesoftware.spark.SparkManager; +import org.jivesoftware.spark.util.log.Log; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.util.HashMap; +import java.util.Map; + +public class ChatTranscripts { + private static Map transcripts = new HashMap(); + private static XStream xstream = new XStream(); + + private ChatTranscripts() { + } + + static { + xstream.alias("transcript", ChatTranscript.class); + xstream.alias("message", HistoryMessage.class); + } + + + public static ChatTranscript getChatTranscript(String jid) { + ChatTranscript transcript = (ChatTranscript)transcripts.get(jid); + if (transcript == null) { + transcript = load(jid); + transcripts.put(jid, transcript); + } + return transcript; + } + + public static void addChatTranscript(String jid, ChatTranscript transcript) { + transcripts.put(jid, transcript); + } + + public static void saveTranscript(String jid) { + + try { + File file = getTranscriptFile(jid); + file.getParentFile().mkdirs(); + ChatTranscript transcript = getChatTranscript(jid); + if (transcript.getMessages().size() == 0) { + file.delete(); + return; + } + + FileOutputStream fout = new FileOutputStream(getTranscriptFile(jid)); + OutputStreamWriter ow = new OutputStreamWriter(fout, "UTF-8"); + + + xstream.toXML(transcript, ow); + } + catch (Exception e) { + Log.error("Error saving settings.", e); + } + } + + private static ChatTranscript load(String jid) { + File transcriptFile = getTranscriptFile(jid); + try { + FileInputStream fis = new FileInputStream(transcriptFile); + InputStreamReader isr = new InputStreamReader(fis, "UTF-8"); + return (ChatTranscript)xstream.fromXML(isr); + } + catch (Exception e) { + // Ignore + } + return new ChatTranscript(); + } + + /** + * Returns the settings file. + * + * @return the settings file. + */ + public static File getTranscriptFile(String jid) { + return new File(SparkManager.getUserDirectory(), "transcripts/" + jid + ".xml"); + } + + +} \ No newline at end of file diff --git a/src/java/org/jivesoftware/sparkimpl/plugin/transcripts/HistoryMessage.java b/src/java/org/jivesoftware/sparkimpl/plugin/transcripts/HistoryMessage.java new file mode 100644 index 00000000..06d9e4cb --- /dev/null +++ b/src/java/org/jivesoftware/sparkimpl/plugin/transcripts/HistoryMessage.java @@ -0,0 +1,53 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.sparkimpl.plugin.transcripts; + +import java.util.Date; + +public class HistoryMessage { + + private String to; + private String from; + private String body; + private Date date; + + public String getTo() { + return to; + } + + public void setTo(String to) { + this.to = to; + } + + public String getFrom() { + return from; + } + + public void setFrom(String from) { + this.from = from; + } + + public Date getDate() { + return date; + } + + public void setDate(Date date) { + this.date = date; + } + + public String getBody() { + return body; + } + + public void setBody(String body) { + this.body = body; + } +} diff --git a/src/java/org/jivesoftware/sparkimpl/plugin/viewer/PluginRenderer.java b/src/java/org/jivesoftware/sparkimpl/plugin/viewer/PluginRenderer.java new file mode 100644 index 00000000..2fecc257 --- /dev/null +++ b/src/java/org/jivesoftware/sparkimpl/plugin/viewer/PluginRenderer.java @@ -0,0 +1,76 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.sparkimpl.plugin.viewer; + +import org.jivesoftware.resource.SparkRes; + +import javax.swing.BorderFactory; +import javax.swing.Icon; +import javax.swing.JLabel; +import javax.swing.JTable; +import javax.swing.border.Border; +import javax.swing.table.TableCellRenderer; + +import java.awt.Color; +import java.awt.Component; + +/** + * A swing renderer used to display labels within a table. + */ +public class PluginRenderer extends JLabel implements TableCellRenderer { + Border unselectedBorder; + Border selectedBorder; + boolean isBordered = true; + + /** + * PluginRenderer + */ + public PluginRenderer() { + setOpaque(true); + } + + public Component getTableCellRendererComponent(JTable table, Object color, boolean isSelected, boolean hasFocus, int row, int column) { + + final Icon icon = SparkRes.getImageIcon(SparkRes.PLUGIN_IMAGE); + setIcon(icon); + + if (isSelected) { + setForeground(table.getSelectionForeground()); + setBackground(table.getSelectionBackground()); + } + else { + setForeground(Color.black); + setBackground(Color.white); + if (row % 2 == 0) { + //setBackground( new Color( 156, 207, 255 ) ); + } + } + + if (isBordered) { + if (isSelected) { + if (selectedBorder == null) { + selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5, + table.getSelectionBackground()); + } + setBorder(selectedBorder); + } + else { + if (unselectedBorder == null) { + unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5, + table.getBackground()); + } + setBorder(unselectedBorder); + } + } + return this; + } +} + diff --git a/src/java/org/jivesoftware/sparkimpl/plugin/viewer/PluginViewer.java b/src/java/org/jivesoftware/sparkimpl/plugin/viewer/PluginViewer.java new file mode 100644 index 00000000..5a1cf744 --- /dev/null +++ b/src/java/org/jivesoftware/sparkimpl/plugin/viewer/PluginViewer.java @@ -0,0 +1,545 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.sparkimpl.plugin.viewer; + +import org.apache.commons.httpclient.HttpClient; +import org.apache.commons.httpclient.methods.GetMethod; +import org.apache.commons.httpclient.protocol.Protocol; +import org.dom4j.Document; +import org.dom4j.DocumentException; +import org.dom4j.Element; +import org.dom4j.Node; +import org.dom4j.io.SAXReader; +import org.jivesoftware.resource.SparkRes; +import org.jivesoftware.spark.PluginManager; +import org.jivesoftware.spark.SparkManager; +import org.jivesoftware.spark.component.MessageDialog; +import org.jivesoftware.spark.component.VerticalFlowLayout; +import org.jivesoftware.spark.plugin.Plugin; +import org.jivesoftware.spark.plugin.PublicPlugin; +import org.jivesoftware.spark.util.GraphicUtils; +import org.jivesoftware.spark.util.ModelUtil; +import org.jivesoftware.spark.util.SwingWorker; +import org.jivesoftware.spark.util.URLFileSystem; +import org.jivesoftware.spark.util.log.Log; +import org.jivesoftware.sparkimpl.updater.EasySSLProtocolSocketFactory; + +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.JButton; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JMenu; +import javax.swing.JMenuBar; +import javax.swing.JMenuItem; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JProgressBar; +import javax.swing.JScrollPane; +import javax.swing.JTabbedPane; +import javax.swing.JToolBar; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +import java.awt.Color; +import java.awt.Component; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; + +public class PluginViewer extends JPanel implements Plugin { + + private JToolBar toolbar = new JToolBar(); + private JButton installButton = new JButton(); + private JButton uninstallButton = new JButton(); + + + private JTabbedPane tabbedPane = new JTabbedPane(); + + private boolean loaded = false; + + private String retrieveListURL = "http://www.jivesoftware.org/updater/plugins.jsp"; + + private JProgressBar progressBar; + private PluginViewer viewer; + + private final JPanel installedPanel = new JPanel(); + private final JPanel availablePanel = new JPanel(); + + public PluginViewer() { + setLayout(new GridBagLayout()); + + installedPanel.setLayout(new VerticalFlowLayout(VerticalFlowLayout.TOP, 0, 0, true, false)); + installedPanel.setBackground(Color.white); + + availablePanel.setLayout(new VerticalFlowLayout(VerticalFlowLayout.TOP, 0, 0, true, false)); + availablePanel.setBackground(Color.white); + + // Add TabbedPane + add(tabbedPane, new GridBagConstraints(0, 1, 2, 1, 1.0, 1.0, GridBagConstraints.WEST, GridBagConstraints.BOTH, new Insets(5, 5, 5, 5), 0, 0)); + + // Add Tabs + tabbedPane.addTab("Installed", new JScrollPane(installedPanel)); + tabbedPane.addTab("Available", new JScrollPane(availablePanel)); + + + loadInstalledPlugins(); + + tabbedPane.addChangeListener(new ChangeListener() { + public void stateChanged(ChangeEvent changeEvent) { + if (tabbedPane.getSelectedIndex() == 1) { + if (true) { + loadAvailablePlugins(); + loaded = true; + } + } + } + }); + } + + private void loadInstalledPlugins() { + PluginManager pluginManager = PluginManager.getInstance(); + List plugins = pluginManager.getPublicPlugins(); + Iterator iter = plugins.iterator(); + while (iter.hasNext()) { + PublicPlugin plugin = (PublicPlugin)iter.next(); + final SparkPlugUI ui = new SparkPlugUI(plugin); + ui.useLocalIcon(); + installedPanel.add(ui); + addSparkPlugUIListener(ui); + } + } + + + public void initialize() { + // Add Plugins Menu + JMenuBar menuBar = SparkManager.getMainWindow().getJMenuBar(); + int count = menuBar.getMenuCount(); + + // Get last menu which is help + JMenu sparkMenu = menuBar.getMenu(0); + + JMenuItem viewPluginsMenu = new JMenuItem(); + + Action viewAction = new AbstractAction() { + public void actionPerformed(ActionEvent e) { + invokeViewer(); + } + }; + + viewAction.putValue(Action.NAME, "Plugins"); + viewAction.putValue(Action.SMALL_ICON, SparkRes.getImageIcon(SparkRes.PLUGIN_IMAGE)); + viewPluginsMenu.setAction(viewAction); + + sparkMenu.insert(viewPluginsMenu, 2); + } + + private boolean uninstall(PublicPlugin plugin) { + int ok = JOptionPane.showConfirmDialog(installedPanel, "Are you sure you want to uninstall \"" + plugin.getName() + "\"?", "Confirmation", JOptionPane.YES_NO_OPTION); + if (ok == JOptionPane.YES_OPTION) { + // Delete main jar. + File pluginDir = plugin.getPluginDir(); + File pluginJAR = new File(plugin.getPluginDir().getParentFile(), pluginDir.getName() + ".jar"); + boolean deleted = pluginJAR.delete(); + + JOptionPane.showMessageDialog(this, "You will need to restart Spark to have the changes take place.", "Reminder", JOptionPane.INFORMATION_MESSAGE); + PluginManager.getInstance().removePlugin(plugin); + return true; + } + + return false; + } + + private void invokeViewer() { + viewer = new PluginViewer(); + MessageDialog.showComponent("Plugins", "Plugins available and installed.", null, viewer, SparkManager.getMainWindow(), 600, 600, false); + } + + public void shutdown() { + + } + + public boolean canShutDown() { + return false; + } + + private void loadAvailablePlugins() { + availablePanel.removeAll(); + availablePanel.invalidate(); + availablePanel.validate(); + availablePanel.repaint(); + + JLabel label = new JLabel("Loading. Please wait..."); + availablePanel.add(label); + + + SwingWorker worker = new SwingWorker() { + Collection pluginList = null; + + public Object construct() { + // Prepare HTTP post + final GetMethod post = new GetMethod(retrieveListURL); + + // Get HTTP client + Protocol.registerProtocol("https", new Protocol("https", new EasySSLProtocolSocketFactory(), 443)); + final HttpClient httpclient = new HttpClient(); + String proxyHost = System.getProperty("http.proxyHost"); + String proxyPort = System.getProperty("http.proxyPort"); + if (ModelUtil.hasLength(proxyHost) && ModelUtil.hasLength(proxyPort)) { + try { + httpclient.getHostConfiguration().setProxy(proxyHost, Integer.parseInt(proxyPort)); + } + catch (NumberFormatException e) { + Log.error(e); + } + } + + // Execute request + + try { + int result = httpclient.executeMethod(post); + if (result != 200) { + return null; + } + + pluginList = getPluginList(post.getResponseBodyAsStream()); + } + catch (Exception ex) { + } + return "ok"; + } + + public void finished() { + if (pluginList == null) { + availablePanel.removeAll(); + availablePanel.invalidate(); + availablePanel.validate(); + availablePanel.repaint(); + + JOptionPane.showMessageDialog(availablePanel, "Unable to contact the plugin repository.", "Error", JOptionPane.ERROR_MESSAGE); + return; + } + Iterator plugs = pluginList.iterator(); + availablePanel.removeAll(); + + while (plugs.hasNext()) { + PublicPlugin plugin = (PublicPlugin)plugs.next(); + if (!PluginManager.isInstalled(plugin)) { + SparkPlugUI ui = new SparkPlugUI(plugin); + availablePanel.add(ui); + addSparkPlugUIListener(ui); + } + } + + availablePanel.invalidate(); + availablePanel.validate(); + availablePanel.repaint(); + } + }; + + worker.start(); + } + + private void downloadPlugin(final PublicPlugin plugin) { + // Prepare HTTP post + final GetMethod post = new GetMethod(plugin.getDownloadURL()); + + // Get HTTP client + Protocol.registerProtocol("https", new Protocol("https", new EasySSLProtocolSocketFactory(), 443)); + final HttpClient httpclient = new HttpClient(); + String proxyHost = System.getProperty("http.proxyHost"); + String proxyPort = System.getProperty("http.proxyPort"); + if (ModelUtil.hasLength(proxyHost) && ModelUtil.hasLength(proxyPort)) { + try { + httpclient.getHostConfiguration().setProxy(proxyHost, Integer.parseInt(proxyPort)); + } + catch (NumberFormatException e) { + Log.error(e); + } + } + // Execute request + + + try { + int result = httpclient.executeMethod(post); + if (result != 200) { + return; + } + + long length = post.getResponseContentLength(); + int contentLength = (int)length; + + progressBar = new JProgressBar(0, contentLength); + + final JFrame frame = new JFrame("Downloading " + plugin.getName()); + + frame.setIconImage(SparkRes.getImageIcon(SparkRes.SMALL_MESSAGE_IMAGE).getImage()); + + final Thread thread = new Thread(new Runnable() { + public void run() { + try { + Thread.sleep(2000); + InputStream stream = post.getResponseBodyAsStream(); + + URL url = new URL(plugin.getDownloadURL()); + String name = URLFileSystem.getFileName(url); + String directoryName = URLFileSystem.getName(url); + + File pluginDownload = new File(PluginManager.PLUGINS_DIRECTORY, name); + + FileOutputStream out = new FileOutputStream(pluginDownload); + copy(stream, out); + out.close(); + + frame.dispose(); + + // Remove SparkPlugUI + // Clear all selections + Component[] comps = availablePanel.getComponents(); + for (int i = 0; i < comps.length; i++) { + Component comp = comps[i]; + if (comp instanceof SparkPlugUI) { + SparkPlugUI sparkPlug = (SparkPlugUI)comp; + if (sparkPlug.getPlugin().getDownloadURL().equals(plugin.getDownloadURL())) { + availablePanel.remove(sparkPlug); + + PluginManager.getInstance().addPlugin(sparkPlug.getPlugin()); + + sparkPlug.showOperationButton(); + installedPanel.add(sparkPlug); + sparkPlug.getPlugin().setPluginDir(new File(PluginManager.PLUGINS_DIRECTORY, directoryName)); + installedPanel.invalidate(); + installedPanel.repaint(); + availablePanel.invalidate(); + availablePanel.invalidate(); + availablePanel.validate(); + availablePanel.repaint(); + } + } + } + } + catch (Exception ex) { + + } + finally { + // Release current connection to the connection pool once you are done + post.releaseConnection(); + } + } + }); + + + frame.getContentPane().setLayout(new GridBagLayout()); + frame.getContentPane().add(new JLabel("Downloading Spark-Plug"), new GridBagConstraints(0, 0, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); + frame.getContentPane().add(progressBar, new GridBagConstraints(0, 1, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); + frame.pack(); + frame.setSize(400, 100); + GraphicUtils.centerWindowOnComponent(frame, this); + + + frame.setVisible(true); + thread.start(); + + } + catch (IOException e) { + Log.error(e); + } + } + + + public Collection getPluginList(InputStream response) { + final List pluginList = new ArrayList(); + SAXReader saxReader = new SAXReader(); + Document pluginXML = null; + + try { + pluginXML = saxReader.read(response); + } + catch (DocumentException e) { + Log.error(e); + } + + List plugins = pluginXML.selectNodes("/plugins/plugin"); + + Iterator iter = plugins.iterator(); + while (iter.hasNext()) { + PublicPlugin publicPlugin = new PublicPlugin(); + + String clazz = null; + String name = null; + try { + Element plugin = (Element)iter.next(); + + + name = plugin.selectSingleNode("name").getText(); + clazz = plugin.selectSingleNode("class").getText(); + publicPlugin.setPluginClass(clazz); + publicPlugin.setName(name); + + try { + String version = plugin.selectSingleNode("version").getText(); + publicPlugin.setVersion(version); + + String author = plugin.selectSingleNode("author").getText(); + publicPlugin.setAuthor(author); + + + Node emailNode = plugin.selectSingleNode("email"); + if (emailNode != null) { + publicPlugin.setEmail(emailNode.getText()); + } + + Node descriptionNode = plugin.selectSingleNode("description"); + if (descriptionNode != null) { + publicPlugin.setDescription(descriptionNode.getText()); + } + + Node homePageNode = plugin.selectSingleNode("homePage"); + if (homePageNode != null) { + publicPlugin.setHomePage(homePageNode.getText()); + } + + Node downloadNode = plugin.selectSingleNode("downloadURL"); + if (downloadNode != null) { + String downloadURL = downloadNode.getText(); + publicPlugin.setDownloadURL(downloadURL); + } + + Node changeLog = plugin.selectSingleNode("changeLog"); + if (changeLog != null) { + publicPlugin.setChangeLogAvailable(true); + } + + Node readMe = plugin.selectSingleNode("readme"); + if (readMe != null) { + publicPlugin.setReadMeAvailable(true); + } + + Node smallIcon = plugin.selectSingleNode("smallIcon"); + if (smallIcon != null) { + publicPlugin.setSmallIconAvailable(true); + } + + Node largeIcon = plugin.selectSingleNode("largeIcon"); + if (largeIcon != null) { + publicPlugin.setLargeIconAvailable(true); + } + + } + catch (Exception e) { + System.out.println("We can ignore these."); + } + pluginList.add(publicPlugin); + } + catch (Exception ex) { + ex.printStackTrace(); + } + + + } + return pluginList; + } + + /** + * Common code for copy routines. By convention, the streams are + * closed in the same method in which they were opened. Thus, + * this method does not close the streams when the copying is done. + */ + private void copy(final InputStream in, final OutputStream out) { + int read = 0; + while (true) { + try { + try { + Thread.sleep(10); + } + catch (InterruptedException e) { + Log.error(e); + } + final byte[] buffer = new byte[4096]; + + int bytesRead = in.read(buffer); + if (bytesRead < 0) { + break; + } + out.write(buffer, 0, bytesRead); + read += bytesRead; + progressBar.setValue(read); + } + catch (IOException e) { + Log.error(e); + } + } + } + + + private void addSparkPlugUIListener(final SparkPlugUI ui) { + ui.addMouseListener(new MouseAdapter() { + public void mouseClicked(MouseEvent mouseEvent) { + // Clear all selections + Component[] comps = installedPanel.getComponents(); + for (int i = 0; i < comps.length; i++) { + Component comp = (Component)comps[i]; + if (comp instanceof SparkPlugUI) { + SparkPlugUI sparkPlug = (SparkPlugUI)comp; + sparkPlug.setSelected(false); + } + } + + // Clear all selections + comps = availablePanel.getComponents(); + for (int i = 0; i < comps.length; i++) { + Component comp = comps[i]; + if (comp instanceof SparkPlugUI) { + SparkPlugUI sparkPlug = (SparkPlugUI)comp; + sparkPlug.setSelected(false); + } + } + + ui.setSelected(true); + ui.getInstallButton().addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent actionEvent) { + boolean isInstalled = PluginManager.isInstalled(ui.getPlugin()); + if (isInstalled) { + boolean uninstalled = uninstall(ui.getPlugin()); + if (uninstalled) { + installedPanel.remove(ui); + installedPanel.invalidate(); + installedPanel.repaint(); + return; + } + } + else { + downloadPlugin(ui.getPlugin()); + } + } + }); + } + }); + } + + public void uninstall() { + // Do nothing. + } +} diff --git a/src/java/org/jivesoftware/sparkimpl/plugin/viewer/SparkPlugUI.java b/src/java/org/jivesoftware/sparkimpl/plugin/viewer/SparkPlugUI.java new file mode 100644 index 00000000..ecccf82f --- /dev/null +++ b/src/java/org/jivesoftware/sparkimpl/plugin/viewer/SparkPlugUI.java @@ -0,0 +1,207 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.sparkimpl.plugin.viewer; + +import org.jivesoftware.resource.SparkRes; +import org.jivesoftware.spark.PluginManager; +import org.jivesoftware.spark.component.RolloverButton; +import org.jivesoftware.spark.plugin.PublicPlugin; +import org.jivesoftware.spark.util.BrowserLauncher; +import org.jivesoftware.spark.util.URLFileSystem; +import org.jivesoftware.spark.util.log.Log; + +import javax.imageio.ImageIO; +import javax.swing.BorderFactory; +import javax.swing.Icon; +import javax.swing.ImageIcon; +import javax.swing.JButton; +import javax.swing.JLabel; +import javax.swing.JPanel; + +import java.awt.Color; +import java.awt.FlowLayout; +import java.awt.Font; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Image; +import java.awt.Insets; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.File; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; + +public class SparkPlugUI extends JPanel { + private PublicPlugin plugin; + private final JButton installButton = new JButton(); + private JLabel imageIcon = new JLabel(); + + + public SparkPlugUI(PublicPlugin plugin) { + this.plugin = plugin; + + setLayout(new GridBagLayout()); + setBackground(Color.white); + + JLabel titleLabel = new JLabel(); + JLabel versionLabel = new JLabel(); + JLabel descriptionLabel = new JLabel(); + + + if (getFilename() != null) { + try { + URL url = new URL("http://www.jivesoftware.org/updater/sparkplugs?filename=" + getFilename()); + Image image = ImageIO.read(url); + ImageIcon icon = new ImageIcon(image); + imageIcon.setIcon(icon); + if (icon.getIconWidth() == -1) { + imageIcon.setIcon(SparkRes.getImageIcon(SparkRes.PLUGIN_IMAGE)); + } + } + catch (Exception e) { + Log.error(e); + } + } + else { + imageIcon.setIcon(SparkRes.getImageIcon(SparkRes.PLUGIN_IMAGE)); + } + + add(imageIcon, new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + + add(titleLabel, new GridBagConstraints(1, 0, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 0), 0, 0)); + titleLabel.setFont(new Font("dialog", Font.BOLD, 11)); + titleLabel.setForeground(new Color(80, 93, 198)); + + add(versionLabel, new GridBagConstraints(2, 0, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); + + titleLabel.setText(plugin.getName()); + versionLabel.setText(plugin.getVersion() + " by " + plugin.getAuthor()); + descriptionLabel.setText(plugin.getDescription()); + + + add(installButton, new GridBagConstraints(4, 0, 1, 2, 1.0, 0.0, GridBagConstraints.EAST, GridBagConstraints.NONE, new Insets(0, 5, 5, 5), 0, 0)); + + + if (plugin.isChangeLogAvailable() && plugin.isReadMeAvailable()) { + RolloverButton changeLogButton = new RolloverButton(SparkRes.getImageIcon(SparkRes.CHANGELOG_IMAGE)); + RolloverButton readMeButton = new RolloverButton(SparkRes.getImageIcon(SparkRes.README_IMAGE)); + + + changeLogButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + try { + BrowserLauncher.openURL("http://www.jivesoftware.org/updater/retrieve.jsp?filename=" + getFilename() + "&changeLog=true"); + } + catch (IOException e1) { + Log.error(e1); + } + } + }); + + readMeButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + try { + BrowserLauncher.openURL("http://www.jivesoftware.org/updater/retrieve.jsp?filename=" + getFilename() + "&readme=true"); + } + catch (IOException e1) { + Log.error(e1); + } + } + }); + + + final JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); + buttonPanel.setOpaque(false); + buttonPanel.add(changeLogButton); + buttonPanel.add(readMeButton); + + changeLogButton.setToolTipText("View Change Log"); + readMeButton.setToolTipText("View Readme"); + add(descriptionLabel, new GridBagConstraints(1, 1, 2, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(0, 5, 5, 5), 0, 0)); + add(buttonPanel, new GridBagConstraints(3, 1, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0)); + } + else { + add(descriptionLabel, new GridBagConstraints(1, 1, 2, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(0, 5, 5, 5), 0, 0)); + } + + installButton.setVisible(false); + } + + public void showOperationButton() { + if (!PluginManager.isInstalled(plugin)) { + installButton.setIcon(SparkRes.getImageIcon(SparkRes.SMALL_ADD_IMAGE)); + } + else { + installButton.setIcon(SparkRes.getImageIcon(SparkRes.SMALL_DELETE)); + } + installButton.setVisible(true); + } + + public void setSelected(boolean isSelected) { + if (isSelected) { + setBackground(new Color(234, 230, 212)); + showOperationButton(); + setBorder(BorderFactory.createEtchedBorder()); + } + else { + setBackground(Color.white); + installButton.setVisible(false); + setBorder(null); + } + } + + public void updateState() { + showOperationButton(); + } + + + public PublicPlugin getPlugin() { + return plugin; + } + + public JButton getInstallButton() { + return installButton; + } + + public void useLocalIcon() { + File pluginDIR = plugin.getPluginDir(); + try { + File smallIcon = new File(pluginDIR, "logo_small.gif"); + File largeIcon = new File(pluginDIR, "logo_large.gif"); + if (largeIcon.exists()) { + setIcon(new ImageIcon(largeIcon.toURL())); + } + else if (smallIcon.exists()) { + setIcon(new ImageIcon(smallIcon.toURL())); + } + } + catch (MalformedURLException e) { + Log.error(e); + } + + } + + public String getFilename() { + String filename = null; + try { + URL downloadURL = new URL(plugin.getDownloadURL()); + filename = URLFileSystem.getFileName(downloadURL); + } + catch (MalformedURLException e) { + } + return filename; + } + + public void setIcon(Icon icon) { + imageIcon.setIcon(icon); + } +} diff --git a/src/java/org/jivesoftware/sparkimpl/preference/PreferenceDialog.java b/src/java/org/jivesoftware/sparkimpl/preference/PreferenceDialog.java new file mode 100644 index 00000000..7a1e50ac --- /dev/null +++ b/src/java/org/jivesoftware/sparkimpl/preference/PreferenceDialog.java @@ -0,0 +1,77 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.sparkimpl.preference; + +import org.jivesoftware.spark.SparkManager; + +import javax.swing.JDialog; +import javax.swing.JFrame; +import javax.swing.JOptionPane; +import javax.swing.JPanel; + +import java.awt.BorderLayout; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; + +public class PreferenceDialog implements PropertyChangeListener { + private JDialog preferenceDialog; + private JOptionPane pane = null; + private PreferencesPanel prefPanel; + + public void invoke(JFrame parentFrame, PreferencesPanel contentPane) { + + this.prefPanel = contentPane; + + // Construct main panel w/ layout. + final JPanel mainPanel = new JPanel(); + mainPanel.setLayout(new BorderLayout()); + + // Construct Dialog + preferenceDialog = new JDialog(parentFrame, + "Preferences", + true); + + Object[] options = {"Close"}; + pane = new JOptionPane(contentPane, JOptionPane.PLAIN_MESSAGE, + JOptionPane.OK_CANCEL_OPTION, null, options, options[0]); + mainPanel.add(pane, BorderLayout.CENTER); + preferenceDialog.pack(); + preferenceDialog.setSize(600, 500); + preferenceDialog.setContentPane(mainPanel); + preferenceDialog.setLocationRelativeTo(SparkManager.getMainWindow()); + + pane.addPropertyChangeListener(this); + + preferenceDialog.setVisible(true); + } + + public void propertyChange(PropertyChangeEvent e) { + if (pane.getValue() instanceof Integer) { + pane.removePropertyChangeListener(this); + preferenceDialog.dispose(); + return; + } + String value = (String)pane.getValue(); + if (value.equals("Close")) { + boolean okToClose = prefPanel.closing(); + if (okToClose) { + preferenceDialog.setVisible(false); + } + else { + pane.setValue(JOptionPane.UNINITIALIZED_VALUE); + } + } + } + + public JDialog getDialog() { + return preferenceDialog; + } +} \ No newline at end of file diff --git a/src/java/org/jivesoftware/sparkimpl/preference/PreferenceUI.java b/src/java/org/jivesoftware/sparkimpl/preference/PreferenceUI.java new file mode 100644 index 00000000..a9ce8425 --- /dev/null +++ b/src/java/org/jivesoftware/sparkimpl/preference/PreferenceUI.java @@ -0,0 +1,53 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.sparkimpl.preference; + +import org.jivesoftware.spark.preference.Preference; +import org.jivesoftware.spark.util.GraphicUtils; + +import javax.swing.JButton; +import javax.swing.JLabel; + +import java.awt.event.MouseEvent; + +public class PreferenceUI extends JLabel { + private Preference preference; + + public PreferenceUI(Preference preference) { + this.preference = preference; + this.setIcon(preference.getIcon()); + this.setText(preference.getListName()); + + // Set tooltip + this.setToolTipText(GraphicUtils.createToolTip(preference.getTooltip())); + } + + + public void mouseEntered(MouseEvent e) { + if (this.isEnabled()) { + this.invalidate(); + this.repaint(); + } + } + + + public void decorate() { + this.setOpaque(true); + + this.setVerticalTextPosition(JButton.BOTTOM); + this.setHorizontalTextPosition(JButton.CENTER); + } + + public Preference getPreference() { + return preference; + } + +} \ No newline at end of file diff --git a/src/java/org/jivesoftware/sparkimpl/preference/PreferencesPanel.java b/src/java/org/jivesoftware/sparkimpl/preference/PreferencesPanel.java new file mode 100644 index 00000000..f333ed9c --- /dev/null +++ b/src/java/org/jivesoftware/sparkimpl/preference/PreferencesPanel.java @@ -0,0 +1,119 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.sparkimpl.preference; + +import org.jivesoftware.spark.component.TitlePanel; +import org.jivesoftware.spark.component.renderer.JLabelIconRenderer; +import org.jivesoftware.spark.preference.Preference; + +import javax.swing.DefaultListModel; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JList; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JSeparator; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.Font; +import java.util.Iterator; + +public class PreferencesPanel extends JPanel implements ListSelectionListener { + private final JLabel titleLabel = new JLabel(); + private final JSeparator jSeparator1 = new JSeparator(); + private final JPanel flowPanel = new JPanel(new BorderLayout()); + private DefaultListModel listModel = new DefaultListModel(); + private JList list = new JList(listModel); + private Preference currentPreference; + + public PreferencesPanel(Iterator preferences) { + this.setLayout(new BorderLayout()); + titleLabel.setText("Spark Preferences"); + titleLabel.setFont(new Font("Verdana", Font.BOLD, 15)); + JScrollPane scrollPane = new JScrollPane(list); + scrollPane.setPreferredSize(new Dimension(125, 0)); + this.add(scrollPane, BorderLayout.WEST); + list.setFixedCellHeight(70); + + this.add(flowPanel, BorderLayout.CENTER); + + + list.setCellRenderer(new JLabelIconRenderer()); + list.addListSelectionListener(this); + // Populate with current preferences + while (preferences.hasNext()) { + Preference preference = (Preference)preferences.next(); + listModel.addElement(new PreferenceUI(preference)); + } + + list.setSelectedIndex(0); + } + + public void valueChanged(ListSelectionEvent e) { + + if (!e.getValueIsAdjusting()) { + + if (currentPreference != null) { + if (currentPreference.isDataValid()) { + currentPreference.commit(); + } + else { + JOptionPane.showMessageDialog(this, currentPreference.getErrorMessage(), + "Preference Error", JOptionPane.ERROR_MESSAGE); + list.removeListSelectionListener(this); + list.setSelectedIndex(e.getLastIndex()); + list.addListSelectionListener(this); + } + + } + + PreferenceUI o = (PreferenceUI)list.getSelectedValue(); + Preference pref = o.getPreference(); + pref.load(); + + JComponent comp = pref.getGUI(); + flowPanel.removeAll(); + + // Create the title panel for this dialog + TitlePanel titlePanel = new TitlePanel(pref.getTitle(), + pref.getTooltip(), + pref.getIcon(), + false); + + + flowPanel.add(comp, BorderLayout.CENTER); + flowPanel.add(titlePanel, BorderLayout.NORTH); + flowPanel.invalidate(); + flowPanel.validate(); + flowPanel.repaint(); + currentPreference = pref; + } + } + + public boolean closing() { + if (currentPreference != null) { + if (currentPreference.isDataValid()) { + currentPreference.commit(); + return true; + } + else { + JOptionPane.showMessageDialog(this, currentPreference.getErrorMessage(), + "Preference Error", JOptionPane.ERROR_MESSAGE); + return false; + } + } + return true; + } +} \ No newline at end of file diff --git a/src/java/org/jivesoftware/sparkimpl/preference/chat/ChatPreference.java b/src/java/org/jivesoftware/sparkimpl/preference/chat/ChatPreference.java new file mode 100644 index 00000000..425c039a --- /dev/null +++ b/src/java/org/jivesoftware/sparkimpl/preference/chat/ChatPreference.java @@ -0,0 +1,158 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.sparkimpl.preference.chat; + +import org.jivesoftware.resource.SparkRes; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.spark.SparkManager; +import org.jivesoftware.spark.preference.Preference; +import org.jivesoftware.spark.util.ModelUtil; +import org.jivesoftware.spark.util.SwingWorker; +import org.jivesoftware.spark.util.log.Log; +import org.jivesoftware.sparkimpl.settings.local.LocalPreferences; +import org.jivesoftware.sparkimpl.settings.local.SettingsManager; + +import javax.swing.Icon; +import javax.swing.JComponent; +import javax.swing.JOptionPane; + +/** + * Handles the preferences for Chatting. This handles preferences used in chatting such as the nickname + * to be used and showing dates and times of chat posts. + */ +public class ChatPreference implements Preference { + private ChatPreferencePanel panel = new ChatPreferencePanel(); + private ChatPreferences preferences; + private String errorMessage = "Error"; + + /** + * Define the Namespace used for this preference. + */ + public static final String NAMESPACE = "http://www.jivesoftware.org/spark/chatwindow"; + + /** + * Initialize ChatPreference. + */ + public ChatPreference() { + preferences = new ChatPreferences(); + } + + public String getTitle() { + return "General Chat Settings"; + } + + public String getListName() { + return "Chat"; + } + + public String getTooltip() { + return "General Chat Settings"; + } + + public Icon getIcon() { + return SparkRes.getImageIcon(SparkRes.USER1_MESSAGE_24x24); + } + + public void load() { + SwingWorker thread = new SwingWorker() { + LocalPreferences pref; + + public Object construct() { + pref = SettingsManager.getLocalPreferences(); + return pref; + } + + public void finished() { + String nickname = pref.getDefaultNickname(); + if (nickname == null) { + nickname = SparkManager.getSessionManager().getUsername(); + } + + boolean showTime = pref.isTimeDisplayedInChat(); + boolean spellCheckerOn = !pref.isSpellCheckerDisable(); + boolean notificationsOn = !pref.isChatRoomNotificationsOff(); + boolean chatHistoryHidden = pref.isHideChatHistory(); + panel.setShowTime(showTime); + panel.setSpellCheckerOn(spellCheckerOn); + panel.setGroupChatNotificationsOn(notificationsOn); + panel.setChatHistoryHidden(chatHistoryHidden); + } + }; + + thread.start(); + + } + + public void commit() { + LocalPreferences pref = SettingsManager.getLocalPreferences(); + pref.setTimeDisplayedInChat(panel.getShowTime()); + pref.setSpellCheckerDisable(!panel.isSpellCheckerOn()); + pref.setChatRoomNotificationsOff(!panel.isGroupChatNotificationsOn()); + pref.setHideChatHistory(panel.isChatHistoryHidden()); + + SettingsManager.saveSettings(); + + // Do not commit if not changed. + if (ModelUtil.hasLength(panel.getPassword()) && ModelUtil.hasLength(panel.getConfirmationPassword())) { + try { + SparkManager.getConnection().getAccountManager().changePassword(panel.getPassword()); + } + catch (XMPPException passwordEx) { + JOptionPane.showMessageDialog(SparkManager.getMainWindow(), "Unable to change password. Please see your server admin.", + "Password Change Error", JOptionPane.ERROR_MESSAGE); + Log.error("Unable to change password", passwordEx); + } + } + } + + public Object getData() { + LocalPreferences pref = SettingsManager.getLocalPreferences(); + String nickname = pref.getDefaultNickname(); + if (nickname == null) { + nickname = SparkManager.getSessionManager().getUsername(); + } + + boolean showTime = pref.isTimeDisplayedInChat(); + + preferences.showDatesInChat(showTime); + return preferences; + } + + public String getErrorMessage() { + return errorMessage; + } + + + public boolean isDataValid() { + boolean dataIsValid = true; + if (ModelUtil.hasLength(panel.getPassword()) && ModelUtil.hasLength(panel.getConfirmationPassword())) { + if (!panel.getPassword().equals(panel.getConfirmationPassword())) { + errorMessage = "The passwords do not match. Please re-enter your password"; + dataIsValid = false; + } + } + return dataIsValid; + } + + public JComponent getGUI() { + return panel; + } + + public String getNamespace() { + return NAMESPACE; + } + + public void shutdown() { + commit(); + } + + +} \ No newline at end of file diff --git a/src/java/org/jivesoftware/sparkimpl/preference/chat/ChatPreferenceDialog.java b/src/java/org/jivesoftware/sparkimpl/preference/chat/ChatPreferenceDialog.java new file mode 100644 index 00000000..b1a6658d --- /dev/null +++ b/src/java/org/jivesoftware/sparkimpl/preference/chat/ChatPreferenceDialog.java @@ -0,0 +1,113 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.sparkimpl.preference.chat; + + +import org.jivesoftware.resource.SparkRes; +import org.jivesoftware.spark.SparkManager; +import org.jivesoftware.spark.component.TitlePanel; + +import javax.swing.JDialog; +import javax.swing.JOptionPane; +import javax.swing.JPanel; + +import java.awt.BorderLayout; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; + +/** + * The Dialog UI to handle changing of ChatPreferences. + */ +public class ChatPreferenceDialog implements PropertyChangeListener { + private ChatPreferencePanel chatPreferencePanel; + private ChatPreferences chatPreferences; + + private JOptionPane optionPane; + private JDialog preferenceDialog; + + private TitlePanel titlePanel; + + /** + * Empty Constructor + */ + public ChatPreferenceDialog() { + } + + /** + * Invoke the ChatPreference Dialog + * + * @param preferences the preferences to use with this dialog. + */ + public void showDialog(ChatPreferences preferences) { + chatPreferencePanel = new ChatPreferencePanel(); + + if (preferences != null) { + chatPreferences = preferences; + } + else { + chatPreferences = new ChatPreferences(); + chatPreferences.showDatesInChat(true); + } + + // Set default values + chatPreferencePanel.setShowTime(chatPreferences.showDatesInChat()); + + // Create the title chatPreferencePanel for this dialog + titlePanel = new TitlePanel("Chat Window Preferences", + "Preferences used by the Chat Window", + SparkRes.getImageIcon(SparkRes.BLANK_24x24), false); + + // Construct main chatPreferencePanel w/ layout. + final JPanel mainPanel = new JPanel(); + mainPanel.setLayout(new BorderLayout()); + mainPanel.add(titlePanel, BorderLayout.NORTH); + + // The user should only be able to close this dialog. + Object[] options = {"Ok", "Cancel"}; + optionPane = new JOptionPane(chatPreferencePanel, JOptionPane.PLAIN_MESSAGE, + JOptionPane.OK_CANCEL_OPTION, null, options, options[0]); + + mainPanel.add(optionPane, BorderLayout.CENTER); + + preferenceDialog = new JDialog(SparkManager.getMainWindow(), "Chat Window", true); + preferenceDialog.pack(); + preferenceDialog.setSize(400, 300); + preferenceDialog.setContentPane(mainPanel); + preferenceDialog.setLocationRelativeTo(SparkManager.getMainWindow()); + optionPane.addPropertyChangeListener(this); + + preferenceDialog.setVisible(true); + preferenceDialog.toFront(); + preferenceDialog.requestFocus(); + } + + public void propertyChange(PropertyChangeEvent e) { + String value = (String)optionPane.getValue(); + if ("Cancel".equals(value)) { + preferenceDialog.setVisible(false); + } + else if ("Ok".equals(value)) { + chatPreferences.showDatesInChat(chatPreferencePanel.getShowTime()); + preferenceDialog.setVisible(false); + } + } + + /** + * Return the current modified preferences. + * + * @return the current modified preferences. + */ + protected ChatPreferences getPreferences() { + return chatPreferences; + } + + +} \ No newline at end of file diff --git a/src/java/org/jivesoftware/sparkimpl/preference/chat/ChatPreferencePanel.java b/src/java/org/jivesoftware/sparkimpl/preference/chat/ChatPreferencePanel.java new file mode 100644 index 00000000..c08d247b --- /dev/null +++ b/src/java/org/jivesoftware/sparkimpl/preference/chat/ChatPreferencePanel.java @@ -0,0 +1,169 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.sparkimpl.preference.chat; + +import org.jivesoftware.spark.SparkManager; +import org.jivesoftware.spark.component.VerticalFlowLayout; +import org.jivesoftware.spark.util.ResourceUtils; + +import javax.swing.BorderFactory; +import javax.swing.JCheckBox; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JPasswordField; + +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.File; + +/** + * The Preference UI used to handle changing of Chat Preferences. + */ +public class ChatPreferencePanel extends JPanel implements ActionListener { + + private JCheckBox showTimeBox = new JCheckBox(); + private JCheckBox spellCheckBox = new JCheckBox(); + private JCheckBox groupChatNotificationBox = new JCheckBox(); + private JPanel generalPanel = new JPanel(); + private JPanel chatWindowPanel = new JPanel(); + + // Password changing + private JPasswordField passwordField = new JPasswordField(); + private JPasswordField confirmationPasswordField = new JPasswordField(); + private JLabel passwordLabel = new JLabel(); + private JLabel confirmationPasswordLabel = new JLabel(); + private JCheckBox hideChatHistory = new JCheckBox(); + + + /** + * Constructor invokes UI setup. + */ + public ChatPreferencePanel() { + // Build the UI + createUI(); + } + + private void createUI() { + setLayout(new VerticalFlowLayout()); + + // Setup Mnemonics + ResourceUtils.resButton(showTimeBox, "&Show time in chat window"); + ResourceUtils.resLabel(passwordLabel, passwordField, "&Change Password To:"); + ResourceUtils.resLabel(confirmationPasswordLabel, confirmationPasswordField, "Confirm &Password:"); + ResourceUtils.resButton(spellCheckBox, "&Perform spell checking in background"); + ResourceUtils.resButton(groupChatNotificationBox, "&Show notifications in conference rooms"); + ResourceUtils.resButton(hideChatHistory, "&Disable Chat History"); + + generalPanel.setBorder(BorderFactory.createTitledBorder("General Information")); + chatWindowPanel.setBorder(BorderFactory.createTitledBorder("Chat Window Information")); + + add(generalPanel); + add(chatWindowPanel); + + generalPanel.setLayout(new GridBagLayout()); + chatWindowPanel.setLayout(new GridBagLayout()); + + // Chat Window Panel settings + chatWindowPanel.add(showTimeBox, new GridBagConstraints(0, 0, 2, 1, 1.0, 1.0, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + chatWindowPanel.add(spellCheckBox, new GridBagConstraints(0, 1, 2, 1, 1.0, 1.0, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + chatWindowPanel.add(groupChatNotificationBox, new GridBagConstraints(0, 2, 2, 1, 1.0, 1.0, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + chatWindowPanel.add(hideChatHistory, new GridBagConstraints(0, 3, 2, 1, 1.0, 1.0, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + + + generalPanel.add(passwordLabel, new GridBagConstraints(0, 1, 1, 1, 0.0, 0.0, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + generalPanel.add(passwordField, new GridBagConstraints(1, 1, 1, 1, 1.0, 0.0, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 100, 0)); + generalPanel.add(confirmationPasswordLabel, new GridBagConstraints(0, 2, 1, 1, 0.0, 0.0, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + generalPanel.add(confirmationPasswordField, new GridBagConstraints(1, 2, 1, 1, 1.0, 0.0, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 100, 0)); + + hideChatHistory.addActionListener(this); + } + + /** + * Set to true to have the ChatWindow show the timestamp of each message. + * + * @param showTime true to show timestamp of each message. + */ + public void setShowTime(boolean showTime) { + showTimeBox.setSelected(showTime); + } + + /** + * Returns true if the ChatWindow should show a timestamp of each message. + * + * @return true if the ChatWindow should show a timestamp of each message. + */ + public boolean getShowTime() { + return showTimeBox.isSelected(); + } + + + /** + * Returns the new password to use. + * + * @return the new password to use. + */ + public String getPassword() { + return new String(passwordField.getPassword()); + } + + /** + * Returns the confirmation password used to compare to the first password. + * + * @return the confirmation password used to compare to the first password. + */ + public String getConfirmationPassword() { + return new String(confirmationPasswordField.getPassword()); + } + + public void setSpellCheckerOn(boolean on) { + spellCheckBox.setSelected(on); + } + + public boolean isSpellCheckerOn() { + return spellCheckBox.isSelected(); + } + + public void setGroupChatNotificationsOn(boolean on) { + groupChatNotificationBox.setSelected(on); + } + + public boolean isGroupChatNotificationsOn() { + return groupChatNotificationBox.isSelected(); + } + + public void setChatHistoryHidden(boolean hide) { + hideChatHistory.setSelected(hide); + } + + public boolean isChatHistoryHidden() { + return hideChatHistory.isSelected(); + } + + public void actionPerformed(ActionEvent actionEvent) { + if (hideChatHistory.isSelected()) { + int ok = JOptionPane.showConfirmDialog(this, "Delete all previous history?", "Delete Confirmation", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE); + if (ok == JOptionPane.YES_OPTION) { + File transcriptDir = new File(SparkManager.getUserDirectory(), "transcripts"); + File[] files = transcriptDir.listFiles(); + + for (int i = 0; i < files.length; i++) { + File transcriptFile = files[i]; + transcriptFile.delete(); + } + } + } + } + +} \ No newline at end of file diff --git a/src/java/org/jivesoftware/sparkimpl/preference/chat/ChatPreferences.java b/src/java/org/jivesoftware/sparkimpl/preference/chat/ChatPreferences.java new file mode 100644 index 00000000..e6380c1a --- /dev/null +++ b/src/java/org/jivesoftware/sparkimpl/preference/chat/ChatPreferences.java @@ -0,0 +1,53 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.sparkimpl.preference.chat; + +import org.jivesoftware.spark.SparkManager; + +/** + * Model representing the Chat Preferences within Spark. + */ +public class ChatPreferences { + + /** + * Set default to not show timestamp. + */ + private boolean showDatesInChat; + + + /** + * Set to true to show timestamp of messages. + * + * @param showDatesInChat true to show timestamp of messages. + */ + public void showDatesInChat(boolean showDatesInChat) { + this.showDatesInChat = showDatesInChat; + } + + + /** + * Returns true if a timestamp should be used to show messages. + * + * @return true if the a timestamp should be used with the messages. + */ + public boolean showDatesInChat() { + return showDatesInChat; + } + + /** + * Returns the nickname used by the agent. + * + * @return the nickname used by the agent. + */ + public String getNickname() { + return SparkManager.getUserManager().getNickname(); + } +} \ No newline at end of file diff --git a/src/java/org/jivesoftware/sparkimpl/preference/sounds/SoundPlugin.java b/src/java/org/jivesoftware/sparkimpl/preference/sounds/SoundPlugin.java new file mode 100644 index 00000000..ea778f19 --- /dev/null +++ b/src/java/org/jivesoftware/sparkimpl/preference/sounds/SoundPlugin.java @@ -0,0 +1,117 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.sparkimpl.preference.sounds; + +import org.jivesoftware.smack.PacketListener; +import org.jivesoftware.smack.filter.PacketTypeFilter; +import org.jivesoftware.smack.packet.Message; +import org.jivesoftware.smack.packet.Packet; +import org.jivesoftware.smack.packet.Presence; +import org.jivesoftware.smackx.packet.DelayInformation; +import org.jivesoftware.spark.SparkManager; +import org.jivesoftware.spark.plugin.Plugin; +import org.jivesoftware.spark.ui.ChatRoom; +import org.jivesoftware.spark.ui.ChatRoomListener; +import org.jivesoftware.spark.ui.MessageListener; + +import java.io.File; + +public class SoundPlugin implements Plugin, MessageListener, ChatRoomListener { + SoundPreference soundPreference; + + public void initialize() { + soundPreference = new SoundPreference(); + SparkManager.getPreferenceManager().addPreference(soundPreference); + + SparkManager.getChatManager().addChatRoomListener(this); + + SparkManager.getConnection().addPacketListener(new PacketListener() { + public void processPacket(Packet packet) { + Presence presence = (Presence)packet; + if (presence != null && presence.getType() == Presence.Type.UNAVAILABLE) { + SoundPreferences preferences = soundPreference.getPreferences(); + if (preferences.isPlayOfflineSound()) { + String offline = preferences.getOfflineSound(); + File offlineFile = new File(offline); + SparkManager.getSoundManager().playClip(offlineFile); + } + } + } + }, new PacketTypeFilter(Presence.class)); + + Thread thread = new Thread(new Runnable() { + public void run() { + soundPreference.loadFromFile(); + } + }); + thread.start(); + + } + + public void messageReceived(ChatRoom room, Message message) { + + // Do not play sounds on history updates. + DelayInformation inf = (DelayInformation)message.getExtension("x", "jabber:x:delay"); + if (inf != null) { + return; + } + + SoundPreferences preferences = soundPreference.getPreferences(); + if (preferences.isPlayIncomingSound()) { + File incomingFile = new File(preferences.getIncomingSound()); + SparkManager.getSoundManager().playClip(incomingFile); + } + } + + public void messageSent(ChatRoom room, Message message) { + SoundPreferences preferences = soundPreference.getPreferences(); + if (preferences.isPlayOutgoingSound()) { + File outgoingFile = new File(preferences.getOutgoingSound()); + SparkManager.getSoundManager().playClip(outgoingFile); + } + } + + public void shutdown() { + + } + + public boolean canShutDown() { + return false; + } + + public void chatRoomOpened(ChatRoom room) { + room.addMessageListener(this); + } + + public void chatRoomLeft(ChatRoom room) { + + } + + public void chatRoomClosed(ChatRoom room) { + room.removeMessageListener(this); + } + + public void chatRoomActivated(ChatRoom room) { + + } + + public void userHasJoined(ChatRoom room, String userid) { + + } + + public void userHasLeft(ChatRoom room, String userid) { + + } + + public void uninstall() { + // Do nothing. + } +} diff --git a/src/java/org/jivesoftware/sparkimpl/preference/sounds/SoundPreference.java b/src/java/org/jivesoftware/sparkimpl/preference/sounds/SoundPreference.java new file mode 100644 index 00000000..01c6f301 --- /dev/null +++ b/src/java/org/jivesoftware/sparkimpl/preference/sounds/SoundPreference.java @@ -0,0 +1,311 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.sparkimpl.preference.sounds; + +import com.thoughtworks.xstream.XStream; +import org.jivesoftware.Spark; +import org.jivesoftware.resource.SparkRes; +import org.jivesoftware.spark.preference.Preference; +import org.jivesoftware.spark.util.ResourceUtils; +import org.jivesoftware.spark.util.SwingWorker; +import org.jivesoftware.spark.util.WindowsFileSystemView; +import org.jivesoftware.spark.util.log.Log; + +import javax.swing.Icon; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JComponent; +import javax.swing.JFileChooser; +import javax.swing.JPanel; +import javax.swing.JTextField; + +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; + +public class SoundPreference implements Preference { + + private XStream xstream = new XStream(); + private SoundPreferences preferences; + private SoundPanel soundPanel; + + public static String NAMESPACE = "Sounds"; + + public SoundPreference() { + xstream.alias("sounds", SoundPreferences.class); + } + + + public String getTitle() { + return "Sound Preferences"; + } + + public Icon getIcon() { + return SparkRes.getImageIcon(SparkRes.SOUND_PREFERENCES_IMAGE); + } + + public String getTooltip() { + return "Sounds"; + } + + public String getListName() { + return "Sounds"; + } + + public String getNamespace() { + return NAMESPACE; + } + + public JComponent getGUI() { + if (soundPanel == null) { + soundPanel = new SoundPanel(); + } + return soundPanel; + } + + public void loadFromFile() { + if (preferences != null) { + return; + } + + if (!getSoundSettingsFile().exists()) { + preferences = new SoundPreferences(); + } + else { + + // Do Initial Load from FileSystem. + File settingsFile = getSoundSettingsFile(); + try { + FileReader reader = new FileReader(settingsFile); + preferences = (SoundPreferences)xstream.fromXML(reader); + } + catch (Exception e) { + Log.error("Error loading Sound Preferences.", e); + preferences = new SoundPreferences(); + } + } + } + + public void load() { + if (soundPanel == null) { + soundPanel = new SoundPanel(); + } + + SwingWorker worker = new SwingWorker() { + public Object construct() { + loadFromFile(); + return preferences; + } + + public void finished() { + // Set default settings + soundPanel.setIncomingMessageSound(preferences.getIncomingSound()); + soundPanel.playIncomingSound(preferences.isPlayIncomingSound()); + + soundPanel.setOutgoingMessageSound(preferences.getOutgoingSound()); + soundPanel.playOutgoingSound(preferences.isPlayOutgoingSound()); + + soundPanel.setOfflineSound(preferences.getOfflineSound()); + soundPanel.playOfflineSound(preferences.isPlayOfflineSound()); + } + }; + worker.start(); + } + + public void commit() { + preferences.setIncomingSound(soundPanel.getIncomingSound()); + preferences.setOutgoingSound(soundPanel.getOutgoingSound()); + preferences.setOfflineSound(soundPanel.getOfflineSound()); + + preferences.setPlayIncomingSound(soundPanel.playIncomingSound()); + preferences.setPlayOutgoingSound(soundPanel.playOutgoingSound()); + preferences.setPlayOfflineSound(soundPanel.playOfflineSound()); + + saveSoundsFile(); + } + + public boolean isDataValid() { + return true; + } + + public String getErrorMessage() { + return null; + } + + public Object getData() { + return null; + } + + + private class SoundPanel extends JPanel { + final JCheckBox incomingMessageBox = new JCheckBox(); + final JTextField incomingMessageSound = new JTextField(); + final JButton incomingBrowseButton = new JButton(); + + final JCheckBox outgoingMessageBox = new JCheckBox(); + final JTextField outgoingMessageSound = new JTextField(); + final JButton outgoingBrowseButton = new JButton(); + + final JCheckBox userOfflineCheckbox = new JCheckBox(); + final JTextField userOfflineField = new JTextField(); + final JButton offlineBrowseButton = new JButton(); + final JFileChooser fc = new JFileChooser(); + + + public SoundPanel() { + setLayout(new GridBagLayout()); + + // Add ResourceUtils + ResourceUtils.resButton(incomingMessageBox, "&Play sound when new message arrives"); + ResourceUtils.resButton(outgoingMessageBox, "&Play sound when a message is sent"); + ResourceUtils.resButton(userOfflineCheckbox, "&Play sound when user goes offline"); + ResourceUtils.resButton(incomingBrowseButton, "&Browse"); + ResourceUtils.resButton(outgoingBrowseButton, "B&rowse"); + ResourceUtils.resButton(offlineBrowseButton, "Br&owse"); + + // Handle incoming sounds + add(incomingMessageBox, new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + add(incomingMessageSound, new GridBagConstraints(0, 1, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); + add(incomingBrowseButton, new GridBagConstraints(1, 1, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + + // Handle sending sounds + add(outgoingMessageBox, new GridBagConstraints(0, 2, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + add(outgoingMessageSound, new GridBagConstraints(0, 3, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); + add(outgoingBrowseButton, new GridBagConstraints(1, 3, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + + // Handle User Online Sound + add(userOfflineCheckbox, new GridBagConstraints(0, 4, 1, 1, 0.0, 0.0, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + add(userOfflineField, new GridBagConstraints(0, 5, 1, 1, 1.0, 0.0, GridBagConstraints.NORTHWEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); + add(offlineBrowseButton, new GridBagConstraints(1, 5, 1, 1, 0.0, 1.0, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + + incomingBrowseButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + pickFile("Choose Incoming Sound File", incomingMessageSound); + } + }); + + + outgoingBrowseButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + pickFile("Choose Outgoing Sound File", outgoingMessageSound); + } + }); + + offlineBrowseButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + pickFile("Choose Offline Sound File", userOfflineField); + } + }); + + if (Spark.isWindows()) { + fc.setFileSystemView(new WindowsFileSystemView()); + } + } + + public void setIncomingMessageSound(String path) { + incomingMessageSound.setText(path); + } + + public void setOutgoingMessageSound(String path) { + outgoingMessageSound.setText(path); + } + + public void setOfflineSound(String path) { + userOfflineField.setText(path); + } + + public void playIncomingSound(boolean play) { + incomingMessageBox.setSelected(play); + } + + public void playOutgoingSound(boolean play) { + outgoingMessageBox.setSelected(play); + } + + public void playOfflineSound(boolean play) { + userOfflineCheckbox.setSelected(play); + } + + + public String getIncomingSound() { + return incomingMessageSound.getText(); + } + + public boolean playIncomingSound() { + return incomingMessageBox.isSelected(); + } + + public boolean playOutgoingSound() { + return outgoingMessageBox.isSelected(); + } + + public String getOutgoingSound() { + return outgoingMessageSound.getText(); + } + + public boolean playOfflineSound() { + return userOfflineCheckbox.isSelected(); + } + + public String getOfflineSound() { + return userOfflineField.getText(); + } + + private void pickFile(String title, JTextField field) { + fc.setDialogTitle(title); + int returnVal = fc.showOpenDialog(this); + + if (returnVal == JFileChooser.APPROVE_OPTION) { + File file = fc.getSelectedFile(); + field.setText(file.getAbsolutePath()); + } + else { + + } + } + + } + + private File getSoundSettingsFile() { + File file = new File(Spark.getUserHome(), "Spark"); + if (!file.exists()) { + file.mkdirs(); + } + return new File(file, "sound-settings.xml"); + } + + private void saveSoundsFile() { + try { + FileWriter writer = new FileWriter(getSoundSettingsFile()); + xstream.toXML(preferences, writer); + } + catch (Exception e) { + Log.error("Error saving sound settings.", e); + } + } + + public SoundPreferences getPreferences() { + if (preferences == null) { + load(); + } + return preferences; + } + + public void shutdown() { + + } + +} diff --git a/src/java/org/jivesoftware/sparkimpl/preference/sounds/SoundPreferences.java b/src/java/org/jivesoftware/sparkimpl/preference/sounds/SoundPreferences.java new file mode 100644 index 00000000..450fa22b --- /dev/null +++ b/src/java/org/jivesoftware/sparkimpl/preference/sounds/SoundPreferences.java @@ -0,0 +1,81 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.sparkimpl.preference.sounds; + +import org.jivesoftware.Spark; + +import java.io.File; + +public class SoundPreferences { + + private String outgoingSound; + private String incomingSound; + private String offlineSound; + + private boolean playOutgoingSound = false; + private boolean playIncomingSound = false; + private boolean playOfflineSound = false; + + public SoundPreferences() { + // Set initial sounds + outgoingSound = new File(Spark.getResourceDirectory(), "sounds/outgoing.wav").getAbsolutePath(); + incomingSound = new File(Spark.getResourceDirectory(), "sounds/incoming.wav").getAbsolutePath(); + offlineSound = new File(Spark.getResourceDirectory(), "sounds/presence_changed.wav").getAbsolutePath(); + } + + public String getOutgoingSound() { + return outgoingSound; + } + + public void setOutgoingSound(String outgoingSound) { + this.outgoingSound = outgoingSound; + } + + public String getIncomingSound() { + return incomingSound; + } + + public void setIncomingSound(String incomingSound) { + this.incomingSound = incomingSound; + } + + public String getOfflineSound() { + return offlineSound; + } + + public void setOfflineSound(String offlineSound) { + this.offlineSound = offlineSound; + } + + public boolean isPlayOutgoingSound() { + return playOutgoingSound; + } + + public void setPlayOutgoingSound(boolean playOutgoingSound) { + this.playOutgoingSound = playOutgoingSound; + } + + public boolean isPlayIncomingSound() { + return playIncomingSound; + } + + public void setPlayIncomingSound(boolean playIncomingSound) { + this.playIncomingSound = playIncomingSound; + } + + public boolean isPlayOfflineSound() { + return playOfflineSound; + } + + public void setPlayOfflineSound(boolean playOfflineSound) { + this.playOfflineSound = playOfflineSound; + } +} diff --git a/src/java/org/jivesoftware/sparkimpl/profile/AvatarPanel.java b/src/java/org/jivesoftware/sparkimpl/profile/AvatarPanel.java new file mode 100644 index 00000000..0cf5e36c --- /dev/null +++ b/src/java/org/jivesoftware/sparkimpl/profile/AvatarPanel.java @@ -0,0 +1,235 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.sparkimpl.profile; + +import org.jivesoftware.Spark; +import org.jivesoftware.spark.component.borders.PartialLineBorder; +import org.jivesoftware.spark.util.GraphicUtils; +import org.jivesoftware.spark.util.SwingWorker; +import org.jivesoftware.spark.util.WindowsFileSystemView; +import org.jivesoftware.spark.util.log.Log; + +import javax.swing.Icon; +import javax.swing.ImageIcon; +import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.JFileChooser; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JTextField; +import javax.swing.filechooser.FileFilter; + +import java.awt.Color; +import java.awt.Component; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Image; +import java.awt.Insets; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.File; +import java.io.IOException; + +public class AvatarPanel extends JPanel implements ActionListener { + private JLabel avatar; + private byte[] bytes; + private File avatarFile; + final JButton browseButton = new JButton("Browse"); + final JButton clearButton = new JButton("Clear"); + private JFileChooser fc; + + public AvatarPanel() { + setLayout(new GridBagLayout()); + + + final JLabel photo = new JLabel("Avatar:"); + + avatar = new JLabel(); + + add(photo, new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + add(avatar, new GridBagConstraints(1, 0, 1, 2, 1.0, 1.0, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + add(browseButton, new GridBagConstraints(2, 0, 1, 1, 0.0, 0.0, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + add(clearButton, new GridBagConstraints(2, 1, 1, 1, 0.0, 0.0, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + + browseButton.addActionListener(this); + + clearButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent actionEvent) { + avatar.setIcon(null); + bytes = null; + avatarFile = null; + } + }); + + avatar.setText("No avatar was configured by the user."); + + GraphicUtils.makeSameSize(new JComponent[]{browseButton, clearButton}); + avatar.setBorder(new PartialLineBorder(Color.lightGray, 1)); + + + Thread thread = new Thread(new Runnable() { + public void run() { + if (fc == null) { + fc = new JFileChooser(Spark.getUserHome()); + if (Spark.isWindows()) { + fc.setFileSystemView(new WindowsFileSystemView()); + } + } + } + }); + + thread.start(); + } + + public void setEditable(boolean editable) { + browseButton.setVisible(editable); + clearButton.setVisible(editable); + } + + public void setAvatar(ImageIcon icon) { + avatar.setIcon(new ImageIcon(icon.getImage().getScaledInstance(-1, 48, Image.SCALE_SMOOTH))); + avatar.setText(""); + } + + public void setAvatarBytes(byte[] bytes) { + this.bytes = bytes; + } + + public byte[] getAvatarBytes() { + return bytes; + } + + public Icon getAvatar() { + return avatar.getIcon(); + } + + public File getAvatarFile() { + return avatarFile; + } + + public void actionPerformed(ActionEvent e) { + + fc.setFileFilter(new ImageFilter()); + + int result = fc.showOpenDialog(this); + // Determine which button was clicked to close the dialog + switch (result) { + case JFileChooser.APPROVE_OPTION: + final JComponent parent = this; + SwingWorker worker = new SwingWorker() { + File file = null; + + public Object construct() { + file = fc.getSelectedFile(); + try { + ImageIcon imageOnDisk = new ImageIcon(file.getCanonicalPath()); + Image avatarImage = imageOnDisk.getImage(); + if (avatarImage.getHeight(null) > 96 || avatarImage.getWidth(null) > 96) { + avatarImage = avatarImage.getScaledInstance(-1, 64, Image.SCALE_SMOOTH); + } + return avatarImage; + } + catch (IOException e1) { + Log.error(e1); + } + return null; + } + + public void finished() { + Image avatarImage = (Image)get(); + // Check size. + long length = GraphicUtils.getBytesFromImage(avatarImage).length * 8; + + long k = 8192; + + long actualSize = (length / k) + 1; + + if (actualSize > 16) { + // Do not allow + JOptionPane.showMessageDialog(parent, "This image is too large to use. Please specify an image 16k or smaller."); + return; + } + + setAvatar(new ImageIcon(avatarImage)); + avatarFile = file; + } + }; + + worker.start(); + } + } + + public class ImageFilter extends FileFilter { + public final String jpeg = "jpeg"; + public final String jpg = "jpg"; + public final String gif = "gif"; + public final String png = "png"; + + //Accept all directories and all gif, jpg, tiff, or png files. + public boolean accept(File f) { + if (f.isDirectory()) { + return true; + } + + String extension = getExtension(f); + if (extension != null) { + if ( + extension.equals(gif) || + extension.equals(jpeg) || + extension.equals(jpg) || + extension.equals(png) + ) { + return true; + } + else { + return false; + } + } + + return false; + } + + /* + * Get the extension of a file. + */ + public String getExtension(File f) { + String ext = null; + String s = f.getName(); + int i = s.lastIndexOf('.'); + + if (i > 0 && i < s.length() - 1) { + ext = s.substring(i + 1).toLowerCase(); + } + return ext; + } + + //The description of this filter + public String getDescription() { + return "*.JPEG, *.GIF, *.PNG"; + } + } + + public void allowEditing(boolean allowEditing) { + Component[] comps = getComponents(); + final int no = comps != null ? comps.length : 0; + for (int i = 0; i < no; i++) { + Component comp = comps[i]; + if (comp instanceof JTextField) { + ((JTextField)comp).setEditable(allowEditing); + } + } + } + + +} + + diff --git a/src/java/org/jivesoftware/sparkimpl/profile/BusinessPanel.java b/src/java/org/jivesoftware/sparkimpl/profile/BusinessPanel.java new file mode 100644 index 00000000..d4932857 --- /dev/null +++ b/src/java/org/jivesoftware/sparkimpl/profile/BusinessPanel.java @@ -0,0 +1,237 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.sparkimpl.profile; + +import org.jivesoftware.spark.util.ResourceUtils; + +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextField; + +import java.awt.Component; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; + +public class BusinessPanel extends JPanel { + private JLabel companyLabel = new JLabel(); + private JLabel streetLabel = new JLabel(); + private JLabel cityLabel = new JLabel(); + private JLabel stateLabel = new JLabel(); + private JLabel zipCodeLabel = new JLabel(); + private JLabel countryLabel = new JLabel(); + + private JTextField companyField = new JTextField(); + private JTextField cityField = new JTextField(); + private JTextField stateField = new JTextField(); + private JTextField zipCodeField = new JTextField(); + private JTextField countryField = new JTextField(); + private JTextField streetField = new JTextField(); + + + private JLabel jobTitleLabel = new JLabel(); + private JLabel departmentLabel = new JLabel(); + private JLabel phoneLabel = new JLabel(); + private JLabel faxLabel = new JLabel(); + private JLabel pagerLabel = new JLabel(); + private JLabel mobileLabel = new JLabel(); + private JLabel webPageLabel = new JLabel(); + private JTextField jobTitleField = new JTextField(); + private JTextField departmentField = new JTextField(); + private JTextField phoneField = new JTextField(); + private JTextField faxField = new JTextField(); + private JTextField pagerField = new JTextField(); + private JTextField mobileField = new JTextField(); + private JTextField webPageField = new JTextField(); + + public BusinessPanel() { + this.setLayout(new GridBagLayout()); + + // Setup Resources + ResourceUtils.resLabel(companyLabel, companyField, "&Company:"); + ResourceUtils.resLabel(streetLabel, streetField, "&Street Address:"); + ResourceUtils.resLabel(cityLabel, cityField, "&City:"); + ResourceUtils.resLabel(stateLabel, stateField, "S&tate/Province:"); + ResourceUtils.resLabel(zipCodeLabel, zipCodeField, "&Postal Code:"); + ResourceUtils.resLabel(countryLabel, countryField, "C&ountry:"); + + ResourceUtils.resLabel(jobTitleLabel, jobTitleField, "&Job Title:"); + ResourceUtils.resLabel(departmentLabel, departmentField, "&Department:"); + ResourceUtils.resLabel(phoneLabel, phoneField, "&Phone:"); + ResourceUtils.resLabel(faxLabel, faxField, "&Fax:"); + ResourceUtils.resLabel(mobileLabel, mobileField, "&Mobile:"); + ResourceUtils.resLabel(webPageLabel, webPageField, "&Web Page:"); + ResourceUtils.resLabel(pagerLabel, pagerField, "&Pager:"); + + this.add(streetField, new GridBagConstraints(1, 1, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); + this.add(countryField, new GridBagConstraints(1, 5, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); + this.add(zipCodeField, new GridBagConstraints(1, 4, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); + this.add(stateField, new GridBagConstraints(1, 3, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); + this.add(cityField, new GridBagConstraints(1, 2, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); + this.add(companyField, new GridBagConstraints(1, 0, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); + this.add(countryLabel, new GridBagConstraints(0, 5, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + this.add(zipCodeLabel, new GridBagConstraints(0, 4, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + this.add(stateLabel, new GridBagConstraints(0, 3, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + this.add(cityLabel, new GridBagConstraints(0, 2, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + this.add(streetLabel, new GridBagConstraints(0, 1, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + this.add(companyLabel, new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + + this.add(jobTitleLabel, new GridBagConstraints(2, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + this.add(jobTitleField, new GridBagConstraints(3, 0, 1, 1, 1.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); + + this.add(departmentLabel, new GridBagConstraints(2, 1, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + this.add(departmentField, new GridBagConstraints(3, 1, 1, 1, 1.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); + + this.add(phoneLabel, new GridBagConstraints(2, 2, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + this.add(phoneField, new GridBagConstraints(3, 2, 1, 1, 1.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); + + this.add(faxLabel, new GridBagConstraints(2, 3, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + this.add(faxField, new GridBagConstraints(3, 3, 1, 1, 1.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); + + this.add(pagerLabel, new GridBagConstraints(2, 4, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + this.add(pagerField, new GridBagConstraints(3, 4, 1, 1, 1.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); + + this.add(mobileLabel, new GridBagConstraints(2, 5, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + this.add(mobileField, new GridBagConstraints(3, 5, 1, 1, 1.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); + + this.add(webPageLabel, new GridBagConstraints(2, 6, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + this.add(webPageField, new GridBagConstraints(3, 6, 1, 1, 1.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); + + companyField.setNextFocusableComponent(streetField); + streetField.setNextFocusableComponent(cityField); + cityField.setNextFocusableComponent(stateField); + stateField.setNextFocusableComponent(zipCodeField); + zipCodeField.setNextFocusableComponent(countryField); + countryField.setNextFocusableComponent(jobTitleField); + jobTitleField.setNextFocusableComponent(departmentField); + departmentField.setNextFocusableComponent(phoneField); + phoneField.setNextFocusableComponent(faxField); + faxField.setNextFocusableComponent(pagerField); + pagerField.setNextFocusableComponent(mobileField); + mobileField.setNextFocusableComponent(webPageField); + } + + public void setCompany(String company) { + companyField.setText(company); + } + + public String getCompany() { + return companyField.getText(); + } + + public void setStreetAddress(String address) { + streetField.setText(address); + } + + public String getStreetAddress() { + return streetField.getText(); + } + + public void setCity(String city) { + cityField.setText(city); + } + + public String getCity() { + return cityField.getText(); + } + + public void setState(String state) { + stateField.setText(state); + } + + public String getState() { + return stateField.getText(); + } + + public void setZipCode(String zip) { + zipCodeField.setText(zip); + } + + public String getZipCode() { + return zipCodeField.getText(); + } + + public void setCountry(String country) { + countryField.setText(country); + } + + public String getCountry() { + return countryField.getText(); + } + + public void setJobTitle(String jobTitle) { + jobTitleField.setText(jobTitle); + } + + public String getJobTitle() { + return jobTitleField.getText(); + } + + public void setDepartment(String department) { + departmentField.setText(department); + } + + public String getDepartment() { + return departmentField.getText(); + } + + public void setPhone(String phone) { + phoneField.setText(phone); + } + + public String getPhone() { + return phoneField.getText(); + } + + public void setFax(String fax) { + faxField.setText(fax); + } + + public String getFax() { + return faxField.getText(); + } + + public void setPager(String pager) { + pagerField.setText(pager); + } + + public String getPager() { + return pagerField.getText(); + } + + public void setMobile(String mobile) { + mobileField.setText(mobile); + } + + public String getMobile() { + return mobileField.getText(); + } + + public void setWebPage(String webPage) { + webPageField.setText(webPage); + } + + public String getWebPage() { + return webPageField.getText(); + } + + public void allowEditing(boolean allowEditing) { + Component[] comps = getComponents(); + final int no = comps != null ? comps.length : 0; + for (int i = 0; i < no; i++) { + Component comp = comps[i]; + if (comp instanceof JTextField) { + ((JTextField)comp).setEditable(allowEditing); + } + } + } + +} \ No newline at end of file diff --git a/src/java/org/jivesoftware/sparkimpl/profile/HomePanel.java b/src/java/org/jivesoftware/sparkimpl/profile/HomePanel.java new file mode 100644 index 00000000..1e65a582 --- /dev/null +++ b/src/java/org/jivesoftware/sparkimpl/profile/HomePanel.java @@ -0,0 +1,193 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.sparkimpl.profile; + +import org.jivesoftware.spark.util.ResourceUtils; + +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextField; + +import java.awt.Component; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; + +public class HomePanel extends JPanel { + private JLabel streetLabel = new JLabel(); + private JLabel cityLabel = new JLabel(); + private JLabel stateLabel = new JLabel(); + private JLabel zipCodeLabel = new JLabel(); + private JLabel countryLabel = new JLabel(); + + private JTextField cityField = new JTextField(); + private JTextField stateField = new JTextField(); + private JTextField zipCodeField = new JTextField(); + private JTextField countryField = new JTextField(); + private JTextField streetField = new JTextField(); + + + private JLabel phoneLabel = new JLabel(); + private JLabel faxLabel = new JLabel(); + private JLabel pagerLabel = new JLabel(); + private JLabel mobileLabel = new JLabel(); + private JTextField phoneField = new JTextField(); + private JTextField faxField = new JTextField(); + private JTextField pagerField = new JTextField(); + private JTextField mobileField = new JTextField(); + private JTextField webPageField = new JTextField(); + + public HomePanel() { + this.setLayout(new GridBagLayout()); + + // Setup Resources + ResourceUtils.resLabel(streetLabel, streetField, "&Street Address:"); + ResourceUtils.resLabel(cityLabel, cityField, "&City:"); + ResourceUtils.resLabel(stateLabel, stateField, "S&tate/Province:"); + ResourceUtils.resLabel(zipCodeLabel, zipCodeField, "&Postal Code:"); + ResourceUtils.resLabel(countryLabel, countryField, "C&ountry:"); + + ResourceUtils.resLabel(phoneLabel, phoneField, "&Phone:"); + ResourceUtils.resLabel(faxLabel, faxField, "&Fax:"); + ResourceUtils.resLabel(mobileLabel, mobileField, "&Mobile:"); + ResourceUtils.resLabel(pagerLabel, pagerField, "&Pager:"); + + this.add(streetLabel, new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + this.add(streetField, new GridBagConstraints(1, 0, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); + + this.add(cityLabel, new GridBagConstraints(0, 1, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + this.add(cityField, new GridBagConstraints(1, 1, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); + + this.add(stateLabel, new GridBagConstraints(0, 2, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + this.add(stateField, new GridBagConstraints(1, 2, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); + + this.add(zipCodeLabel, new GridBagConstraints(0, 3, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + this.add(zipCodeField, new GridBagConstraints(1, 3, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); + + this.add(countryLabel, new GridBagConstraints(0, 4, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + this.add(countryField, new GridBagConstraints(1, 4, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); + + + this.add(phoneLabel, new GridBagConstraints(2, 0, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + this.add(faxLabel, new GridBagConstraints(2, 1, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + this.add(pagerLabel, new GridBagConstraints(2, 2, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + this.add(mobileLabel, new GridBagConstraints(2, 3, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + + this.add(phoneField, new GridBagConstraints(3, 0, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); + this.add(faxField, new GridBagConstraints(3, 1, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); + this.add(pagerField, new GridBagConstraints(3, 2, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); + this.add(mobileField, new GridBagConstraints(3, 3, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); + + + streetField.setNextFocusableComponent(cityField); + cityField.setNextFocusableComponent(stateField); + stateField.setNextFocusableComponent(zipCodeField); + zipCodeField.setNextFocusableComponent(countryField); + phoneField.setNextFocusableComponent(faxField); + faxField.setNextFocusableComponent(pagerField); + pagerField.setNextFocusableComponent(mobileField); + mobileField.setNextFocusableComponent(webPageField); + countryField.setNextFocusableComponent(phoneField); + } + + + public void setStreetAddress(String address) { + streetField.setText(address); + } + + public String getStreetAddress() { + return streetField.getText(); + } + + public void setCity(String city) { + cityField.setText(city); + } + + public String getCity() { + return cityField.getText(); + } + + public void setState(String state) { + stateField.setText(state); + } + + public String getState() { + return stateField.getText(); + } + + public void setZipCode(String zip) { + zipCodeField.setText(zip); + } + + public String getZipCode() { + return zipCodeField.getText(); + } + + public void setCountry(String country) { + countryField.setText(country); + } + + public String getCountry() { + return countryField.getText(); + } + + + public void setPhone(String phone) { + phoneField.setText(phone); + } + + public String getPhone() { + return phoneField.getText(); + } + + public void setFax(String fax) { + faxField.setText(fax); + } + + public String getFax() { + return faxField.getText(); + } + + public void setPager(String pager) { + pagerField.setText(pager); + } + + public String getPager() { + return pagerField.getText(); + } + + public void setMobile(String mobile) { + mobileField.setText(mobile); + } + + public String getMobile() { + return mobileField.getText(); + } + + public void setWebPage(String webPage) { + webPageField.setText(webPage); + } + + public String getWebPage() { + return webPageField.getText(); + } + + public void allowEditing(boolean allowEditing) { + Component[] comps = getComponents(); + final int no = comps != null ? comps.length : 0; + for (int i = 0; i < no; i++) { + Component comp = comps[i]; + if (comp instanceof JTextField) { + ((JTextField)comp).setEditable(allowEditing); + } + } + } +} \ No newline at end of file diff --git a/src/java/org/jivesoftware/sparkimpl/profile/PersonalPanel.java b/src/java/org/jivesoftware/sparkimpl/profile/PersonalPanel.java new file mode 100644 index 00000000..cf253823 --- /dev/null +++ b/src/java/org/jivesoftware/sparkimpl/profile/PersonalPanel.java @@ -0,0 +1,128 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.sparkimpl.profile; + +import org.jivesoftware.spark.util.ResourceUtils; + +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextField; + +import java.awt.Component; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; + +public class PersonalPanel extends JPanel { + + private JTextField firstNameField; + private JTextField middleNameField; + private JTextField lastNameField; + private JTextField nicknameField; + private JTextField emailAddressField; + + public PersonalPanel() { + setLayout(new GridBagLayout()); + + // Handle First Name + JLabel firstNameLabel = new JLabel(); + firstNameField = new JTextField(); + ResourceUtils.resLabel(firstNameLabel, firstNameField, "&First Name:"); + + add(firstNameLabel, new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + add(firstNameField, new GridBagConstraints(1, 0, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); + + // Handle Middle Name + JLabel middleNameLabel = new JLabel(); + middleNameField = new JTextField(); + ResourceUtils.resLabel(middleNameLabel, middleNameField, "&Middle Name:"); + add(middleNameLabel, new GridBagConstraints(0, 1, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + add(middleNameField, new GridBagConstraints(1, 1, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); + + // Handle Last Name + JLabel lastNameLabel = new JLabel(); + lastNameField = new JTextField(); + ResourceUtils.resLabel(lastNameLabel, lastNameField, "&Last Name:"); + add(lastNameLabel, new GridBagConstraints(0, 2, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + add(lastNameField, new GridBagConstraints(1, 2, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); + + // Handle Nickname + JLabel nicknameLabel = new JLabel(); + nicknameField = new JTextField(); + ResourceUtils.resLabel(nicknameLabel, nicknameField, "&Nickname:"); + add(nicknameLabel, new GridBagConstraints(0, 4, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + add(nicknameField, new GridBagConstraints(1, 4, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); + + // Handle Email Address + JLabel emaiAddressLabel = new JLabel(); + emailAddressField = new JTextField(); + ResourceUtils.resLabel(emaiAddressLabel, emailAddressField, "&Email Address:"); + add(emaiAddressLabel, new GridBagConstraints(0, 5, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + add(emailAddressField, new GridBagConstraints(1, 5, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); + } + + public String getFirstName() { + return firstNameField.getText(); + } + + public void setFirstName(String firstName) { + firstNameField.setText(firstName); + } + + public void setMiddleName(String middleName) { + middleNameField.setText(middleName); + } + + public String getMiddleName() { + return middleNameField.getText(); + } + + public void setLastName(String lastName) { + lastNameField.setText(lastName); + } + + public String getLastName() { + return lastNameField.getText(); + } + + + public void setNickname(String nickname) { + nicknameField.setText(nickname); + } + + public String getNickname() { + return nicknameField.getText(); + } + + public void setEmailAddress(String emailAddress) { + emailAddressField.setText(emailAddress); + } + + public String getEmailAddress() { + return emailAddressField.getText(); + } + + public void focus() { + firstNameField.requestFocus(); + } + + public void allowEditing(boolean allowEditing) { + Component[] comps = getComponents(); + final int no = comps != null ? comps.length : 0; + for (int i = 0; i < no; i++) { + Component comp = comps[i]; + if (comp instanceof JTextField) { + ((JTextField)comp).setEditable(allowEditing); + } + } + } + +} diff --git a/src/java/org/jivesoftware/sparkimpl/profile/VCardManager.java b/src/java/org/jivesoftware/sparkimpl/profile/VCardManager.java new file mode 100644 index 00000000..ef87d2e9 --- /dev/null +++ b/src/java/org/jivesoftware/sparkimpl/profile/VCardManager.java @@ -0,0 +1,576 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.sparkimpl.profile; + +import org.jivesoftware.resource.SparkRes; +import org.jivesoftware.smack.PacketInterceptor; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.filter.PacketFilter; +import org.jivesoftware.smack.filter.PacketTypeFilter; +import org.jivesoftware.smack.packet.Packet; +import org.jivesoftware.smack.packet.PacketExtension; +import org.jivesoftware.smack.packet.Presence; +import org.jivesoftware.smack.packet.XMPPError; +import org.jivesoftware.smack.util.StringUtils; +import org.jivesoftware.smackx.packet.VCard; +import org.jivesoftware.spark.SparkManager; +import org.jivesoftware.spark.component.TitlePanel; +import org.jivesoftware.spark.component.borders.PartialLineBorder; +import org.jivesoftware.spark.util.GraphicUtils; +import org.jivesoftware.spark.util.ModelUtil; +import org.jivesoftware.spark.util.ResourceUtils; +import org.jivesoftware.spark.util.SwingWorker; +import org.jivesoftware.spark.util.log.Log; +import org.jivesoftware.sparkimpl.plugin.manager.Enterprise; +import org.jivesoftware.sparkimpl.profile.ext.JabberAvatarExtension; +import org.jivesoftware.sparkimpl.profile.ext.VCardUpdateExtension; + +import javax.swing.ImageIcon; +import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.JDialog; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JMenu; +import javax.swing.JMenuItem; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JTabbedPane; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Image; +import java.awt.Insets; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.io.File; +import java.net.MalformedURLException; +import java.util.HashMap; +import java.util.Map; + +public class VCardManager { + private BusinessPanel businessPanel; + private PersonalPanel personalPanel; + private HomePanel homePanel; + private AvatarPanel avatarPanel; + private JLabel avatarLabel; + private VCard vcard = new VCard(); + + private Map vcardMap = new HashMap(); + private boolean vcardLoaded = false; + + public VCardManager() { + initialize(); + + // Intercept all presence packets being sent and append vcard information. + PacketFilter presenceFilter = new PacketTypeFilter(Presence.class); + SparkManager.getConnection().addPacketWriterInterceptor(new PacketInterceptor() { + public void interceptPacket(Packet packet) { + Presence newPresence = (Presence)packet; + VCardUpdateExtension update = new VCardUpdateExtension(); + JabberAvatarExtension jax = new JabberAvatarExtension(); + + PacketExtension updateExt = newPresence.getExtension(update.getElementName(), update.getNamespace()); + PacketExtension jabberExt = newPresence.getExtension(jax.getElementName(), jax.getNamespace()); + + if (updateExt != null) { + newPresence.removeExtension(updateExt); + } + + if (jabberExt != null) { + newPresence.removeExtension(jabberExt); + } + + byte[] bytes = getVCard().getAvatar(); + if (bytes != null) { + String hash = org.jivesoftware.spark.util.StringUtils.hash(bytes); + update.setPhotoHash(hash); + jax.setPhotoHash(hash); + + newPresence.addExtension(update); + newPresence.addExtension(jax); + } + + } + }, presenceFilter); + } + + public void showProfile(JComponent parent) { + final JTabbedPane tabbedPane = new JTabbedPane(); + + personalPanel = new PersonalPanel(); + tabbedPane.addTab("Personal", personalPanel); + + businessPanel = new BusinessPanel(); + tabbedPane.addTab("Business", businessPanel); + + homePanel = new HomePanel(); + tabbedPane.addTab("Home", homePanel); + + avatarPanel = new AvatarPanel(); + tabbedPane.addTab("Avatar", avatarPanel); + + loadVCard(SparkManager.getSessionManager().getJID()); + + final JOptionPane pane; + final JDialog dlg; + + TitlePanel titlePanel; + + ImageIcon icon = getAvatarIcon(); + if (icon == null) { + icon = SparkRes.getImageIcon(SparkRes.BLANK_24x24); + } + + // Create the title panel for this dialog + titlePanel = new TitlePanel("Edit Profile Information", "To save changes to your profile, click Save.", icon, true); + + // Construct main panel w/ layout. + final JPanel mainPanel = new JPanel(); + mainPanel.setLayout(new BorderLayout()); + mainPanel.add(titlePanel, BorderLayout.NORTH); + + // The user should only be able to close this dialog. + Object[] options = {"Save", "Cancel"}; + pane = new JOptionPane(tabbedPane, JOptionPane.PLAIN_MESSAGE, JOptionPane.OK_CANCEL_OPTION, null, options, options[0]); + + mainPanel.add(pane, BorderLayout.CENTER); + + JOptionPane p = new JOptionPane(); + dlg = p.createDialog(parent, "Profile Information"); + dlg.setModal(false); + + dlg.pack(); + dlg.setSize(600, 400); + dlg.setResizable(true); + dlg.setContentPane(mainPanel); + dlg.setLocationRelativeTo(parent); + + PropertyChangeListener changeListener = new PropertyChangeListener() { + public void propertyChange(PropertyChangeEvent e) { + String value = (String)pane.getValue(); + if ("Cancel".equals(value)) { + pane.removePropertyChangeListener(this); + dlg.dispose(); + } + else if ("Save".equals(value)) { + pane.removePropertyChangeListener(this); + dlg.dispose(); + saveVCard(); + } + } + }; + + pane.addPropertyChangeListener(changeListener); + + dlg.setVisible(true); + dlg.toFront(); + dlg.requestFocus(); + + personalPanel.focus(); + } + + public void viewProfile(final String jid, final JComponent parent) { + SwingWorker worker = new SwingWorker() { + VCard userVCard = new VCard(); + + public Object construct() { + userVCard = getVCard(jid); + return vcard; + } + + public void finished() { + if (userVCard.getError() != null) { + // Show vcard not found + JOptionPane.showMessageDialog(parent, "Unable to locate a profile for " + jid, "Profile Not Found", JOptionPane.ERROR_MESSAGE); + return; + } + else { + showUserProfile(jid, userVCard, parent); + } + } + }; + + worker.start(); + + } + + private void showUserProfile(String jid, VCard vcard, JComponent parent) { + final JTabbedPane tabbedPane = new JTabbedPane(); + + personalPanel = new PersonalPanel(); + tabbedPane.addTab("Personal", personalPanel); + + businessPanel = new BusinessPanel(); + tabbedPane.addTab("Business", businessPanel); + + homePanel = new HomePanel(); + tabbedPane.addTab("Home", homePanel); + + avatarPanel = new AvatarPanel(); + avatarPanel.setEditable(false); + + personalPanel.allowEditing(false); + businessPanel.allowEditing(false); + homePanel.allowEditing(false); + + final JOptionPane pane; + final JFrame dlg; + + avatarLabel = new JLabel(); + avatarLabel.setHorizontalAlignment(JButton.RIGHT); + avatarLabel.setBorder(new PartialLineBorder(Color.gray, 1)); + + // Construct main panel w/ layout. + final JPanel mainPanel = new JPanel(); + mainPanel.setLayout(new GridBagLayout()); + mainPanel.add(avatarLabel, new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.EAST, GridBagConstraints.NONE, new Insets(5, 5, 0, 5), 0, 0)); + + // The user should only be able to close this dialog. + Object[] options = {"Close"}; + pane = new JOptionPane(tabbedPane, JOptionPane.PLAIN_MESSAGE, JOptionPane.OK_CANCEL_OPTION, null, options, options[0]); + + mainPanel.add(pane, new GridBagConstraints(0, 1, 1, 1, 1.0, 1.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 5, 5, 5), 0, 0)); + + dlg = new JFrame("Viewing Profile For " + jid); + dlg.setIconImage(SparkRes.getImageIcon(SparkRes.SMALL_PROFILE_IMAGE).getImage()); + + dlg.pack(); + dlg.setSize(500, 400); + dlg.setResizable(true); + dlg.setContentPane(mainPanel); + dlg.setLocationRelativeTo(parent); + + PropertyChangeListener changeListener = new PropertyChangeListener() { + public void propertyChange(PropertyChangeEvent e) { + if (pane.getValue() instanceof Integer) { + pane.removePropertyChangeListener(this); + dlg.dispose(); + return; + } + String value = (String)pane.getValue(); + if ("Close".equals(value)) { + pane.removePropertyChangeListener(this); + dlg.dispose(); + } + } + }; + + pane.addPropertyChangeListener(changeListener); + + dlg.addKeyListener(new KeyAdapter() { + public void keyPressed(KeyEvent keyEvent) { + if (keyEvent.getKeyChar() == KeyEvent.VK_ESCAPE) { + dlg.dispose(); + } + } + }); + + populateVCardUI(vcard); + + dlg.setVisible(true); + dlg.toFront(); + dlg.requestFocus(); + } + + public void initialize() { + boolean enabled = Enterprise.containsFeature(Enterprise.VCARD_FEATURE); + if (!enabled) { + return; + } + + // Add Actions Menu + final JMenu contactsMenu = SparkManager.getMainWindow().getMenuByName("Contacts"); + final JMenu communicatorMenu = SparkManager.getMainWindow().getJMenuBar().getMenu(0); + + JMenuItem editProfileMenu = new JMenuItem("Edit Profile...", SparkRes.getImageIcon(SparkRes.SMALL_BUSINESS_MAN_VIEW)); + ResourceUtils.resButton(editProfileMenu, "&Edit My Profile..."); + + int size = contactsMenu.getMenuComponentCount(); + + communicatorMenu.insert(editProfileMenu, 1); + editProfileMenu.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + SwingWorker vcardLoaderWorker = new SwingWorker() { + public Object construct() { + try { + vcard.load(SparkManager.getConnection()); + } + catch (XMPPException e) { + Log.error("Error loading vcard information.", e); + } + return "ok"; + } + + public void finished() { + showProfile(SparkManager.getWorkspace()); + } + }; + vcardLoaderWorker.start(); + } + }); + + JMenuItem viewProfileMenu = new JMenuItem("Lookup Profile...", SparkRes.getImageIcon(SparkRes.FIND_TEXT_IMAGE)); + ResourceUtils.resButton(viewProfileMenu, "&Lookup Profile..."); + contactsMenu.insert(viewProfileMenu, size - 1); + viewProfileMenu.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + String jidToView = JOptionPane.showInputDialog(SparkManager.getMainWindow(), "Enter Jabber ID:", "Lookup Profile", JOptionPane.QUESTION_MESSAGE); + if (ModelUtil.hasLength(jidToView) && jidToView.indexOf("@") != -1 && ModelUtil.hasLength(StringUtils.parseServer(jidToView))) { + viewProfile(jidToView, SparkManager.getWorkspace()); + } + else if (ModelUtil.hasLength(jidToView)) { + JOptionPane.showMessageDialog(SparkManager.getMainWindow(), "Not a valid Jabber ID", "Invalid JID", JOptionPane.ERROR_MESSAGE); + } + } + }); + } + + public void shutdown() { + + } + + public boolean canShutDown() { + return true; + } + + private void loadVCard(String jid) { + final String userJID = StringUtils.parseBareAddress(jid); + final VCard userVCard = new VCard(); + + SwingWorker worker = new SwingWorker() { + public Object construct() { + try { + userVCard.load(SparkManager.getConnection(), userJID); + } + catch (XMPPException e) { + } + return "ok"; + } + + public void finished() { + populateVCardUI(userVCard); + } + }; + + worker.start(); + } + + + private void saveVCard() { + // Save personal info + vcard.setFirstName(personalPanel.getFirstName()); + vcard.setLastName(personalPanel.getLastName()); + vcard.setMiddleName(personalPanel.getMiddleName()); + vcard.setEmailHome(personalPanel.getEmailAddress()); + vcard.setNickName(personalPanel.getNickname()); + + // Save business info + vcard.setOrganization(businessPanel.getCompany()); + vcard.setAddressFieldWork("STREET", businessPanel.getStreetAddress()); + vcard.setAddressFieldWork("LOCALITY", businessPanel.getCity()); + vcard.setAddressFieldWork("REGION", businessPanel.getState()); + vcard.setAddressFieldWork("PCODE", businessPanel.getZipCode()); + vcard.setAddressFieldWork("CTRY", businessPanel.getCountry()); + vcard.setField("TITLE", businessPanel.getJobTitle()); + vcard.setOrganizationUnit(businessPanel.getDepartment()); + vcard.setPhoneWork("VOICE", businessPanel.getPhone()); + vcard.setPhoneWork("FAX", businessPanel.getFax()); + vcard.setPhoneWork("PAGER", businessPanel.getPager()); + vcard.setPhoneWork("CELL", businessPanel.getMobile()); + vcard.setField("URL", businessPanel.getWebPage()); + + // Save Home Info + vcard.setAddressFieldHome("STREET", homePanel.getStreetAddress()); + vcard.setAddressFieldHome("LOCALITY", homePanel.getCity()); + vcard.setAddressFieldHome("REGION", homePanel.getState()); + vcard.setAddressFieldHome("PCODE", homePanel.getZipCode()); + vcard.setAddressFieldHome("CTRY", homePanel.getCountry()); + vcard.setPhoneHome("VOICE", homePanel.getPhone()); + vcard.setPhoneHome("FAX", homePanel.getFax()); + vcard.setPhoneHome("PAGER", homePanel.getPager()); + vcard.setPhoneHome("CELL", homePanel.getMobile()); + + + final SwingWorker worker = new SwingWorker() { + boolean saved = false; + + public Object construct() { + + // Save Avatar + final File avatarFile = avatarPanel.getAvatarFile(); + if (avatarFile != null) { + try { + // Make it 48x48 + ImageIcon icon = new ImageIcon(avatarFile.toURL()); + Image image = icon.getImage(); + image = image.getScaledInstance(-1, 48, Image.SCALE_SMOOTH); + byte[] imageBytes = GraphicUtils.getBytesFromImage(image); + vcard.setAvatar(imageBytes); + } + catch (MalformedURLException e) { + Log.error("Unable to set avatar.", e); + } + } + else if (avatarPanel.getAvatarBytes() != null) { + vcard.setAvatar(avatarPanel.getAvatarBytes()); + } + try { + vcard.save(SparkManager.getConnection()); + saved = true; + + byte[] avatarBytes = vcard.getAvatar(); + + // Notify users. + if (avatarFile != null || avatarBytes != null) { + Presence presence = SparkManager.getWorkspace().getStatusBar().getPresence(); + Presence newPresence = new Presence(presence.getType(), presence.getStatus(), presence.getPriority(), presence.getMode()); + + // Change my own presence + SparkManager.getSessionManager().changePresence(newPresence); + } + else { + String firstName = vcard.getFirstName(); + String lastName = vcard.getLastName(); + if (ModelUtil.hasLength(firstName) && ModelUtil.hasLength(lastName)) { + SparkManager.getWorkspace().getStatusBar().setNickname(firstName + " " + lastName); + } + else if (ModelUtil.hasLength(firstName)) { + SparkManager.getWorkspace().getStatusBar().setNickname(firstName); + } + } + } + catch (XMPPException e) { + Log.error(e); + } + + return new Boolean(saved); + } + + public void finished() { + if (!saved) { + JOptionPane.showMessageDialog(SparkManager.getMainWindow(), "Server does not support VCards. Unable to save your VCard.", "Error", JOptionPane.ERROR_MESSAGE); + return; + } + else { + + } + } + }; + + worker.start(); + + + } + + private void populateVCardUI(VCard vcard) { + personalPanel.setFirstName(vcard.getFirstName()); + personalPanel.setMiddleName(vcard.getMiddleName()); + personalPanel.setLastName(vcard.getLastName()); + personalPanel.setEmailAddress(vcard.getEmailHome()); + personalPanel.setNickname(vcard.getNickName()); + + businessPanel.setCompany(vcard.getOrganization()); + businessPanel.setDepartment(vcard.getOrganizationUnit()); + businessPanel.setStreetAddress(vcard.getAddressFieldWork("STREET")); + businessPanel.setCity(vcard.getAddressFieldWork("LOCALITY")); + businessPanel.setState(vcard.getAddressFieldWork("REGION")); + businessPanel.setZipCode(vcard.getAddressFieldWork("PCODE")); + businessPanel.setCountry(vcard.getAddressFieldWork("CTRY")); + businessPanel.setJobTitle(vcard.getField("TITLE")); + businessPanel.setPhone(vcard.getPhoneWork("VOICE")); + businessPanel.setFax(vcard.getPhoneWork("FAX")); + businessPanel.setPager(vcard.getPhoneWork("PAGER")); + businessPanel.setMobile(vcard.getPhoneWork("CELL")); + businessPanel.setWebPage(vcard.getField("URL")); + + // Load Home Info + homePanel.setStreetAddress(vcard.getAddressFieldHome("STREET")); + homePanel.setCity(vcard.getAddressFieldHome("LOCALITY")); + homePanel.setState(vcard.getAddressFieldHome("REGION")); + homePanel.setZipCode(vcard.getAddressFieldHome("PCODE")); + homePanel.setCountry(vcard.getAddressFieldHome("CTRY")); + homePanel.setPhone(vcard.getPhoneHome("VOICE")); + homePanel.setFax(vcard.getPhoneHome("FAX")); + homePanel.setPager(vcard.getPhoneHome("PAGER")); + homePanel.setMobile(vcard.getPhoneHome("CELL")); + + // Set avatar + byte[] bytes = vcard.getAvatar(); + if (bytes != null) { + ImageIcon icon = new ImageIcon(bytes); + avatarPanel.setAvatar(icon); + avatarPanel.setAvatarBytes(bytes); + if (avatarLabel != null) { + icon = GraphicUtils.scaleImageIcon(icon, 48, 48); + + avatarLabel.setIcon(icon); + } + } + } + + public VCard getVCard() { + if (!vcardLoaded) { + try { + vcard.load(SparkManager.getConnection()); + } + catch (XMPPException e) { + } + vcardLoaded = true; + } + return vcard; + } + + private ImageIcon getAvatarIcon() { + // Set avatar + byte[] bytes = vcard.getAvatar(); + if (bytes != null) { + ImageIcon icon = new ImageIcon(bytes); + return GraphicUtils.scaleImageIcon(icon, 40, 40); + } + return null; + } + + public VCard getVCard(String jid) { + if (!vcardMap.containsKey(jid)) { + VCard vcard = new VCard(); + try { + vcard.load(SparkManager.getConnection(), jid); + vcardMap.put(jid, vcard); + } + catch (XMPPException e) { + Log.warning("Unable to load vcard for " + jid, e); + vcard.setError(new XMPPError(409)); + } + } + return (VCard)vcardMap.get(jid); + } + + public void addVCard(String jid, VCard vcard) { + vcardMap.put(jid, vcard); + } + + public static ImageIcon scale(ImageIcon icon) { + Image avatarImage = icon.getImage(); + if (icon.getIconHeight() > 64 || icon.getIconWidth() > 64) { + avatarImage = avatarImage.getScaledInstance(-1, 64, Image.SCALE_SMOOTH); + } + + return new ImageIcon(avatarImage); + } + +} diff --git a/src/java/org/jivesoftware/sparkimpl/profile/ext/JabberAvatarExtension.java b/src/java/org/jivesoftware/sparkimpl/profile/ext/JabberAvatarExtension.java new file mode 100644 index 00000000..f9d3de79 --- /dev/null +++ b/src/java/org/jivesoftware/sparkimpl/profile/ext/JabberAvatarExtension.java @@ -0,0 +1,40 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.sparkimpl.profile.ext; + +import org.jivesoftware.smack.packet.PacketExtension; + + +public class JabberAvatarExtension implements PacketExtension { + private String photoHash; + + public void setPhotoHash(String hash) { + photoHash = hash; + } + + public String getElementName() { + return "x"; + } + + public String getNamespace() { + return "jabber:x:avatar"; + } + + public String toXML() { + StringBuffer buf = new StringBuffer(); + buf.append("<").append(getElementName()).append(" xmlns=\"").append(getNamespace()).append("\">"); + buf.append(""); + buf.append(photoHash); + buf.append(""); + buf.append(""); + return buf.toString(); + } +} \ No newline at end of file diff --git a/src/java/org/jivesoftware/sparkimpl/profile/ext/VCardUpdateExtension.java b/src/java/org/jivesoftware/sparkimpl/profile/ext/VCardUpdateExtension.java new file mode 100644 index 00000000..d89d2316 --- /dev/null +++ b/src/java/org/jivesoftware/sparkimpl/profile/ext/VCardUpdateExtension.java @@ -0,0 +1,39 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.sparkimpl.profile.ext; + +import org.jivesoftware.smack.packet.PacketExtension; + +public class VCardUpdateExtension implements PacketExtension { + private String photoHash; + + public void setPhotoHash(String hash) { + photoHash = hash; + } + + public String getElementName() { + return "x"; + } + + public String getNamespace() { + return "vcard-temp:x:update"; + } + + public String toXML() { + StringBuffer buf = new StringBuffer(); + buf.append("<").append(getElementName()).append(" xmlns=\"").append(getNamespace()).append("\">"); + buf.append(""); + buf.append(photoHash); + buf.append(""); + buf.append(""); + return buf.toString(); + } +} diff --git a/src/java/org/jivesoftware/sparkimpl/search/users/SearchForm.java b/src/java/org/jivesoftware/sparkimpl/search/users/SearchForm.java new file mode 100644 index 00000000..779d0441 --- /dev/null +++ b/src/java/org/jivesoftware/sparkimpl/search/users/SearchForm.java @@ -0,0 +1,126 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.sparkimpl.search.users; + +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smackx.Form; +import org.jivesoftware.smackx.ReportedData; +import org.jivesoftware.smackx.search.UserSearchManager; +import org.jivesoftware.spark.SparkManager; +import org.jivesoftware.spark.ui.DataFormUI; +import org.jivesoftware.spark.util.ResourceUtils; +import org.jivesoftware.spark.util.log.Log; + +import javax.swing.AbstractAction; +import javax.swing.BorderFactory; +import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.KeyStroke; + +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.KeyEvent; + +public class SearchForm extends JPanel { + private UserSearchResults searchResults; + private DataFormUI questionForm; + private UserSearchManager searchManager; + private String serviceName; + private Form searchForm; + + public SearchForm(String service) { + this.serviceName = service; + + searchManager = new UserSearchManager(SparkManager.getConnection()); + setLayout(new GridBagLayout()); + + // Load searchForm + + try { + searchForm = searchManager.getSearchForm(service); + } + catch (XMPPException e) { + Log.error("Unable to load search services.", e); + JOptionPane.showMessageDialog(SparkManager.getMainWindow(), "Unable to contact search service.", "Search Service Not Available", JOptionPane.ERROR_MESSAGE); + return; + } + + searchManager = new UserSearchManager(SparkManager.getConnection()); + questionForm = new DataFormUI(searchForm); + questionForm.setBorder(BorderFactory.createTitledBorder("Search Form")); + + add(questionForm, new GridBagConstraints(0, 0, 3, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.BOTH, new Insets(5, 5, 5, 5), 0, 0)); + + // Add User DataForm + final JButton searchButton = new JButton(); + ResourceUtils.resButton(searchButton, "&Search"); + add(searchButton, new GridBagConstraints(0, 1, 3, 1, 1.0, 0.0, GridBagConstraints.EAST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + + searchButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + performSearch(); + } + }); + + KeyStroke enter = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0); + String enterString = org.jivesoftware.spark.util.StringUtils.keyStroke2String(enter); + + // Handle Left Arrow + getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(enterString), "enter"); + + getActionMap().put("enter", new AbstractAction("enter") { + public void actionPerformed(ActionEvent evt) { + performSearch(); + } + }); + + // Add searchResults + searchResults = new UserSearchResults(); + searchResults.setBorder(BorderFactory.createTitledBorder("Search Results")); + add(searchResults, new GridBagConstraints(0, 2, 2, 1, 1.0, 1.0, GridBagConstraints.WEST, GridBagConstraints.BOTH, new Insets(5, 5, 5, 5), 0, 0)); + } + + public DataFormUI getQuestionForm() { + return questionForm; + } + + public Form getSearchForm() { + return searchForm; + } + + /** + * Starts a search based on the Answered form. + */ + public void performSearch() { + searchResults.clearTable(); + + Form answerForm = questionForm.getFilledForm(); + try { + ReportedData data = searchManager.getSearchResults(answerForm, serviceName); + if (data != null) { + searchResults.showUsersFound(data); + } + } + catch (XMPPException e1) { + Log.error("Unable to load search service.", e1); + JOptionPane.showMessageDialog(searchResults, "No results found!", "No Results", JOptionPane.ERROR_MESSAGE); + } + + searchResults.invalidate(); + searchResults.validate(); + searchResults.repaint(); + } +} diff --git a/src/java/org/jivesoftware/sparkimpl/search/users/UserSearchForm.java b/src/java/org/jivesoftware/sparkimpl/search/users/UserSearchForm.java new file mode 100644 index 00000000..e69ecbf3 --- /dev/null +++ b/src/java/org/jivesoftware/sparkimpl/search/users/UserSearchForm.java @@ -0,0 +1,226 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.sparkimpl.search.users; + +import org.jivesoftware.resource.SparkRes; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smackx.Form; +import org.jivesoftware.smackx.search.UserSearchManager; +import org.jivesoftware.spark.SparkManager; +import org.jivesoftware.spark.component.TitlePanel; +import org.jivesoftware.spark.ui.DataFormUI; +import org.jivesoftware.spark.util.ModelUtil; +import org.jivesoftware.spark.util.ResourceUtils; +import org.jivesoftware.spark.util.SwingWorker; +import org.jivesoftware.spark.util.log.Log; + +import javax.swing.JButton; +import javax.swing.JComboBox; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; + +import java.awt.CardLayout; +import java.awt.Component; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +/** + * UserSearchForm is used to do explicit searching of users using the JEP 55 Search Service. + */ +public class UserSearchForm extends JPanel { + private JComboBox servicesBox; + private UserSearchManager searchManager; + + private Collection searchServices; + + private CardLayout cardLayout = new CardLayout(); + private JPanel cardPanel = new JPanel(); + + private TitlePanel titlePanel; + + private Map serviceMap = new HashMap(); + + /** + * Initializes the UserSearchForm with all available search services. + * + * @param searchServices a Collection of all search services found. + */ + public UserSearchForm(Collection searchServices) { + setLayout(new GridBagLayout()); + + cardPanel.setLayout(cardLayout); + + this.searchServices = searchServices; + + searchManager = new UserSearchManager(SparkManager.getConnection()); + + addSearchServices(); + + showService(getSearchService()); + } + + private void addSearchServices() { + // Populate with Search Services + servicesBox = new JComboBox(); + + Iterator services = searchServices.iterator(); + while (services.hasNext()) { + String service = (String)services.next(); + servicesBox.addItem(service); + } + + if (servicesBox.getItemCount() > 0) { + servicesBox.setSelectedIndex(0); + } + + + titlePanel = new TitlePanel("", "", SparkRes.getImageIcon(SparkRes.BLANK_IMAGE), true); + add(titlePanel, new GridBagConstraints(0, 0, 3, 1, 1.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0)); + + // Add Search Service ComboBox + final JLabel serviceLabel = new JLabel("Search Service:"); + add(serviceLabel, new GridBagConstraints(0, 1, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + add(servicesBox, new GridBagConstraints(1, 1, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 150, 0)); + + final JButton addService = new JButton(); + ResourceUtils.resButton(addService, "&Add Service"); + add(addService, new GridBagConstraints(2, 1, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + addService.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent actionEvent) { + final String serviceName = JOptionPane.showInputDialog(getRootPane(), "Name of search service?", "Add Search Service", JOptionPane.QUESTION_MESSAGE); + if (ModelUtil.hasLength(serviceName)) { + + SwingWorker findServiceThread = new SwingWorker() { + Form newForm; + + public Object construct() { + try { + newForm = searchManager.getSearchForm(serviceName); + } + catch (XMPPException e) { + } + return newForm; + } + + public void finished() { + if (newForm == null) { + JOptionPane.showMessageDialog(getGUI(), "Unable to contact search service.", "Search Service Not Available", JOptionPane.ERROR_MESSAGE); + return; + } + else { + servicesBox.addItem(serviceName); + servicesBox.setSelectedItem(serviceName); + } + } + + }; + findServiceThread.start(); + } + } + }); + + servicesBox.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent actionEvent) { + SwingWorker worker = new SwingWorker() { + public Object construct() { + try { + Thread.sleep(50); + } + catch (Exception e) { + Log.error("Problem sleeping thread.", e); + } + return "ok"; + } + + public void finished() { + showService(getSearchService()); + } + }; + worker.start(); + } + }); + + add(cardPanel, new GridBagConstraints(0, 3, 3, 1, 1.0, 1.0, GridBagConstraints.WEST, GridBagConstraints.BOTH, new Insets(5, 5, 5, 5), 0, 0)); + + + } + + /** + * Displays the specified search service. + * + * @param service the search service to display. + */ + public void showService(String service) { + if (serviceMap.containsKey(service)) { + cardLayout.show(cardPanel, service); + } + else { + // Create new Form + SearchForm searchForm = new SearchForm(service); + cardPanel.add(searchForm, service); + serviceMap.put(service, searchForm); + cardLayout.show(cardPanel, service); + } + + SearchForm searchForm = (SearchForm)serviceMap.get(service); + Form form = searchForm.getSearchForm(); + String title = form.getTitle(); + String description = form.getInstructions(); + titlePanel.setTitle(title); + titlePanel.setDescription(description); + } + + + /** + * Returns the selected search service. + * + * @return the selected search service. + */ + public String getSearchService() { + return (String)servicesBox.getSelectedItem(); + } + + /** + * Return the QuestionForm retrieved by the search service. + * + * @return the QuestionForm retrieved by the search service. + */ + public DataFormUI getQuestionForm() { + SearchForm searchForm = (SearchForm)serviceMap.get(getSearchService()); + return searchForm.getQuestionForm(); + } + + /** + * Performs a search on the specified search service. + */ + public void performSearch() { + SearchForm searchForm = (SearchForm)serviceMap.get(getSearchService()); + searchForm.performSearch(); + } + + /** + * Returns the UI that represent the UserSearchForm + * + * @return the UserSearchForm + */ + public Component getGUI() { + return this; + } + +} diff --git a/src/java/org/jivesoftware/sparkimpl/search/users/UserSearchResults.java b/src/java/org/jivesoftware/sparkimpl/search/users/UserSearchResults.java new file mode 100644 index 00000000..3ab1ad33 --- /dev/null +++ b/src/java/org/jivesoftware/sparkimpl/search/users/UserSearchResults.java @@ -0,0 +1,256 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.sparkimpl.search.users; + +import org.jivesoftware.resource.SparkRes; +import org.jivesoftware.smack.util.StringUtils; +import org.jivesoftware.smackx.ReportedData; +import org.jivesoftware.spark.ChatManager; +import org.jivesoftware.spark.SparkManager; +import org.jivesoftware.spark.component.Table; +import org.jivesoftware.spark.ui.ChatContainer; +import org.jivesoftware.spark.ui.ChatRoom; +import org.jivesoftware.spark.ui.RosterDialog; +import org.jivesoftware.spark.util.ModelUtil; +import org.jivesoftware.spark.util.log.Log; +import org.jivesoftware.sparkimpl.profile.VCardManager; + +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.JMenuItem; +import javax.swing.JPanel; +import javax.swing.JPopupMenu; +import javax.swing.JScrollPane; +import javax.swing.ListSelectionModel; +import javax.swing.table.TableColumn; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.event.ActionEvent; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * UserSearchResults displays the UI for all users found using the JEP-055 search service. + */ +public class UserSearchResults extends JPanel { + private UsersInfoTable resultsTable; + + /** + * Intiliaze the Search Service Results UI. + */ + public UserSearchResults() { + setLayout(new BorderLayout()); + } + + /** + * Populate the SearchResults UI table with the ReportedData returned from the search service. + * + * @param data the ReportedData returned by the Search Service. + */ + public void showUsersFound(ReportedData data) { + List columnList = new ArrayList(); + Iterator columns = data.getColumns(); + while (columns.hasNext()) { + ReportedData.Column column = (ReportedData.Column)columns.next(); + String label = column.getLabel(); + columnList.add(label); + } + + if (resultsTable == null) { + resultsTable = new UsersInfoTable((String[])columnList.toArray(new String[columnList.size()])); + + final JScrollPane scrollPane = new JScrollPane(resultsTable); + scrollPane.getViewport().setBackground(Color.white); + + add(scrollPane, BorderLayout.CENTER); + + resultsTable.addMouseListener(new MouseAdapter() { + public void mouseClicked(MouseEvent e) { + if (e.getClickCount() == 2) { + int row = resultsTable.getSelectedRow(); + openChatRoom(row); + } + } + + public void mouseReleased(MouseEvent e) { + if (!e.isPopupTrigger()) { + return; + } + // Get agent + final int row = resultsTable.rowAtPoint(e.getPoint()); + + final JPopupMenu menu = new JPopupMenu(); + + Action addContactAction = new AbstractAction() { + public void actionPerformed(ActionEvent e) { + RosterDialog dialog = new RosterDialog(); + String jid = (String)resultsTable.getValueAt(row, 0); + + TableColumn column = null; + try { + column = resultsTable.getColumn("Username"); + } + catch (Exception ex) { + try { + column = resultsTable.getColumn("nick"); + } + catch (Exception e1) { + } + } + if (column != null) { + int col = column.getModelIndex(); + String nickname = (String)resultsTable.getValueAt(row, col); + if (!ModelUtil.hasLength(nickname)) { + nickname = StringUtils.parseName(jid); + } + dialog.setDefaultNickname(nickname); + } + + dialog.setDefaultJID(jid); + dialog.showRosterDialog(); + } + }; + + Action chatAction = new AbstractAction() { + public void actionPerformed(ActionEvent e) { + openChatRoom(row); + } + }; + + Action profileAction = new AbstractAction() { + public void actionPerformed(ActionEvent e) { + VCardManager vcardSupport = SparkManager.getVCardManager(); + String jid = (String)resultsTable.getValueAt(row, 0); + vcardSupport.viewProfile(jid, resultsTable); + } + }; + + final JMenuItem addAsContact = new JMenuItem(addContactAction); + addContactAction.putValue(Action.SMALL_ICON, SparkRes.getImageIcon(SparkRes.SMALL_ADD_IMAGE)); + addContactAction.putValue(Action.NAME, "Add as Contact"); + menu.add(addAsContact); + + final JMenuItem chatMenu = new JMenuItem(chatAction); + chatAction.putValue(Action.SMALL_ICON, SparkRes.getImageIcon(SparkRes.SMALL_MESSAGE_IMAGE)); + chatAction.putValue(Action.NAME, "Chat"); + menu.add(chatMenu); + + final JMenuItem viewProfileMenu = new JMenuItem(profileAction); + profileAction.putValue(Action.SMALL_ICON, SparkRes.getImageIcon(SparkRes.SMALL_PROFILE_IMAGE)); + profileAction.putValue(Action.NAME, "View Profile"); + menu.add(viewProfileMenu); + + + menu.show(resultsTable, e.getX(), e.getY()); + } + + public void mousePressed(MouseEvent e) { + } + }); + } + else { + resultsTable.clearTable(); + } + // Populate with answers + Iterator rows = data.getRows(); + List modelList; + while (rows.hasNext()) { + modelList = new ArrayList(); + ReportedData.Row row = (ReportedData.Row)rows.next(); + for (int i = 0; i < resultsTable.getColumnCount(); i++) { + String tableValue = (String)resultsTable.getTableHeader().getColumnModel().getColumn(i).getHeaderValue(); + Iterator columnIterator = data.getColumns(); + while (columnIterator.hasNext()) { + ReportedData.Column column = (ReportedData.Column)columnIterator.next(); + if (column.getLabel().equals(tableValue)) { + tableValue = column.getVariable(); + break; + } + } + + String modelValue = getFirstValue(row, tableValue); + modelList.add(modelValue); + } + + resultsTable.getTableModel().addRow(modelList.toArray()); + + } + } + + + private final class UsersInfoTable extends Table { + UsersInfoTable(String[] headers) { + super(headers); + getColumnModel().setColumnMargin(0); + setSelectionBackground(Table.SELECTION_COLOR); + setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + setRowSelectionAllowed(true); + } + } + + /** + * Returns the first value found in the ReportedData.Row. + * + * @param row the ReportedData.Row. + * @param key the specified key in the ReportedData.Row. + * @return the first value found in the ReportedData.Row + */ + public String getFirstValue(ReportedData.Row row, String key) { + try { + final Iterator rows = row.getValues(key); + while (rows.hasNext()) { + return (String)rows.next(); + } + } + catch (Exception e) { + Log.error("Error retrieving the first value.", e); + } + return null; + } + + private void openChatRoom(int row) { + String jid = (String)resultsTable.getValueAt(row, 0); + String nickname = StringUtils.parseName(jid); + + TableColumn column = null; + try { + column = resultsTable.getColumn("nick"); + int col = column.getModelIndex(); + nickname = (String)resultsTable.getValueAt(row, col); + if (!ModelUtil.hasLength(nickname)) { + nickname = StringUtils.parseName(jid); + } + } + catch (Exception e1) { + // Ignore e1 + } + + ChatManager chatManager = SparkManager.getChatManager(); + ChatRoom chatRoom = chatManager.createChatRoom(jid, nickname, nickname); + + ChatContainer chatRooms = chatManager.getChatContainer(); + chatRooms.activateChatRoom(chatRoom); + } + + /** + * Clears the Search Results Table. + */ + public void clearTable() { + if (resultsTable != null) { + resultsTable.clearTable(); + } + } + +} diff --git a/src/java/org/jivesoftware/sparkimpl/search/users/UserSearchService.java b/src/java/org/jivesoftware/sparkimpl/search/users/UserSearchService.java new file mode 100644 index 00000000..928663fd --- /dev/null +++ b/src/java/org/jivesoftware/sparkimpl/search/users/UserSearchService.java @@ -0,0 +1,127 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.sparkimpl.search.users; + +import org.jivesoftware.resource.SparkRes; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smackx.search.UserSearchManager; +import org.jivesoftware.spark.SparkManager; +import org.jivesoftware.spark.search.Searchable; +import org.jivesoftware.spark.ui.DataFormUI; +import org.jivesoftware.spark.util.GraphicUtils; +import org.jivesoftware.spark.util.log.Log; + +import javax.swing.Icon; +import javax.swing.JDialog; +import javax.swing.JFrame; +import javax.swing.JOptionPane; +import javax.swing.JTextField; + +import java.util.Collection; + +public class UserSearchService implements Searchable { + private Collection searchServices; + + public UserSearchService() { + // On initialization, find search service. + loadSearchServices(); + } + + public void search(String query) { + if (searchServices == null) { + JOptionPane.showMessageDialog(SparkManager.getMainWindow(), "Unable to locate a search service.", "Search Service Not Available", JOptionPane.ERROR_MESSAGE); + return; + } + + UserSearchForm searchForm = null; + DataFormUI dataFormUI = null; + try { + searchForm = new UserSearchForm(searchServices); + dataFormUI = searchForm.getQuestionForm(); + } + catch (Exception e) { + JOptionPane.showMessageDialog(SparkManager.getMainWindow(), "Unable to contact search service.", "Search Service Not Available", JOptionPane.ERROR_MESSAGE); + return; + } + + JTextField textField = (JTextField)dataFormUI.getComponent("search"); + if (textField != null) { + textField.setText(query); + } + else { + textField = (JTextField)dataFormUI.getComponent("last"); + if (textField != null) { + textField.setText(query); + } + } + + if (textField == null) { + textField = (JTextField)dataFormUI.getComponent("userName"); + if (textField != null) { + textField.setText(query); + } + } + + if (textField != null) { + searchForm.performSearch(); + } + + JFrame frame = new JFrame(); + frame.setIconImage(SparkRes.getImageIcon(SparkRes.VIEW_IMAGE).getImage()); + final JDialog dialog = new JDialog(frame, "User Search", false); + dialog.getContentPane().add(searchForm); + dialog.pack(); + dialog.setSize(500, 500); + + GraphicUtils.centerWindowOnScreen(dialog); + dialog.setVisible(true); + } + + /** + * Load all Search Services. + */ + private void loadSearchServices() { + UserSearchManager userSearchManager = new UserSearchManager(SparkManager.getConnection()); + try { + searchServices = userSearchManager.getSearchServices(); + } + catch (XMPPException e) { + Log.error("Unable to load search services.", e); + } + } + + /** + * Return the Search Services discovered by the client. + * + * @return the discovered search services. + */ + public Collection getSearchServices() { + return searchServices; + } + + public String getToolTip() { + return "Search for other people on the server."; + } + + public String getDefaultText() { + return "Search for other people."; + } + + public String getName() { + return "User Search"; + } + + public Icon getIcon() { + return SparkRes.getImageIcon(SparkRes.SEARCH_USER_16x16); + } + + +} diff --git a/src/java/org/jivesoftware/sparkimpl/settings/JiveInfo.java b/src/java/org/jivesoftware/sparkimpl/settings/JiveInfo.java new file mode 100644 index 00000000..85eb7eaf --- /dev/null +++ b/src/java/org/jivesoftware/sparkimpl/settings/JiveInfo.java @@ -0,0 +1,26 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.sparkimpl.settings; + +public class JiveInfo { + + private JiveInfo() { + + } + + public static String getVersion() { + return "1.1.9.2"; + } + + public static String getOS() { + return System.getProperty("os.name"); + } +} diff --git a/src/java/org/jivesoftware/sparkimpl/settings/SettingsData.java b/src/java/org/jivesoftware/sparkimpl/settings/SettingsData.java new file mode 100644 index 00000000..8b9059a4 --- /dev/null +++ b/src/java/org/jivesoftware/sparkimpl/settings/SettingsData.java @@ -0,0 +1,55 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.sparkimpl.settings; + +import org.jivesoftware.smackx.packet.PrivateData; + +import java.util.Iterator; +import java.util.Map; + +public class SettingsData implements PrivateData { + private Map settingsMap; + + + public SettingsData(Map map) { + settingsMap = map; + } + + public Map getMap() { + return settingsMap; + } + + public String getElementName() { + return "personal_settings"; + } + + public String getNamespace() { + return "jive:user:settings"; + } + + public String toXML() { + StringBuffer buf = new StringBuffer(); + buf.append("<" + getElementName() + " xmlns=\"" + getNamespace() + "\">"); + String key; + for (Iterator iter = settingsMap.keySet().iterator(); iter.hasNext(); buf.append("")) { + key = (String)iter.next(); + String value = (String)settingsMap.get(key); + buf.append(""); + buf.append("<" + key + ">"); + buf.append(value); + } + + buf.append(""); + return buf.toString(); + } + + +} \ No newline at end of file diff --git a/src/java/org/jivesoftware/sparkimpl/settings/SettingsDataProvider.java b/src/java/org/jivesoftware/sparkimpl/settings/SettingsDataProvider.java new file mode 100644 index 00000000..07f37f35 --- /dev/null +++ b/src/java/org/jivesoftware/sparkimpl/settings/SettingsDataProvider.java @@ -0,0 +1,41 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.sparkimpl.settings; + +import org.jivesoftware.smackx.packet.PrivateData; +import org.jivesoftware.smackx.provider.PrivateDataProvider; +import org.xmlpull.v1.XmlPullParser; + +import java.util.HashMap; +import java.util.Map; + +public class SettingsDataProvider implements PrivateDataProvider { + + public SettingsDataProvider() { + } + + public PrivateData parsePrivateData(XmlPullParser parser) throws Exception { + Map map = new HashMap(); + int eventType = parser.getEventType(); + if (eventType == 2) ; + eventType = parser.nextTag(); + for (String text = parser.getName(); text.equals("entry"); text = parser.getName()) { + eventType = parser.nextTag(); + String name = parser.getName(); + text = parser.nextText(); + map.put(name, text); + eventType = parser.nextTag(); + eventType = parser.nextTag(); + } + + return new SettingsData(map); + } +} \ No newline at end of file diff --git a/src/java/org/jivesoftware/sparkimpl/settings/UserSettings.java b/src/java/org/jivesoftware/sparkimpl/settings/UserSettings.java new file mode 100644 index 00000000..f891b776 --- /dev/null +++ b/src/java/org/jivesoftware/sparkimpl/settings/UserSettings.java @@ -0,0 +1,94 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.sparkimpl.settings; + +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smackx.PrivateDataManager; +import org.jivesoftware.spark.SparkManager; +import org.jivesoftware.spark.util.ModelUtil; +import org.jivesoftware.spark.util.log.Log; + +import java.util.Map; + +public class UserSettings { + public static final String NAMESPACE = "jive:user:settings"; + public static final String ELEMENT_NAME = "personal_settings"; + private PrivateDataManager privateDataManager; + private SettingsData settingsData; + private static UserSettings singleton; + private static final Object LOCK = new Object(); + + public static UserSettings getInstance() { + synchronized (LOCK) { + if (null == singleton) { + UserSettings controller = new UserSettings(); + singleton = controller; + UserSettings usersettings = controller; + return usersettings; + } + } + return singleton; + } + + private UserSettings() { + privateDataManager = new PrivateDataManager(SparkManager.getConnection()); + PrivateDataManager.addPrivateDataProvider("personal_settings", "jive:user:settings", new SettingsDataProvider()); + + try { + settingsData = (SettingsData)privateDataManager.getPrivateData("personal_settings", "jive:user:settings"); + } + catch (XMPPException e) { + Log.error("Error in User Settings", e); + } + } + + public Map getSettings() { + try { + Map map = settingsData.getMap(); + return map; + } + catch (Exception ex) { + Log.error("Error in User Settings.", ex); + } + return null; + } + + public void setProperty(String name, String value) { + getSettings().put(name, value); + } + + public void setProperty(String name, boolean showtime) { + getSettings().put(name, Boolean.toString(showtime)); + } + + public void setProperty(String name, int value) { + getSettings().put(name, Integer.toString(value)); + } + + public String getProperty(String name) { + return (String)getSettings().get(name); + } + + public String getEmptyPropertyIfNull(String name) { + return ModelUtil.nullifyIfEmpty((String)getSettings().get(name)); + } + + public void save() { + try { + privateDataManager.setPrivateData(settingsData); + } + catch (XMPPException e) { + Log.error("Error in User Settings.", e); + } + } + + +} \ No newline at end of file diff --git a/src/java/org/jivesoftware/sparkimpl/settings/local/LocalPreference.java b/src/java/org/jivesoftware/sparkimpl/settings/local/LocalPreference.java new file mode 100644 index 00000000..11384c28 --- /dev/null +++ b/src/java/org/jivesoftware/sparkimpl/settings/local/LocalPreference.java @@ -0,0 +1,113 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.sparkimpl.settings.local; + +import org.jivesoftware.resource.SparkRes; +import org.jivesoftware.spark.preference.Preference; + +import javax.swing.Icon; +import javax.swing.JComponent; + +/** + * Represents the Local Preference inside the Preference Manager. + */ +public class LocalPreference implements Preference { + private LocalPreferencePanel panel; + private LocalPreferences preferences; + private String errorMessage = "Error"; + + /** + * Initalize and load local preference. + */ + public LocalPreference() { + preferences = SettingsManager.getLocalPreferences(); + } + + public String getTitle() { + return "Login Settings"; + } + + public String getListName() { + return "Login"; + } + + public String getTooltip() { + return "Spark Login Settings"; + } + + public Icon getIcon() { + return SparkRes.getImageIcon(SparkRes.LOGIN_KEY_IMAGE); + } + + public void load() { + preferences = SettingsManager.getLocalPreferences(); + } + + public void commit() { + getData(); + } + + public Object getData() { + preferences = SettingsManager.getLocalPreferences(); + preferences.setAutoLogin(panel.getAutoLogin()); + preferences.setTimeOut(Integer.parseInt(panel.getTimeout())); + preferences.setXmppPort(Integer.parseInt(panel.getPort())); + preferences.setSavePassword(panel.isSavePassword()); + preferences.setIdleOn(panel.isIdleOn()); + preferences.setSecondIdleTime(Integer.parseInt(panel.getIdleTime())); + preferences.setStartedHidden(panel.startInSystemTray()); + + return preferences; + } + + public String getErrorMessage() { + return errorMessage; + } + + public boolean isDataValid() { + preferences.setTimeOut(Integer.parseInt(panel.getTimeout())); + preferences.setXmppPort(Integer.parseInt(panel.getPort())); + + try { + Integer.parseInt(panel.getTimeout()); + Integer.parseInt(panel.getPort()); + Integer.parseInt(panel.getIdleTime()); + } + catch (Exception ex) { + errorMessage = "You must specify a valid timeout and port."; + return false; + } + + int timeOut = Integer.parseInt(panel.getTimeout()); + if (timeOut < 5) { + errorMessage = "The timeout must be 5 seconds or greater."; + return false; + } + + return true; + } + + public JComponent getGUI() { + panel = new LocalPreferencePanel(); + + return panel; + } + + public String getNamespace() { + return "LOGIN"; + } + + public void shutdown() { + // Commit to file. + SettingsManager.saveSettings(); + } + +} \ No newline at end of file diff --git a/src/java/org/jivesoftware/sparkimpl/settings/local/LocalPreferencePanel.java b/src/java/org/jivesoftware/sparkimpl/settings/local/LocalPreferencePanel.java new file mode 100644 index 00000000..38b5a568 --- /dev/null +++ b/src/java/org/jivesoftware/sparkimpl/settings/local/LocalPreferencePanel.java @@ -0,0 +1,285 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.sparkimpl.settings.local; + +import org.jivesoftware.spark.component.VerticalFlowLayout; +import org.jivesoftware.spark.util.ResourceUtils; + +import javax.swing.BorderFactory; +import javax.swing.JCheckBox; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextField; + +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + + +/** + * UI for editing Local Preferences. + */ +public class LocalPreferencePanel extends JPanel { + private JLabel portLabel = new JLabel(); + private JTextField portField = new JTextField(); + + private JLabel timeOutLabel = new JLabel(); + private JTextField timeOutField = new JTextField(); + + private JCheckBox autoLoginBox = new JCheckBox(); + private JCheckBox savePasswordBox = new JCheckBox(); + + private JCheckBox idleBox = new JCheckBox(); + private JLabel idleLabel = new JLabel(); + private JTextField idleField = new JTextField(); + + private JCheckBox launchOnStartupBox = new JCheckBox(); + private JCheckBox startMinimizedBox = new JCheckBox(); + + /** + * Construct Local Preference UI. + */ + public LocalPreferencePanel() { + setLayout(new VerticalFlowLayout()); + + // Load local localPref + LocalPreferences localPref = SettingsManager.getLocalPreferences(); + portField.setText(Integer.toString(localPref.getXmppPort())); + timeOutField.setText(Integer.toString(localPref.getTimeOut())); + autoLoginBox.setSelected(localPref.isAutoLogin()); + savePasswordBox.setSelected(localPref.isSavePassword()); + startMinimizedBox.setSelected(localPref.isStartedHidden()); + + savePasswordBox.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + autoLoginBox.setEnabled(savePasswordBox.isSelected()); + if (!savePasswordBox.isSelected()) { + autoLoginBox.setSelected(false); + } + } + }); + + autoLoginBox.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + if (autoLoginBox.isSelected()) { + savePasswordBox.setSelected(true); + } + } + }); + + idleBox.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + idleField.setEnabled(idleBox.isSelected()); + } + }); + + idleBox.setSelected(localPref.isIdleOn()); + idleField.setText(Integer.toString(localPref.getSecondIdleTime())); + + final JPanel inputPanel = new JPanel(); + inputPanel.setLayout(new GridBagLayout()); + inputPanel.setBorder(BorderFactory.createTitledBorder("Login Information")); + + ResourceUtils.resLabel(portLabel, portField, "&XMPP Port:"); + ResourceUtils.resLabel(timeOutLabel, timeOutField, "&Response Time Out(sec):"); + ResourceUtils.resButton(autoLoginBox, "&Auto Login"); + ResourceUtils.resButton(savePasswordBox, "&Save Password"); + ResourceUtils.resLabel(idleLabel, idleField, "&Time till Idle(minutes):"); + ResourceUtils.resButton(idleBox, "&Idle enabled"); + ResourceUtils.resButton(launchOnStartupBox, "&Launch on Startup"); + ResourceUtils.resButton(startMinimizedBox, "&Start in System Tray"); + + inputPanel.add(portLabel, new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); + inputPanel.add(portField, new GridBagConstraints(1, 0, 1, 1, 0.0, 0.0, GridBagConstraints.NORTHWEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); + inputPanel.add(timeOutLabel, new GridBagConstraints(0, 1, 1, 1, 0.0, 0.0, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 50, 0)); + inputPanel.add(timeOutField, new GridBagConstraints(1, 1, 1, 1, 1.0, 0.0, GridBagConstraints.NORTHWEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 50, 0)); + inputPanel.add(idleLabel, new GridBagConstraints(0, 2, 1, 1, 0.0, 0.0, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 50, 0)); + inputPanel.add(idleField, new GridBagConstraints(1, 2, 1, 1, 1.0, 0.0, GridBagConstraints.NORTHWEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 50, 0)); + + inputPanel.add(idleBox, new GridBagConstraints(0, 3, 2, 1, 1.0, 0.0, GridBagConstraints.NORTHWEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 50, 0)); + inputPanel.add(savePasswordBox, new GridBagConstraints(0, 4, 2, 1, 0.0, 0.0, GridBagConstraints.NORTHWEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 50, 0)); + inputPanel.add(autoLoginBox, new GridBagConstraints(0, 5, 2, 1, 0.0, 0.0, GridBagConstraints.NORTHWEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 50, 0)); + + /* + if (Spark.isWindows()) { + inputPanel.add(launchOnStartupBox, new GridBagConstraints(0, 6, 2, 1, 0.0, 0.0, GridBagConstraints.NORTHWEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 50, 0)); + checkRegistry(); + launchOnStartupBox.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + launchOnStartup(launchOnStartupBox.isSelected()); + } + }); + } + */ + + inputPanel.add(startMinimizedBox, new GridBagConstraints(0, 7, 2, 1, 0.0, 0.0, GridBagConstraints.NORTHWEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 50, 0)); + inputPanel.add(new JLabel(), new GridBagConstraints(0, 8, 2, 1, 1.0, 1.0, GridBagConstraints.NORTHWEST, GridBagConstraints.BOTH, new Insets(5, 5, 5, 5), 50, 0)); + + + add(inputPanel); + } + + /** + * Sets the XMPP port to comminucate on. + * + * @param port the XMPP port to communicate on. + */ + public void setPort(String port) { + portField.setText(port); + } + + /** + * Return the XMPP Port to communicate on. + * + * @return the XMPP Port to communicate on. + */ + public String getPort() { + return portField.getText(); + } + + /** + * Sets the XMPP Timeout(in seconds). + * + * @param timeOut the XMPP Timeout(in seconds). + */ + public void setTimeOut(String timeOut) { + timeOutField.setText(timeOut); + } + + /** + * Return the XMPP Timeout variable. + * + * @return the XMPP Timeout variable. + */ + public String getTimeout() { + return timeOutField.getText(); + } + + /** + * Sets Auto Login on and off. + * + * @param auto true if Auto Login is on. + */ + public void setAutoLogin(boolean auto) { + autoLoginBox.setSelected(auto); + } + + /** + * Return true if Auto Login is on. + * + * @return true if Auto Login is on. + */ + public boolean getAutoLogin() { + return autoLoginBox.isSelected(); + } + + /** + * Set true if the password should be encoded and saved. + * + * @param save true if the password should be encoded and saved. + */ + public void setSavePassword(boolean save) { + savePasswordBox.setSelected(save); + } + + /** + * Return true if the password should be saved. + * + * @return true if the password should be saved. + */ + public boolean isSavePassword() { + return savePasswordBox.isSelected(); + } + + /** + * Returns true if IDLE is on. + * + * @return true if IDLE is on. + */ + public boolean isIdleOn() { + return idleBox.isSelected(); + } + + /** + * Sets the IDLE on or off. + * + * @param on true if IDLE should be on. + */ + public void setIdleOn(boolean on) { + idleBox.setSelected(on); + } + + /** + * Sets the Idle Time in minutes. + * + * @param time the Idle time in minutes. + */ + public void setIdleTime(int time) { + String idleTime = Integer.toString(time); + idleField.setText(idleTime); + } + + /** + * Return the time to IDLE. + * + * @return the time to IDLE. + */ + public String getIdleTime() { + return idleField.getText(); + } + + public void startInSystemTray(boolean startInTray) { + startMinimizedBox.setSelected(startInTray); + } + + public boolean startInSystemTray() { + return startMinimizedBox.isSelected(); + } + + /* + private void checkRegistry() { + try { + RegistryKeyValues values = RegistryKey.CURRENT_USER.openSubKey("Software").openSubKey("Microsoft").openSubKey("Windows").openSubKey("CurrentVersion").openSubKey("Run").values(); + for (Iterator iterator = values.entrySet().iterator(); iterator.hasNext();) { + Map.Entry entry = (Map.Entry)iterator.next(); + String key = (String)entry.getKey(); + if (key.equals("Spark")) { + launchOnStartupBox.setSelected(true); + } + } + } + catch (Exception e) { + Log.error("Unable to retrieve registry settings.", e); + } + } + + private void launchOnStartup(boolean launch) { + try { + if (launch) { + // Add to Registery + RegistryKeyValues values = RegistryKey.CURRENT_USER.openSubKey("Software").openSubKey("Microsoft").openSubKey("Windows").openSubKey("CurrentVersion").openSubKey("Run", true).values(); + File starter = new File(Spark.getBinDirectory().getParentFile(), "Spark.exe"); + values.put("Spark", starter.getAbsolutePath()); + } + else { + // Add to Registery + RegistryKey key = RegistryKey.CURRENT_USER.openSubKey("Software").openSubKey("Microsoft").openSubKey("Windows").openSubKey("CurrentVersion").openSubKey("Run", true); + key.values().remove("Spark"); + } + } + catch (Exception e) { + Log.error("Unable to retrieve registry settings.", e); + } + } + */ +} diff --git a/src/java/org/jivesoftware/sparkimpl/settings/local/LocalPreferences.java b/src/java/org/jivesoftware/sparkimpl/settings/local/LocalPreferences.java new file mode 100644 index 00000000..c39df973 --- /dev/null +++ b/src/java/org/jivesoftware/sparkimpl/settings/local/LocalPreferences.java @@ -0,0 +1,468 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.sparkimpl.settings.local; + +import java.util.Collection; +import java.util.Date; +import java.util.HashSet; +import java.util.Set; + + +/** + * Represents the LocalPreference Model for this system. + */ +public class LocalPreferences { + private int xmppPort = 5222; + private int timeOut = 40; + private String password; + private String username; + private String server; + private boolean autoLogin; + private boolean savePassword; + private boolean idleOn = true; + private int secondIdleTime = 30; + private Set folderList = new HashSet(); + private boolean newInstall = true; + private boolean indexing; + private boolean SSL; + private boolean prompted = true; + private String downloadDir; + + private String xmppHost; + private boolean hostAndPortConfigured; + + private String resource = "Spark"; + + + // Handle proxy info + private boolean proxyEnabled; + private String protocol; + private String host; + private String port; + private String proxyUsername; + private String proxyPassword; + + // Handle Chat settings + private String defaultNickname; + + // Handle updates + private Date lastCheckForUpdates; + private boolean timeDisplayedInChat = true; + + // Start In System Tray + private boolean startedHidden; + private boolean spellCheckerDisable; + private boolean chatRoomNotificationsOff; + + private boolean hideChatHistory; + private boolean emptyGroupsShown; + + /** + * Empty Constructor. + */ + public LocalPreferences() { + } + + /** + * Returns the XMPP Port to communicate on. + * + * @return the XMPP Port to communicate on. Default is 5222. + */ + public int getXmppPort() { + return xmppPort; + } + + /** + * Sets the XMPP Port to communicate on. + * + * @param xmppPort the XMPP Port to communicate on. Default is 5222. + */ + public void setXmppPort(int xmppPort) { + this.xmppPort = xmppPort; + } + + /** + * Return the smack timeout for requests. Default is 5 seconds. + * + * @return the smack timeout for requests. + */ + public int getTimeOut() { + return timeOut; + } + + /** + * Sets the smack timeout for requests. The default is 5 seconds, but you may wish + * to increase this number for low bandwidth users. + * + * @param timeOut the smack timeout. + */ + public void setTimeOut(int timeOut) { + this.timeOut = timeOut; + } + + /** + * Returns the encoded password. + * + * @return the encoded password. + */ + public String getPassword() { + return password; + } + + /** + * Sets the encoded password. + * + * @param password the encoded password. + */ + public void setPassword(String password) { + this.password = password; + } + + /** + * Return true if the IDLE feature is on. The IDLE feature allows to monitor + * computer activity and set presence accordingly. + * + * @return true if IDLE is on. + */ + public boolean isIdleOn() { + return idleOn; + } + + /** + * Set the IDLE feature on or off. The IDLE feature allows to monitor + * computer activity and set presence accordingly. + * + * @param idleOn true to turn idle on. + */ + public void setIdleOn(boolean idleOn) { + this.idleOn = idleOn; + } + + /** + * Returns the number of minutes to set to unavailable if the computer has + * no activity. + * + * @return the number of minutes before checking for IDLE computer. + */ + public int getSecondIdleTime() { + return secondIdleTime; + } + + /** + * Set the number of minutes to set to unavailable if the computer has + * no activity. + * + * @param secondIdleTime the number of minutes. + */ + public void setSecondIdleTime(int secondIdleTime) { + this.secondIdleTime = secondIdleTime; + } + + /** + * Return true if Auto Login is on. + * + * @return true if Auto Login is on. + */ + public boolean isAutoLogin() { + return autoLogin; + } + + /** + * Turn on or off Auto Login. Auto Login allows a user to login to the system without + * inputting their signing information. + * + * @param autoLogin true if Auto Login should be on. + */ + public void setAutoLogin(boolean autoLogin) { + this.autoLogin = autoLogin; + } + + /** + * Return true if the password should be encoded and persisted. + * + * @return true if the password is encoded and persisted. + */ + public boolean isSavePassword() { + return savePassword; + } + + /** + * Set to true to encode and save password. You would use this if you + * wish to not always input ones password. + * + * @param savePassword true if the password should be saved. + */ + public void setSavePassword(boolean savePassword) { + this.savePassword = savePassword; + } + + /** + * Add an email folder for future parsing. + * + * @param folder folder to add. + */ + public void addFolder(String folder) { + folderList.add(folder); + } + + /** + * Returns a Collection of all Email folders. + * + * @return a Collection of all Email Folders. + */ + public Collection getFolderList() { + return folderList; + } + + /** + * Returns the users username. + * + * @return the username of the agent. + */ + public String getUsername() { + return username; + } + + /** + * Sets the Agents username. + * + * @param username the agents username. + */ + public void setUsername(String username) { + this.username = username; + } + + /** + * Returns the last Server accessed. + * + * @return the last Server accessed. + */ + public String getServer() { + return server; + } + + /** + * Sets the last Server accessed. + * + * @param server the last Server accessed. + */ + public void setServer(String server) { + this.server = server; + } + + /** + * Return true if this is a fresh install. + * + * @return true if a fresh install. + */ + public boolean isNewInstall() { + return newInstall; + } + + /** + * Set if this is a fresh install. + * + * @param newInstall true if this is a fresh install. + */ + public void setNewInstall(boolean newInstall) { + this.newInstall = newInstall; + } + + /** + * Returns true if indexing is turned on. + * + * @return true if indexing is turned on. + */ + public boolean isIndexing() { + return indexing; + } + + /** + * Set to true if indexing is turned on. + * + * @param indexing true if indexing is turned on. + */ + public void setIndexing(boolean indexing) { + this.indexing = indexing; + } + + /** + * Returns true to use SSL. + * + * @return true if we should connect via SSL. + */ + public boolean isSSL() { + return SSL; + } + + /** + * Sets if the agent should use SSL for connecting. + * + * @param SSL true if we should be using SSL. + */ + public void setSSL(boolean SSL) { + this.SSL = SSL; + } + + + public boolean isPrompted() { + return prompted; + } + + public void setPrompted(boolean prompted) { + this.prompted = prompted; + } + + public String getDownloadDir() { + return downloadDir; + } + + public void setDownloadDir(String downloadDir) { + this.downloadDir = downloadDir; + } + + public boolean isProxyEnabled() { + return proxyEnabled; + } + + public void setProxyEnabled(boolean proxyEnabled) { + this.proxyEnabled = proxyEnabled; + } + + public String getHost() { + return host; + } + + public void setHost(String host) { + this.host = host; + } + + public String getPort() { + return port; + } + + public void setPort(String port) { + this.port = port; + } + + public String getProxyUsername() { + return proxyUsername; + } + + public void setProxyUsername(String proxyUsername) { + this.proxyUsername = proxyUsername; + } + + public String getProxyPassword() { + return proxyPassword; + } + + public void setProxyPassword(String proxyPassword) { + this.proxyPassword = proxyPassword; + } + + public String getProtocol() { + return protocol; + } + + public void setProtocol(String protocol) { + this.protocol = protocol; + } + + public String getDefaultNickname() { + return defaultNickname; + } + + public void setDefaultNickname(String defaultNickname) { + this.defaultNickname = defaultNickname; + } + + + public Date getLastCheckForUpdates() { + return lastCheckForUpdates; + } + + public void setLastCheckForUpdates(Date lastCheckForUpdates) { + this.lastCheckForUpdates = lastCheckForUpdates; + } + + public String getXmppHost() { + return xmppHost; + } + + public void setXmppHost(String xmppHost) { + this.xmppHost = xmppHost; + } + + public boolean isHostAndPortConfigured() { + return hostAndPortConfigured; + } + + public void setHostAndPortConfigured(boolean hostAndPortConfigured) { + this.hostAndPortConfigured = hostAndPortConfigured; + } + + public String getResource() { + return resource; + } + + public void setResource(String resource) { + this.resource = resource; + } + + public boolean isStartedHidden() { + return startedHidden; + } + + public void setStartedHidden(boolean startedHidden) { + this.startedHidden = startedHidden; + } + + public boolean isTimeDisplayedInChat() { + return timeDisplayedInChat; + } + + public void setTimeDisplayedInChat(boolean timeDisplayedInChat) { + this.timeDisplayedInChat = timeDisplayedInChat; + } + + public boolean isSpellCheckerDisable() { + return spellCheckerDisable; + } + + public void setSpellCheckerDisable(boolean spellCheckerDisable) { + this.spellCheckerDisable = spellCheckerDisable; + } + + public boolean isChatRoomNotificationsOff() { + return chatRoomNotificationsOff; + } + + public void setChatRoomNotificationsOff(boolean chatRoomNotificationsOff) { + this.chatRoomNotificationsOff = chatRoomNotificationsOff; + } + + public boolean isHideChatHistory() { + return hideChatHistory; + } + + public void setHideChatHistory(boolean hideChatHistory) { + this.hideChatHistory = hideChatHistory; + } + + public boolean isEmptyGroupsShown() { + return emptyGroupsShown; + } + + public void setEmptyGroupsShown(boolean shown) { + this.emptyGroupsShown = shown; + } +} diff --git a/src/java/org/jivesoftware/sparkimpl/settings/local/SettingsManager.java b/src/java/org/jivesoftware/sparkimpl/settings/local/SettingsManager.java new file mode 100644 index 00000000..89a22ea0 --- /dev/null +++ b/src/java/org/jivesoftware/sparkimpl/settings/local/SettingsManager.java @@ -0,0 +1,108 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.sparkimpl.settings.local; + +import com.thoughtworks.xstream.XStream; +import org.jivesoftware.Spark; +import org.jivesoftware.spark.util.log.Log; + +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; + + +/** + * Responsbile for the loading and persisting of LocalSettings. + */ +public class SettingsManager { + private static LocalPreferences localPreferences; + private static XStream xstream = new XStream(); + + private SettingsManager() { + } + + static { + xstream.alias("settings", LocalPreferences.class); + } + + /** + * Returns the LocalPreferences for this agent. + * + * @return the LocalPreferences for this agent. + */ + public static LocalPreferences getLocalPreferences() { + + if (!exists() && localPreferences == null) { + localPreferences = new LocalPreferences(); + } + + if (localPreferences == null) { + // Do Initial Load from FileSystem. + File settingsFile = getSettingsFile(); + FileReader reader = null; + + try { + reader = new FileReader(settingsFile); + localPreferences = (LocalPreferences)xstream.fromXML(reader); + } + catch (Exception e) { + xstream.alias("com.jivesoftware.settings.local.LocalPreferences", LocalPreferences.class); + try { + reader = new FileReader(settingsFile); + localPreferences = (LocalPreferences)xstream.fromXML(reader); + } + catch (Exception e1) { + Log.error("Error loading LocalPreferences.", e); + localPreferences = new LocalPreferences(); + } + } + xstream.alias("settings", LocalPreferences.class); + } + + + return localPreferences; + } + + /** + * Persists the settings to the local file system. + */ + public static void saveSettings() { + try { + FileWriter writer = new FileWriter(getSettingsFile()); + xstream.toXML(localPreferences, writer); + } + catch (Exception e) { + Log.error("Error saving settings.", e); + } + } + + /** + * Return true if the settings file exists. + * + * @return true if the settings file exists.('settings.xml') + */ + public static boolean exists() { + return getSettingsFile().exists(); + } + + /** + * Returns the settings file. + * + * @return the settings file. + */ + public static File getSettingsFile() { + File file = new File(Spark.getUserHome(), "Spark"); + if (!file.exists()) { + file.mkdirs(); + } + return new File(file, "settings.xml"); + } +} diff --git a/src/java/org/jivesoftware/sparkimpl/updater/CheckUpdates.java b/src/java/org/jivesoftware/sparkimpl/updater/CheckUpdates.java new file mode 100644 index 00000000..b4076bd8 --- /dev/null +++ b/src/java/org/jivesoftware/sparkimpl/updater/CheckUpdates.java @@ -0,0 +1,546 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.sparkimpl.updater; + +import com.thoughtworks.xstream.XStream; +import org.apache.commons.httpclient.HttpClient; +import org.apache.commons.httpclient.methods.GetMethod; +import org.apache.commons.httpclient.methods.PostMethod; +import org.apache.commons.httpclient.protocol.Protocol; +import org.jivesoftware.Spark; +import org.jivesoftware.resource.SparkRes; +import org.jivesoftware.smack.PacketCollector; +import org.jivesoftware.smack.SmackConfiguration; +import org.jivesoftware.smack.XMPPConnection; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.filter.PacketIDFilter; +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.provider.ProviderManager; +import org.jivesoftware.smackx.ServiceDiscoveryManager; +import org.jivesoftware.smackx.packet.DiscoverItems; +import org.jivesoftware.spark.SparkManager; +import org.jivesoftware.spark.component.ConfirmDialog; +import org.jivesoftware.spark.component.ConfirmDialog.ConfirmListener; +import org.jivesoftware.spark.component.TitlePanel; +import org.jivesoftware.spark.util.BrowserLauncher; +import org.jivesoftware.spark.util.ByteFormat; +import org.jivesoftware.spark.util.GraphicUtils; +import org.jivesoftware.spark.util.ModelUtil; +import org.jivesoftware.spark.util.SwingWorker; +import org.jivesoftware.spark.util.log.Log; +import org.jivesoftware.sparkimpl.settings.JiveInfo; +import org.jivesoftware.sparkimpl.settings.local.LocalPreferences; +import org.jivesoftware.sparkimpl.settings.local.SettingsManager; + +import javax.swing.JEditorPane; +import javax.swing.JFrame; +import javax.swing.JOptionPane; +import javax.swing.JProgressBar; +import javax.swing.JScrollPane; +import javax.swing.text.html.HTMLEditorKit; + +import java.awt.Color; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collections; +import java.util.Date; +import java.util.Iterator; +import java.util.List; +import java.util.TimerTask; + +public class CheckUpdates { + private String mainUpdateURL; + private JProgressBar bar; + private TitlePanel titlePanel; + private boolean downloadComplete = false; + private boolean cancel = false; + public static boolean UPDATING = false; + private boolean sparkPluginInstalled; + private XStream xstream = new XStream(); + private String sizeText; + + + public CheckUpdates() { + // Set the Jabber IQ Provider for Jabber:iq:spark + ProviderManager.addIQProvider("query", "jabber:iq:spark", new SparkVersion.Provider()); + + // For simplicity, use an alias for the root xml tag + xstream.alias("Version", SparkVersion.class); + + // Specify the main update url for JiveSoftware + this.mainUpdateURL = "http://www.jivesoftware.org/updater/updater"; + + sparkPluginInstalled = isSparkPluginInstalled(SparkManager.getConnection()); + } + + public SparkVersion newBuildAvailable() { + if (!sparkPluginInstalled && !Spark.isCustomBuild()) { + // Handle Jivesoftware.org update + return isNewBuildAvailableFromJivesoftware(); + } + else if (sparkPluginInstalled) { + try { + SparkVersion serverVersion = getLatestVersion(SparkManager.getConnection()); + if (isGreater(serverVersion.getVersion(), JiveInfo.getVersion())) { + return serverVersion; + } + } + catch (XMPPException e) { + } + + } + + return null; + } + + + /** + * Returns true if there is a new build available for download. + * + * @return true if there is a new build available for download. + */ + public SparkVersion isNewBuildAvailableFromJivesoftware() { + PostMethod post = new PostMethod(mainUpdateURL); + if (Spark.isWindows()) { + post.addParameter("os", "windows"); + } + else if (Spark.isMac()) { + post.addParameter("os", "mac"); + } + else { + post.addParameter("os", "linux"); + } + + Protocol.registerProtocol("https", new Protocol("https", new EasySSLProtocolSocketFactory(), 443)); + HttpClient httpclient = new HttpClient(); + String proxyHost = System.getProperty("http.proxyHost"); + String proxyPort = System.getProperty("http.proxyPort"); + if (ModelUtil.hasLength(proxyHost) && ModelUtil.hasLength(proxyPort)) { + try { + httpclient.getHostConfiguration().setProxy(proxyHost, Integer.parseInt(proxyPort)); + } + catch (NumberFormatException e) { + Log.error(e); + } + } + try { + int result = httpclient.executeMethod(post); + if (result != 200) { + return null; + } + + + String xml = post.getResponseBodyAsString(); + + // Server Version + SparkVersion serverVersion = (SparkVersion)xstream.fromXML(xml); + if (isGreater(serverVersion.getVersion(), JiveInfo.getVersion())) { + return serverVersion; + } + } + catch (IOException e) { + Log.error(e); + } + return null; + } + + + public void downloadUpdate(final File downloadedFile, final SparkVersion version) { + final java.util.Timer timer = new java.util.Timer(); + + // Prepare HTTP post + final GetMethod post = new GetMethod(version.getDownloadURL()); + + // Get HTTP client + Protocol.registerProtocol("https", new Protocol("https", new EasySSLProtocolSocketFactory(), 443)); + final HttpClient httpclient = new HttpClient(); + String proxyHost = System.getProperty("http.proxyHost"); + String proxyPort = System.getProperty("http.proxyPort"); + if (ModelUtil.hasLength(proxyHost) && ModelUtil.hasLength(proxyPort)) { + try { + httpclient.getHostConfiguration().setProxy(proxyHost, Integer.parseInt(proxyPort)); + } + catch (NumberFormatException e) { + Log.error(e); + } + } + + // Execute request + + try { + int result = httpclient.executeMethod(post); + if (result != 200) { + return; + } + + long length = post.getResponseContentLength(); + int contentLength = (int)length; + + bar = new JProgressBar(0, contentLength); + } + catch (IOException e) { + Log.error(e); + } + + final JFrame frame = new JFrame("Downloading IM Client"); + + frame.setIconImage(SparkRes.getImageIcon(SparkRes.SMALL_MESSAGE_IMAGE).getImage()); + + titlePanel = new TitlePanel("Upgrading Client", "Version: " + version.getVersion(), SparkRes.getImageIcon(SparkRes.SEND_FILE_24x24), true); + + final Thread thread = new Thread(new Runnable() { + public void run() { + try { + InputStream stream = post.getResponseBodyAsStream(); + long size = post.getResponseContentLength(); + ByteFormat formater = new ByteFormat(); + sizeText = formater.format(size); + titlePanel.setDescription("Version: " + version.getVersion() + " \nFile Size: " + sizeText); + + + downloadedFile.getParentFile().mkdirs(); + + FileOutputStream out = new FileOutputStream(downloadedFile); + copy(stream, out); + out.close(); + + if (!cancel) { + downloadComplete = true; + + + ConfirmDialog confirm = new ConfirmDialog(); + confirm.showConfirmDialog(SparkManager.getMainWindow(), "Download Complete", + "You will need to shut down the client to \n" + + "install the new version. Would you like to do that now?", "Yes", "No", + null); + confirm.setConfirmListener(new ConfirmListener() { + public void yesOption() { + try { + if (Spark.isWindows()) { + Runtime.getRuntime().exec(downloadedFile.getAbsolutePath()); + } + else if (Spark.isMac()) { + Runtime.getRuntime().exec("open " + downloadedFile.getCanonicalPath()); + } + } + catch (IOException e) { + Log.error(e); + } + SparkManager.getMainWindow().shutdown(); + } + + public void noOption() { + + } + }); + } + + + UPDATING = false; + frame.dispose(); + } + catch (Exception ex) { + + } + finally { + timer.cancel(); + // Release current connection to the connection pool once you are done + post.releaseConnection(); + } + } + }); + + + frame.getContentPane().setLayout(new GridBagLayout()); + frame.getContentPane().add(titlePanel, new GridBagConstraints(0, 0, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); + frame.getContentPane().add(bar, new GridBagConstraints(0, 1, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); + + JEditorPane pane = new JEditorPane(); + boolean displayContentPane = version.getChangeLogURL() != null || version.getDisplayMessage() != null; + + try { + pane.setEditable(false); + if (version.getChangeLogURL() != null) { + pane.setEditorKit(new HTMLEditorKit()); + pane.setPage(version.getChangeLogURL()); + } + else if (version.getDisplayMessage() != null) { + pane.setText(version.getDisplayMessage()); + } + + if (displayContentPane) { + frame.getContentPane().add(new JScrollPane(pane), new GridBagConstraints(0, 2, 1, 1, 1.0, 1.0, GridBagConstraints.WEST, GridBagConstraints.BOTH, new Insets(5, 5, 5, 5), 0, 0)); + } + } + catch (IOException e) { + Log.error(e); + } + + frame.getContentPane().setBackground(Color.WHITE); + frame.pack(); + if (displayContentPane) { + frame.setSize(600, 400); + } + else { + frame.setSize(400, 100); + } + frame.setLocationRelativeTo(SparkManager.getMainWindow()); + GraphicUtils.centerWindowOnScreen(frame); + frame.addWindowListener(new WindowAdapter() { + public void windowClosing(WindowEvent windowEvent) { + thread.interrupt(); + cancel = true; + + UPDATING = false; + + if (!downloadComplete) { + JOptionPane.showMessageDialog(SparkManager.getMainWindow(), "Updating has been cancelled.", "Update Canceled", JOptionPane.ERROR_MESSAGE); + } + + } + }); + frame.setVisible(true); + thread.start(); + + + timer.scheduleAtFixedRate(new TimerTask() { + int seconds = 1; + + public void run() { + ByteFormat formatter = new ByteFormat(); + long value = bar.getValue(); + long average = value / seconds; + String text = formatter.format(average) + "/Sec"; + + String total = formatter.format(value); + titlePanel.setDescription("Version: " + version.getVersion() + " \nFile Size: " + sizeText + "\nTransfer Rate: " + text + "\nTotal Downloaded: " + total); + seconds++; + } + }, 1000, 1000); + } + + /** + * Common code for copy routines. By convention, the streams are + * closed in the same method in which they were opened. Thus, + * this method does not close the streams when the copying is done. + */ + private void copy(final InputStream in, final OutputStream out) { + int read = 0; + + try { + final byte[] buffer = new byte[4096]; + while (!cancel) { + int bytesRead = in.read(buffer); + if (bytesRead < 0) { + break; + } + out.write(buffer, 0, bytesRead); + read += bytesRead; + bar.setValue(read); + } + } + catch (IOException e) { + Log.error(e); + } + } + + public void checkForUpdate(boolean forced) throws Exception { + if (UPDATING) { + return; + } + + UPDATING = true; + + LocalPreferences localPreferences = SettingsManager.getLocalPreferences(); + + Date lastChecked = localPreferences.getLastCheckForUpdates(); + if (lastChecked == null) { + lastChecked = new Date(); + // This is the first invocation of Communicator + localPreferences.setLastCheckForUpdates(lastChecked); + SettingsManager.saveSettings(); + } + + // Check to see if it has been a week + Calendar calendar = Calendar.getInstance(); + calendar.setTime(new java.util.Date()); + calendar.add(Calendar.DATE, -1); + + final Date weekAgo = calendar.getTime(); + + boolean dayOrLonger = weekAgo.getTime() >= lastChecked.getTime(); + + + if (dayOrLonger || forced || sparkPluginInstalled) { + // Check version on server. + lastChecked = new Date(); + localPreferences.setLastCheckForUpdates(lastChecked); + SettingsManager.saveSettings(); + + final SparkVersion serverVersion = newBuildAvailable(); + if (serverVersion == null) { + UPDATING = false; + + if (forced) { + JOptionPane.showMessageDialog(SparkManager.getMainWindow(), "There are no updates.", "No Updates", JOptionPane.INFORMATION_MESSAGE); + } + return; + } + + // Otherwise updates are available + String downloadURL = serverVersion.getDownloadURL(); + String filename = downloadURL.substring(downloadURL.lastIndexOf("/") + 1); + + if (filename.indexOf('=') != -1) { + filename = filename.substring(filename.indexOf('=') + 1); + } + + final File downloadFile = new File(Spark.getBinDirectory(), filename); + if (downloadFile.exists()) { + downloadFile.delete(); + } + + ConfirmDialog confirm = new ConfirmDialog(); + confirm.showConfirmDialog(SparkManager.getMainWindow(), "New Version Available", + filename + " is now available.\nWould you like to install?", "Yes", "No", + null); + confirm.setDialogSize(400, 300); + confirm.setConfirmListener(new ConfirmListener() { + public void yesOption() { + SwingWorker worker = new SwingWorker() { + public Object construct() { + try { + Thread.sleep(50); + } + catch (InterruptedException e) { + Log.error(e); + } + return "ok"; + } + + public void finished() { + if (Spark.isWindows()) { + downloadUpdate(downloadFile, serverVersion); + } + else { + // Launch browser to download page. + try { + if (sparkPluginInstalled) { + BrowserLauncher.openURL(serverVersion.getDownloadURL()); + } + else { + BrowserLauncher.openURL("http://www.jivesoftware.org/downloads.jsp#spark"); + } + } + + catch (IOException e) { + Log.error(e); + } + UPDATING = false; + } + } + + }; + worker.start(); + } + + public void noOption() { + UPDATING = false; + } + }); + } + else { + UPDATING = false; + } + + } + + + /** + * Returns true if the first version number is greater than the second. + * + * @param version1 the first version number. + * @param version2 the second version number. + * @return returns true if the first version is greater than the second. + */ + public boolean isGreater(String version1, String version2) { + List list = new ArrayList(); + list.add(version1); + list.add(version2); + + Collections.sort(list); + + if (version1.equals(version2)) { + return false; + } + + String topVersion = (String)list.get(1); + return topVersion.equals(version1); + } + + public static SparkVersion getLatestVersion(XMPPConnection connection) throws XMPPException { + SparkVersion request = new SparkVersion(); + request.setType(IQ.Type.GET); + request.setTo("updater." + connection.getServiceName()); + + PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(request.getPacketID())); + connection.sendPacket(request); + + + SparkVersion response = (SparkVersion)collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); + + // Cancel the collector. + collector.cancel(); + if (response == null) { + throw new XMPPException("No response from server."); + } + if (response.getError() != null) { + throw new XMPPException(response.getError()); + } + return response; + } + + public static boolean isSparkPluginInstalled(XMPPConnection con) { + if (!con.isConnected()) { + return false; + } + + ServiceDiscoveryManager disco = ServiceDiscoveryManager.getInstanceFor(con); + try { + DiscoverItems items = disco.discoverItems(con.getServiceName()); + Iterator iter = items.getItems(); + while (iter.hasNext()) { + DiscoverItems.Item item = (DiscoverItems.Item)iter.next(); + if ("Spark Updater".equals(item.getName())) { + return true; + } + } + } + catch (XMPPException e) { + Log.error(e); + } + + return false; + + } + + +} diff --git a/src/java/org/jivesoftware/sparkimpl/updater/EasySSLProtocolSocketFactory.java b/src/java/org/jivesoftware/sparkimpl/updater/EasySSLProtocolSocketFactory.java new file mode 100644 index 00000000..e82528d0 --- /dev/null +++ b/src/java/org/jivesoftware/sparkimpl/updater/EasySSLProtocolSocketFactory.java @@ -0,0 +1,233 @@ +/** + * $RCSfile: ,v $ + * $Revision: $ + * $Date: $ + * + * Copyright (C) 1999-2005 Jive Software. All rights reserved. + * + * This software is the proprietary information of Jive Software. + * Use is subject to license terms. + */ + +package org.jivesoftware.sparkimpl.updater; +/* + * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//httpclient/src/contrib/org/apache/commons/httpclient/contrib/ssl/EasySSLProtocolSocketFactory.java,v 1.7 2004/06/11 19:26:27 olegk Exp $ + * $Revision$ + * $Date$ + * + * ==================================================================== + * + * Copyright 2002-2004 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ + + +import com.sun.net.ssl.SSLContext; +import com.sun.net.ssl.TrustManager; +import org.apache.commons.httpclient.ConnectTimeoutException; +import org.apache.commons.httpclient.HttpClientError; +import org.apache.commons.httpclient.params.HttpConnectionParams; +import org.apache.commons.httpclient.protocol.ControllerThreadSocketFactory; +import org.apache.commons.httpclient.protocol.SecureProtocolSocketFactory; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.Socket; +import java.net.UnknownHostException; + +/** + *

        + * EasySSLProtocolSocketFactory can be used to creats SSL {@link Socket}s + * that accept self-signed certificates. + *

        + *

        + * This socket factory SHOULD NOT be used for productive systems + * due to security reasons, unless it is a concious decision and + * you are perfectly aware of security implications of accepting + * self-signed certificates + *

        + *

        + *

        + * Example of using custom protocol socket factory for a specific host: + *

        + *     Protocol easyhttps = new Protocol("https", new EasySSLProtocolSocketFactory(), 443);
        + * 

        + * HttpClient client = new HttpClient(); + * client.getHostConfiguration().setHost("localhost", 443, easyhttps); + * // use relative url only + * GetMethod httpget = new GetMethod("/"); + * client.executeMethod(httpget); + *

        + *

        + *

        + * Example of using custom protocol socket factory per default instead of the standard one: + *

        + *     Protocol easyhttps = new Protocol("https", new EasySSLProtocolSocketFactory(), 443);
        + *     Protocol.registerProtocol("https", easyhttps);
        + * 

        + * HttpClient client = new HttpClient(); + * GetMethod httpget = new GetMethod("https://localhost/"); + * client.executeMethod(httpget); + *

        + *

        + * + * @author Oleg Kalnichevski + *

        + *

        + * DISCLAIMER: HttpClient developers DO NOT actively support this component. + * The component is provided as a reference material, which may be inappropriate + * for use without additional customization. + *

        + */ + +public class EasySSLProtocolSocketFactory implements SecureProtocolSocketFactory { + + /** + * Log object for this class. + */ + private static final Log LOG = LogFactory.getLog(EasySSLProtocolSocketFactory.class); + + private SSLContext sslcontext = null; + + /** + * Constructor for EasySSLProtocolSocketFactory. + */ + public EasySSLProtocolSocketFactory() { + super(); + } + + private static SSLContext createEasySSLContext() { + try { + SSLContext context = SSLContext.getInstance("SSL"); + context.init( + null, + new TrustManager[]{new EasyX509TrustManager(null)}, + null); + return context; + } + catch (Exception e) { + LOG.error(e.getMessage(), e); + throw new HttpClientError(e.toString()); + } + } + + private SSLContext getSSLContext() { + if (this.sslcontext == null) { + this.sslcontext = createEasySSLContext(); + } + return this.sslcontext; + } + + /** + * @see SecureProtocolSocketFactory#createSocket(java.lang.String,int,java.net.InetAddress,int) + */ + public Socket createSocket( + String host, + int port, + InetAddress clientHost, + int clientPort) + throws IOException, UnknownHostException { + + return getSSLContext().getSocketFactory().createSocket( + host, + port, + clientHost, + clientPort + ); + } + + /** + * Attempts to get a new socket connection to the given host within the given time limit. + *

        + * To circumvent the limitations of older JREs that do not support connect timeout a + * controller thread is executed. The controller thread attempts to create a new socket + * within the given limit of time. If socket constructor does not return until the + * timeout expires, the controller terminates and throws an {@link ConnectTimeoutException} + *

        + * + * @param host the host name/IP + * @param port the port on the host + * @param params {@link HttpConnectionParams Http connection parameters} + * @return Socket a new socket + * @throws IOException if an I/O error occurs while creating the socket + * @throws UnknownHostException if the IP address of the host cannot be + * determined + */ + public Socket createSocket( + final String host, + final int port, + final InetAddress localAddress, + final int localPort, + final HttpConnectionParams params + ) throws IOException, UnknownHostException, ConnectTimeoutException { + if (params == null) { + throw new IllegalArgumentException("Parameters may not be null"); + } + int timeout = params.getConnectionTimeout(); + if (timeout == 0) { + return createSocket(host, port, localAddress, localPort); + } + else { + // To be eventually deprecated when migrated to Java 1.4 or above + return ControllerThreadSocketFactory.createSocket( + this, host, port, localAddress, localPort, timeout); + } + } + + /** + * @see SecureProtocolSocketFactory#createSocket(java.lang.String,int) + */ + public Socket createSocket(String host, int port) + throws IOException, UnknownHostException { + return getSSLContext().getSocketFactory().createSocket( + host, + port + ); + } + + /** + * @see SecureProtocolSocketFactory#createSocket(java.net.Socket,java.lang.String,int,boolean) + */ + public Socket createSocket( + Socket socket, + String host, + int port, + boolean autoClose) + throws IOException, UnknownHostException { + return getSSLContext().getSocketFactory().createSocket( + socket, + host, + port, + autoClose + ); + } + + public boolean equals(Object obj) { + return ((obj != null) && obj.getClass().equals(EasySSLProtocolSocketFactory.class)); + } + + public int hashCode() { + return EasySSLProtocolSocketFactory.class.hashCode(); + } + +} diff --git a/src/java/org/jivesoftware/sparkimpl/updater/EasyX509TrustManager.java b/src/java/org/jivesoftware/sparkimpl/updater/EasyX509TrustManager.java new file mode 100644 index 00000000..48f0880a --- /dev/null +++ b/src/java/org/jivesoftware/sparkimpl/updater/EasyX509TrustManager.java @@ -0,0 +1,134 @@ +/** + * $RCSfile: ,v $ + * $Revision: $ + * $Date: $ + * + * Copyright (C) 1999-2005 Jive Software. All rights reserved. + * + * This software is the proprietary information of Jive Software. + * Use is subject to license terms. + */ + +package org.jivesoftware.sparkimpl.updater; +/* + * ==================================================================== + * + * Copyright 2002-2004 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ + + +import com.sun.net.ssl.TrustManager; +import com.sun.net.ssl.TrustManagerFactory; +import com.sun.net.ssl.X509TrustManager; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; + +/** + *

        + * EasyX509TrustManager unlike default {@link X509TrustManager} accepts + * self-signed certificates. + *

        + *

        + * This trust manager SHOULD NOT be used for productive systems + * due to security reasons, unless it is a concious decision and + * you are perfectly aware of security implications of accepting + * self-signed certificates + *

        + * + * @author Adrian Sutton + * @author Oleg Kalnichevski + *

        + *

        + * DISCLAIMER: HttpClient developers DO NOT actively support this component. + * The component is provided as a reference material, which may be inappropriate + * for use without additional customization. + *

        + */ + +public class EasyX509TrustManager implements X509TrustManager { + private X509TrustManager standardTrustManager = null; + + /** + * Log object for this class. + */ + private static final Log LOG = LogFactory.getLog(EasyX509TrustManager.class); + + /** + * Constructor for EasyX509TrustManager. + */ + public EasyX509TrustManager(KeyStore keystore) throws NoSuchAlgorithmException, KeyStoreException { + super(); + TrustManagerFactory factory = TrustManagerFactory.getInstance("SunX509"); + factory.init(keystore); + TrustManager[] trustmanagers = factory.getTrustManagers(); + if (trustmanagers.length == 0) { + throw new NoSuchAlgorithmException("SunX509 trust manager not supported"); + } + this.standardTrustManager = (X509TrustManager)trustmanagers[0]; + } + + /** + * @see com.sun.net.ssl.X509TrustManager#isClientTrusted(X509Certificate[]) + */ + public boolean isClientTrusted(X509Certificate[] certificates) { + return this.standardTrustManager.isClientTrusted(certificates); + } + + /** + * @see com.sun.net.ssl.X509TrustManager#isServerTrusted(X509Certificate[]) + */ + public boolean isServerTrusted(X509Certificate[] certificates) { + if ((certificates != null) && LOG.isDebugEnabled()) { + LOG.debug("Server certificate chain:"); + for (int i = 0; i < certificates.length; i++) { + LOG.debug("X509Certificate[" + i + "]=" + certificates[i]); + } + } + if ((certificates != null) && (certificates.length == 1)) { + X509Certificate certificate = certificates[0]; + try { + certificate.checkValidity(); + } + catch (CertificateException e) { + LOG.error(e.toString()); + return false; + } + return true; + } + else { + return this.standardTrustManager.isServerTrusted(certificates); + } + } + + /** + * @see com.sun.net.ssl.X509TrustManager#getAcceptedIssuers() + */ + public X509Certificate[] getAcceptedIssuers() { + return this.standardTrustManager.getAcceptedIssuers(); + } +} diff --git a/src/java/org/jivesoftware/sparkimpl/updater/SparkVersion.java b/src/java/org/jivesoftware/sparkimpl/updater/SparkVersion.java new file mode 100644 index 00000000..2b518538 --- /dev/null +++ b/src/java/org/jivesoftware/sparkimpl/updater/SparkVersion.java @@ -0,0 +1,146 @@ +/** + * $Revision: $ + * $Date: $ + * + * Copyright (C) 2006 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Lesser Public License (LGPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.sparkimpl.updater; + +import org.jivesoftware.Spark; +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.provider.IQProvider; +import org.xmlpull.v1.XmlPullParser; + +import java.util.Date; + +public class SparkVersion extends IQ { + + private String version; + private long updateTime; + private String downloadURL; + private String displayMessage; + private String changeLogURL; + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + public Date getUpdateTime() { + return new Date(updateTime); + } + + public void setUpdateTime(long updateTime) { + this.updateTime = updateTime; + } + + public String getDownloadURL() { + return downloadURL; + } + + public void setDownloadURL(String downloadURL) { + this.downloadURL = downloadURL; + } + + public String getDisplayMessage() { + return displayMessage; + } + + public void setDisplayMessage(String displayMessage) { + this.displayMessage = displayMessage; + } + + public String getChangeLogURL() { + return changeLogURL; + } + + public void setChangeLogURL(String changeLogURL) { + this.changeLogURL = changeLogURL; + } + + + /** + * Element name of the packet extension. + */ + public static final String ELEMENT_NAME = "query"; + + /** + * Namespace of the packet extension. + */ + public static final String NAMESPACE = "jabber:iq:spark"; + + public SparkVersion() { + + } + + public String getChildElementXML() { + StringBuffer buf = new StringBuffer(); + buf.append(""); + + // Add os specific information + + if (Spark.isWindows()) { + buf.append("windows"); + } + else if (Spark.isMac()) { + buf.append("mac"); + } + else { + buf.append("linux"); + } + + buf.append(""); + return buf.toString(); + } + + /** + * An IQProvider for SparkVersion packets. + * + * @author Derek DeMoro + */ + public static class Provider implements IQProvider { + + public Provider() { + super(); + } + + public IQ parseIQ(XmlPullParser parser) throws Exception { + SparkVersion version = new SparkVersion(); + + boolean done = false; + while (!done) { + int eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG) { + if (parser.getName().equals("version")) { + version.setVersion(parser.nextText()); + } + else if (parser.getName().equals("updatedTime")) { + Long time = new Long(parser.nextText()); + version.setUpdateTime(time.longValue()); + } + else if (parser.getName().equals("downloadURL")) { + version.setDownloadURL(parser.nextText()); + } + else if (parser.getName().equals("displayMessage")) { + version.setDisplayMessage(parser.nextText()); + } + } + + else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals(ELEMENT_NAME)) { + done = true; + } + } + } + + return version; + } + } +} diff --git a/src/resources/Info.plist b/src/resources/Info.plist new file mode 100644 index 00000000..eb2d164a --- /dev/null +++ b/src/resources/Info.plist @@ -0,0 +1,55 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + JavaApplicationStub + CFBundleGetInfoString + 1.0, Copyright 2005 Jive Software, All Rights Reserved. + CFBundleIconFile + message.icns + + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + Spark 1.0 + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + Java + + JVMVersion + 1.4* + MainClass + com.jivesoftware.JiveTalk + + ClassPath + /System/Library/Java:$APP_PACKAGE/Contents/Resources/Java/lib/activation.jar:$APP_PACKAGE/Contents/Resources/Java/lib/base.jar:$APP_PACKAGE/Contents/Resources/Java/lib/smack.jar:$APP_PACKAGE/Contents/Resources/Java/lib/smackx-debug.jar:$APP_PACKAGE/Contents/Resources/Java/lib/smackx.jar:$APP_PACKAGE/Contents/Resources/Java/lib/spark.jar + + Properties + + apple.laf.useScreenMenuBar + true + apple.awt.showGrowBox + false + + + LSHasLocalizedDisplayName + + NSHumanReadableCopyright + Copyright 2005 Jive Software, All Rights Reserved. + NSJavaPath + + + \ No newline at end of file diff --git a/src/resources/META-INF/mime.types b/src/resources/META-INF/mime.types new file mode 100644 index 00000000..59028e53 --- /dev/null +++ b/src/resources/META-INF/mime.types @@ -0,0 +1,124 @@ +# MIME type to file extension mappings. Adapted from the +# Apache mime.types file and other sources. + +# MIME type Extension +application/andrew-inset ez EZ +application/iges igs iges IGS IGES +application/acad dwg DWG +application/dxf dxf DXF +application/mdb mdb MDB +application/ptc prt PRT +application/dwf dwf DWF +application/mac-binhex40 hqx HQX +application/mac-compactpro cpt CPT +application/msword doc DOC +application/octet-stream bin dms lha lzh exe class so dll BIN DMS LHA LZH EXE CLASS SO DLL +application/oda oda ODA +application/pdf pdf PDF +application/postscript ai eps ps AI EPS PS +application/rtf rtf RTF +application/set set SET +application/smil smi smil SMI SMIL +application/tajima dst DST +application/vnd.mif mif MIF +application/vnd.ms-excel xls XLS +application/vnd.ms-powerpoint ppt PPT +application/vnd.wap.wbxml wbxml WBXML +application/vnd.wap.wmlc wmlc WMLC +application/vnd.wap.wmlscriptc wmlsc WMLSC +application/x-bcpio bcpio BCPIO +application/x-bzip2 bz2 BZ2 +application/x-cdlink vcd VCD +application/x-chess-pgn pgn PGN +application/x-cpio cpio CPIO +application/x-csh csh CSH +application/x-director dcr dir dxr DCR DIR DXR +application/x-dvi dvi DVI +application/x-envoy evy EVY +application/x-futuresplash spl SPL +application/x-gtar gtar GTAR +application/x-gzip gz tgz GZ TGZ +application/x-hdf hdf HDF +application/x-javascript js JS +application/x-kword kwd kwt KWD KWT +application/x-kspread ksp KSP +application/x-kpresenter kpr kpt KPR KPT +application/x-kchart chrt CHRT +application/x-killustrator kil KIL +application/x-koan skp skd skt skm SKP SKD SKT SKM +application/x-latex latex LATEX +application/x-lisp lsp LSP +application/x-netcdf nc cdf NC CDF +application/x-ogg ogg OGG +application/x-rpm rpm RPM +application/x-sh sh SH +application/x-shar shar SHAR +application/x-shockwave-flash swf SWF +application/x-stuffit sit SIT +application/x-sv4cpio sv4cpio SV4PIO +application/x-sv4crc sv4crc SV4CRC +application/x-tar tar TAR +application/x-tcl tcl TCL +application/x-tex tex TEX +application/x-texinfo texinfo texi TEXINFO TEXI +application/x-troff t tr roff T TR ROFF +application/x-troff-man man MAN +application/x-troff-me me ME +application/x-troff-ms ms MS +application/x-ustar ustar USTAR +application/x-vrml vrml VRML +application/x-wais-source src SRC +application/xhtml+xml xhtml xht XHTML XHT +application/zip zip ZIP +audio/basic au snd AU SND +audio/midi mid midi kar MID MIDI KAR +audio/mpeg mpga mp2 mp3 MPGA MP2 MP3 +audio/x-aiff aif aiff aifc AIF AIFF AIFC +audio/x-mpegurl m3u M3U +audio/x-pn-realaudio ram rm RAM RM +audio/x-realaudio ra RA +audio/x-wav wav WAV +chemical/x-pdb pdb PDB +chemical/x-xyz xyz XYZ +image/bmp bmp BMP +image/gif gif GIF +image/ief ief IEF +image/jpeg jpeg jpg jpe JPG JPEG JPE +image/png png PNG +image/tga tga TGA +image/tiff tiff tif TIFF TIF +image/vnd svf SVF +image/vnd.dwg dwg DWG +image/vnd.djvu djvu djv DJVU DJV +image/vnd.svf svf SVF +image/vnd.wap.wbmp wbmp WBMP +image/x-cmu-raster ras RAS +image/x-portable-anymap pnm PNM +image/x-portable-bitmap pbm PBM +image/x-portable-graymap pgm PGM +image/x-portable-pixmap ppm PPM +image/x-rgb rgb RGB +image/x-xbitmap xbm XBM +image/x-xpixmap xpm XPM +image/x-xwindowdump xwd XWD +model/iges igs iges IGS IGES +model/mesh msh mesh silo MSH MESH SILO +model/vrml wrl vrml WRL VRML +text/css css CSS +text/html html htm HTML HTM +text/plain asc txt ASC TXT TEXT +text/richtext rtx RTX +text/rtf rtf RTF +text/sgml sgml sgm SGML SGM +text/tab-separated-values tsv TSV +text/vnd.wap.wml wml WML +text/vnd.wap.wmlscript wmls WMLS +text/x-setext etx ETX +text/x-vcard cvd CVD +text/xml xml xsl XML XSL +video/mpeg mpeg mpg mpe MPEG MPG MPE +video/quicktime qt mov QT MOV +video/vnd.mpegurl mxu MXU +video/x-msvideo avi AVI +video/x-sgi-movie movie MOVIE +x-conference/x-cooltalk ice ICE \ No newline at end of file diff --git a/src/resources/META-INF/plugins.xml b/src/resources/META-INF/plugins.xml new file mode 100644 index 00000000..01051c24 --- /dev/null +++ b/src/resources/META-INF/plugins.xml @@ -0,0 +1,123 @@ + + + + + Server Alerts + 1.0 + Derek DeMoro + http://www.jivesoftware.com + derek@jivesoftware.com + Used to display alerts from server. Can be used for server administrators for global + notification. + org.jivesoftware.sparkimpl.plugin.alerts.BroadcastPlugin + + + Jabber Browser + 1.0 + Derek DeMoro + http://www.jivesoftware.com + derek@jivesoftware.com + Used for discovering items on a XMPP server. + org.jivesoftware.sparkimpl.plugin.jabber.JabberBrowser + + + Asterisks Phone Plugin + 1.0 + Derek DeMoro + http://www.jivesoftware.com + derek@jivesoftware.com + Adds phone-pbx capabilities within Spark. Users can make calls, take incoming calls and be + notified of presence changes. + org.jivesoftware.sparkimpl.plugin.phone.PhonePlugin + + + Jabber Version + 1.0 + Derek DeMoro + http://www.jivesoftware.com + derek@jivesoftware.com + Displays which version of Spark users are running. + org.jivesoftware.sparkimpl.plugin.jabber.JabberVersion + + + Sounds Plugin + 1.0 + Derek DeMoro + http://www.jivesoftware.com + derek@jivesoftware.com + Adds sound preferences for Spark. + org.jivesoftware.sparkimpl.preference.sounds.SoundPlugin + + + Layout Plugin + 1.0 + Derek DeMoro + http://www.jivesoftware.com + derek@jivesoftware.com + Persists users layouts on subsequent loads + org.jivesoftware.sparkimpl.plugin.layout.LayoutPlugin + + + Chat Post Loader + 1.0 + Derek DeMoro + http://www.jivesoftware.com + derek@jivesoftware.com + Allows users to specify command-line arguments to control Spark on startup. + org.jivesoftware.sparkimpl.plugin.chat.ChatArgumentsPlugin + + + Presence Change Plugin + 1.0 + Derek DeMoro + http://www.jivesoftware.com + derek@jivesoftware.com + Tracks users presenences during the runtime of Spark. + org.jivesoftware.sparkimpl.plugin.chat.PresenceChangePlugin + + + Plugin Viewer Plugin + 1.0 + Derek DeMoro + http://www.jivesoftware.com + derek@jivesoftware.com + Allows users to download, install and uninstall 3rd party plugins + org.jivesoftware.sparkimpl.plugin.viewer.PluginViewer + + + Emoticon Plugin + 1.0 + Derek DeMoro + http://www.jivesoftware.com + derek@jivesoftware.com + Allows users to use a pick list for Emoticons. + org.jivesoftware.sparkimpl.plugin.emoticons.EmoticonPlugin + + + Sparklers Plugin + 1.0 + Derek DeMoro + http://www.jivesoftware.com + derek@jivesoftware.com + Allows users set Active Sparklers within a Chat Conversation. + org.jivesoftware.sparkimpl.plugin.sparklers.SparklersPlugin + + + Notes Plugin + 1.0 + Derek DeMoro + http://www.jivesoftware.com + derek@jivesoftware.com + Allows users to have scratch paper within Spark. + org.jivesoftware.sparkimpl.plugin.scratchpad.ScratchPadPlugin + + + Bookmark Plugin + 1.0 + Derek DeMoro + http://www.jivesoftware.com + derek@jivesoftware.com + Allows users to have bookmarks. + org.jivesoftware.sparkimpl.plugin.bookmarks.BookmarkPlugin + + diff --git a/src/resources/PkgInfo b/src/resources/PkgInfo new file mode 100644 index 00000000..bd04210f --- /dev/null +++ b/src/resources/PkgInfo @@ -0,0 +1 @@ +APPL???? \ No newline at end of file diff --git a/src/resources/images/QueryManager.gif b/src/resources/images/QueryManager.gif new file mode 100644 index 00000000..f5c59954 Binary files /dev/null and b/src/resources/images/QueryManager.gif differ diff --git a/src/resources/images/Text.gif b/src/resources/images/Text.gif new file mode 100644 index 00000000..337409ba Binary files /dev/null and b/src/resources/images/Text.gif differ diff --git a/src/resources/images/User1_32x32.png b/src/resources/images/User1_32x32.png new file mode 100644 index 00000000..e12702c2 Binary files /dev/null and b/src/resources/images/User1_32x32.png differ diff --git a/src/resources/images/about.png b/src/resources/images/about.png new file mode 100644 index 00000000..4017adde Binary files /dev/null and b/src/resources/images/about.png differ diff --git a/src/resources/images/add.png b/src/resources/images/add.png new file mode 100644 index 00000000..ad794dc7 Binary files /dev/null and b/src/resources/images/add.png differ diff --git a/src/resources/images/add_24x24.png b/src/resources/images/add_24x24.png new file mode 100644 index 00000000..a871081e Binary files /dev/null and b/src/resources/images/add_24x24.png differ diff --git a/src/resources/images/add_contact.png b/src/resources/images/add_contact.png new file mode 100644 index 00000000..30c2dbc0 Binary files /dev/null and b/src/resources/images/add_contact.png differ diff --git a/src/resources/images/address_book.png b/src/resources/images/address_book.png new file mode 100644 index 00000000..169278cd Binary files /dev/null and b/src/resources/images/address_book.png differ diff --git a/src/resources/images/alarmclock.png b/src/resources/images/alarmclock.png new file mode 100644 index 00000000..ddffedc1 Binary files /dev/null and b/src/resources/images/alarmclock.png differ diff --git a/src/resources/images/arrow_left_green.png b/src/resources/images/arrow_left_green.png new file mode 100644 index 00000000..f9896e37 Binary files /dev/null and b/src/resources/images/arrow_left_green.png differ diff --git a/src/resources/images/arrow_right_green.png b/src/resources/images/arrow_right_green.png new file mode 100644 index 00000000..11cc87a4 Binary files /dev/null and b/src/resources/images/arrow_right_green.png differ diff --git a/src/resources/images/availableUser.png b/src/resources/images/availableUser.png new file mode 100644 index 00000000..a1d1bac8 Binary files /dev/null and b/src/resources/images/availableUser.png differ diff --git a/src/resources/images/awayUser.png b/src/resources/images/awayUser.png new file mode 100644 index 00000000..f1358431 Binary files /dev/null and b/src/resources/images/awayUser.png differ diff --git a/src/resources/images/blank.gif b/src/resources/images/blank.gif new file mode 100644 index 00000000..e7b8ec8e Binary files /dev/null and b/src/resources/images/blank.gif differ diff --git a/src/resources/images/blank_24x24.png b/src/resources/images/blank_24x24.png new file mode 100644 index 00000000..ff0368b9 Binary files /dev/null and b/src/resources/images/blank_24x24.png differ diff --git a/src/resources/images/blue-ball.png b/src/resources/images/blue-ball.png new file mode 100644 index 00000000..b2699149 Binary files /dev/null and b/src/resources/images/blue-ball.png differ diff --git a/src/resources/images/bookmark.png b/src/resources/images/bookmark.png new file mode 100644 index 00000000..cd26bd30 Binary files /dev/null and b/src/resources/images/bookmark.png differ diff --git a/src/resources/images/bookmark_add.png b/src/resources/images/bookmark_add.png new file mode 100644 index 00000000..b1169c27 Binary files /dev/null and b/src/resources/images/bookmark_add.png differ diff --git a/src/resources/images/bookmark_delete.png b/src/resources/images/bookmark_delete.png new file mode 100644 index 00000000..bb47ce2e Binary files /dev/null and b/src/resources/images/bookmark_delete.png differ diff --git a/src/resources/images/brickwall.png b/src/resources/images/brickwall.png new file mode 100644 index 00000000..7de03a2b Binary files /dev/null and b/src/resources/images/brickwall.png differ diff --git a/src/resources/images/bullet_ball_glass_clear.png b/src/resources/images/bullet_ball_glass_clear.png new file mode 100644 index 00000000..f0bbd027 Binary files /dev/null and b/src/resources/images/bullet_ball_glass_clear.png differ diff --git a/src/resources/images/businessman_view.png b/src/resources/images/businessman_view.png new file mode 100644 index 00000000..096ee87f Binary files /dev/null and b/src/resources/images/businessman_view.png differ diff --git a/src/resources/images/busy.gif b/src/resources/images/busy.gif new file mode 100644 index 00000000..c91e58e9 Binary files /dev/null and b/src/resources/images/busy.gif differ diff --git a/src/resources/images/call.png b/src/resources/images/call.png new file mode 100644 index 00000000..1882f470 Binary files /dev/null and b/src/resources/images/call.png differ diff --git a/src/resources/images/cancel.gif b/src/resources/images/cancel.gif new file mode 100644 index 00000000..6e9a9fe4 Binary files /dev/null and b/src/resources/images/cancel.gif differ diff --git a/src/resources/images/chatManager.png b/src/resources/images/chatManager.png new file mode 100644 index 00000000..6e4d91ed Binary files /dev/null and b/src/resources/images/chatManager.png differ diff --git a/src/resources/images/check.png b/src/resources/images/check.png new file mode 100644 index 00000000..deefc129 Binary files /dev/null and b/src/resources/images/check.png differ diff --git a/src/resources/images/close.png b/src/resources/images/close.png new file mode 100644 index 00000000..014f53e8 Binary files /dev/null and b/src/resources/images/close.png differ diff --git a/src/resources/images/close_dark.png b/src/resources/images/close_dark.png new file mode 100644 index 00000000..87a082c6 Binary files /dev/null and b/src/resources/images/close_dark.png differ diff --git a/src/resources/images/close_white.png b/src/resources/images/close_white.png new file mode 100644 index 00000000..b76a93c2 Binary files /dev/null and b/src/resources/images/close_white.png differ diff --git a/src/resources/images/conference_16x16.png b/src/resources/images/conference_16x16.png new file mode 100644 index 00000000..6dab998e Binary files /dev/null and b/src/resources/images/conference_16x16.png differ diff --git a/src/resources/images/conference_24x24.png b/src/resources/images/conference_24x24.png new file mode 100644 index 00000000..cdf12ca3 Binary files /dev/null and b/src/resources/images/conference_24x24.png differ diff --git a/src/resources/images/conference_32x32.png b/src/resources/images/conference_32x32.png new file mode 100644 index 00000000..43daac90 Binary files /dev/null and b/src/resources/images/conference_32x32.png differ diff --git a/src/resources/images/copy.png b/src/resources/images/copy.png new file mode 100644 index 00000000..87cf5c96 Binary files /dev/null and b/src/resources/images/copy.png differ diff --git a/src/resources/images/data_delete.png b/src/resources/images/data_delete.png new file mode 100644 index 00000000..49b54064 Binary files /dev/null and b/src/resources/images/data_delete.png differ diff --git a/src/resources/images/data_find.png b/src/resources/images/data_find.png new file mode 100644 index 00000000..e7ddfe44 Binary files /dev/null and b/src/resources/images/data_find.png differ diff --git a/src/resources/images/data_refresh.png b/src/resources/images/data_refresh.png new file mode 100644 index 00000000..70a826d8 Binary files /dev/null and b/src/resources/images/data_refresh.png differ diff --git a/src/resources/images/delete.png b/src/resources/images/delete.png new file mode 100644 index 00000000..0dbc6f35 Binary files /dev/null and b/src/resources/images/delete.png differ diff --git a/src/resources/images/deleteitem.gif b/src/resources/images/deleteitem.gif new file mode 100644 index 00000000..81169c54 Binary files /dev/null and b/src/resources/images/deleteitem.gif differ diff --git a/src/resources/images/desktop.png b/src/resources/images/desktop.png new file mode 100644 index 00000000..4ee0db7b Binary files /dev/null and b/src/resources/images/desktop.png differ diff --git a/src/resources/images/doc-changelog-16x16.gif b/src/resources/images/doc-changelog-16x16.gif new file mode 100644 index 00000000..bcab9f8f Binary files /dev/null and b/src/resources/images/doc-changelog-16x16.gif differ diff --git a/src/resources/images/doc-readme-16x16.gif b/src/resources/images/doc-readme-16x16.gif new file mode 100644 index 00000000..a87f1d77 Binary files /dev/null and b/src/resources/images/doc-readme-16x16.gif differ diff --git a/src/resources/images/document.png b/src/resources/images/document.png new file mode 100644 index 00000000..da793ccd Binary files /dev/null and b/src/resources/images/document.png differ diff --git a/src/resources/images/document_add.png b/src/resources/images/document_add.png new file mode 100644 index 00000000..6db0919d Binary files /dev/null and b/src/resources/images/document_add.png differ diff --git a/src/resources/images/document_attachment-32x32.png b/src/resources/images/document_attachment-32x32.png new file mode 100644 index 00000000..755a5b0d Binary files /dev/null and b/src/resources/images/document_attachment-32x32.png differ diff --git a/src/resources/images/document_exchange.png b/src/resources/images/document_exchange.png new file mode 100644 index 00000000..745916fc Binary files /dev/null and b/src/resources/images/document_exchange.png differ diff --git a/src/resources/images/document_find.png b/src/resources/images/document_find.png new file mode 100644 index 00000000..be22cc11 Binary files /dev/null and b/src/resources/images/document_find.png differ diff --git a/src/resources/images/document_info.png b/src/resources/images/document_info.png new file mode 100644 index 00000000..b35fef43 Binary files /dev/null and b/src/resources/images/document_info.png differ diff --git a/src/resources/images/document_into.png b/src/resources/images/document_into.png new file mode 100644 index 00000000..3dce3fae Binary files /dev/null and b/src/resources/images/document_into.png differ diff --git a/src/resources/images/document_view.png b/src/resources/images/document_view.png new file mode 100644 index 00000000..aa58c324 Binary files /dev/null and b/src/resources/images/document_view.png differ diff --git a/src/resources/images/documents.png b/src/resources/images/documents.png new file mode 100644 index 00000000..1a0894c7 Binary files /dev/null and b/src/resources/images/documents.png differ diff --git a/src/resources/images/door.gif b/src/resources/images/door.gif new file mode 100644 index 00000000..92c422ee Binary files /dev/null and b/src/resources/images/door.gif differ diff --git a/src/resources/images/down_arrow.gif b/src/resources/images/down_arrow.gif new file mode 100644 index 00000000..a5744005 Binary files /dev/null and b/src/resources/images/down_arrow.gif differ diff --git a/src/resources/images/download.png b/src/resources/images/download.png new file mode 100644 index 00000000..52c94a45 Binary files /dev/null and b/src/resources/images/download.png differ diff --git a/src/resources/images/earth_connection-16x16.png b/src/resources/images/earth_connection-16x16.png new file mode 100644 index 00000000..0bac2927 Binary files /dev/null and b/src/resources/images/earth_connection-16x16.png differ diff --git a/src/resources/images/earth_lock-16x16.png b/src/resources/images/earth_lock-16x16.png new file mode 100644 index 00000000..21443be0 Binary files /dev/null and b/src/resources/images/earth_lock-16x16.png differ diff --git a/src/resources/images/earth_view.png b/src/resources/images/earth_view.png new file mode 100644 index 00000000..cc547d06 Binary files /dev/null and b/src/resources/images/earth_view.png differ diff --git a/src/resources/images/emoticons/angry.gif b/src/resources/images/emoticons/angry.gif new file mode 100644 index 00000000..6489c9bc Binary files /dev/null and b/src/resources/images/emoticons/angry.gif differ diff --git a/src/resources/images/emoticons/blush.gif b/src/resources/images/emoticons/blush.gif new file mode 100644 index 00000000..22f1db55 Binary files /dev/null and b/src/resources/images/emoticons/blush.gif differ diff --git a/src/resources/images/emoticons/confused.gif b/src/resources/images/emoticons/confused.gif new file mode 100644 index 00000000..ef1107be Binary files /dev/null and b/src/resources/images/emoticons/confused.gif differ diff --git a/src/resources/images/emoticons/cool.gif b/src/resources/images/emoticons/cool.gif new file mode 100644 index 00000000..2d805e5d Binary files /dev/null and b/src/resources/images/emoticons/cool.gif differ diff --git a/src/resources/images/emoticons/cry.gif b/src/resources/images/emoticons/cry.gif new file mode 100644 index 00000000..e244944c Binary files /dev/null and b/src/resources/images/emoticons/cry.gif differ diff --git a/src/resources/images/emoticons/devil.gif b/src/resources/images/emoticons/devil.gif new file mode 100644 index 00000000..31bca49f Binary files /dev/null and b/src/resources/images/emoticons/devil.gif differ diff --git a/src/resources/images/emoticons/eyeRoll.gif b/src/resources/images/emoticons/eyeRoll.gif new file mode 100644 index 00000000..0e827073 Binary files /dev/null and b/src/resources/images/emoticons/eyeRoll.gif differ diff --git a/src/resources/images/emoticons/grin.gif b/src/resources/images/emoticons/grin.gif new file mode 100644 index 00000000..e5c92e2d Binary files /dev/null and b/src/resources/images/emoticons/grin.gif differ diff --git a/src/resources/images/emoticons/happy.gif b/src/resources/images/emoticons/happy.gif new file mode 100644 index 00000000..20a93e32 Binary files /dev/null and b/src/resources/images/emoticons/happy.gif differ diff --git a/src/resources/images/emoticons/laugh.gif b/src/resources/images/emoticons/laugh.gif new file mode 100644 index 00000000..e808a258 Binary files /dev/null and b/src/resources/images/emoticons/laugh.gif differ diff --git a/src/resources/images/emoticons/love.gif b/src/resources/images/emoticons/love.gif new file mode 100644 index 00000000..d1c320e3 Binary files /dev/null and b/src/resources/images/emoticons/love.gif differ diff --git a/src/resources/images/emoticons/mischief.gif b/src/resources/images/emoticons/mischief.gif new file mode 100644 index 00000000..238db06f Binary files /dev/null and b/src/resources/images/emoticons/mischief.gif differ diff --git a/src/resources/images/emoticons/party.gif b/src/resources/images/emoticons/party.gif new file mode 100644 index 00000000..e43f2808 Binary files /dev/null and b/src/resources/images/emoticons/party.gif differ diff --git a/src/resources/images/emoticons/plain.gif b/src/resources/images/emoticons/plain.gif new file mode 100644 index 00000000..a5beeea4 Binary files /dev/null and b/src/resources/images/emoticons/plain.gif differ diff --git a/src/resources/images/emoticons/sad.gif b/src/resources/images/emoticons/sad.gif new file mode 100644 index 00000000..0320885d Binary files /dev/null and b/src/resources/images/emoticons/sad.gif differ diff --git a/src/resources/images/emoticons/shocked.gif b/src/resources/images/emoticons/shocked.gif new file mode 100644 index 00000000..20a70f8c Binary files /dev/null and b/src/resources/images/emoticons/shocked.gif differ diff --git a/src/resources/images/emoticons/silly.gif b/src/resources/images/emoticons/silly.gif new file mode 100644 index 00000000..a162605b Binary files /dev/null and b/src/resources/images/emoticons/silly.gif differ diff --git a/src/resources/images/emoticons/sleepy.gif b/src/resources/images/emoticons/sleepy.gif new file mode 100644 index 00000000..d40769fb Binary files /dev/null and b/src/resources/images/emoticons/sleepy.gif differ diff --git a/src/resources/images/emoticons/wink.gif b/src/resources/images/emoticons/wink.gif new file mode 100644 index 00000000..6d91b0fa Binary files /dev/null and b/src/resources/images/emoticons/wink.gif differ diff --git a/src/resources/images/end_button_24x24.png b/src/resources/images/end_button_24x24.png new file mode 100644 index 00000000..88233580 Binary files /dev/null and b/src/resources/images/end_button_24x24.png differ diff --git a/src/resources/images/eraser.gif b/src/resources/images/eraser.gif new file mode 100644 index 00000000..f4d1f1ad Binary files /dev/null and b/src/resources/images/eraser.gif differ diff --git a/src/resources/images/fastpath16.png b/src/resources/images/fastpath16.png new file mode 100644 index 00000000..ccf6e522 Binary files /dev/null and b/src/resources/images/fastpath16.png differ diff --git a/src/resources/images/fastpath16_offline.png b/src/resources/images/fastpath16_offline.png new file mode 100644 index 00000000..34b21a55 Binary files /dev/null and b/src/resources/images/fastpath16_offline.png differ diff --git a/src/resources/images/fastpath24.png b/src/resources/images/fastpath24.png new file mode 100644 index 00000000..c32d23fa Binary files /dev/null and b/src/resources/images/fastpath24.png differ diff --git a/src/resources/images/fastpath24_offline.png b/src/resources/images/fastpath24_offline.png new file mode 100644 index 00000000..48ab2dd9 Binary files /dev/null and b/src/resources/images/fastpath24_offline.png differ diff --git a/src/resources/images/find.png b/src/resources/images/find.png new file mode 100644 index 00000000..d70e4ae6 Binary files /dev/null and b/src/resources/images/find.png differ diff --git a/src/resources/images/find_text.png b/src/resources/images/find_text.png new file mode 100644 index 00000000..02f90972 Binary files /dev/null and b/src/resources/images/find_text.png differ diff --git a/src/resources/images/first_aid_box.png b/src/resources/images/first_aid_box.png new file mode 100644 index 00000000..0c312103 Binary files /dev/null and b/src/resources/images/first_aid_box.png differ diff --git a/src/resources/images/flag.png b/src/resources/images/flag.png new file mode 100644 index 00000000..97c0c2ec Binary files /dev/null and b/src/resources/images/flag.png differ diff --git a/src/resources/images/flag_blue.png b/src/resources/images/flag_blue.png new file mode 100644 index 00000000..68afb6f7 Binary files /dev/null and b/src/resources/images/flag_blue.png differ diff --git a/src/resources/images/flag_green.png b/src/resources/images/flag_green.png new file mode 100644 index 00000000..f7aedc05 Binary files /dev/null and b/src/resources/images/flag_green.png differ diff --git a/src/resources/images/flag_red.png b/src/resources/images/flag_red.png new file mode 100644 index 00000000..12366360 Binary files /dev/null and b/src/resources/images/flag_red.png differ diff --git a/src/resources/images/flag_yellow.png b/src/resources/images/flag_yellow.png new file mode 100644 index 00000000..ae6835ca Binary files /dev/null and b/src/resources/images/flag_yellow.png differ diff --git a/src/resources/images/folder.png b/src/resources/images/folder.png new file mode 100644 index 00000000..c5df8d8a Binary files /dev/null and b/src/resources/images/folder.png differ diff --git a/src/resources/images/folder_add.png b/src/resources/images/folder_add.png new file mode 100644 index 00000000..c8375bdd Binary files /dev/null and b/src/resources/images/folder_add.png differ diff --git a/src/resources/images/folder_closed.png b/src/resources/images/folder_closed.png new file mode 100644 index 00000000..caad1268 Binary files /dev/null and b/src/resources/images/folder_closed.png differ diff --git a/src/resources/images/folder_cubes.png b/src/resources/images/folder_cubes.png new file mode 100644 index 00000000..a627895d Binary files /dev/null and b/src/resources/images/folder_cubes.png differ diff --git a/src/resources/images/folder_delete.png b/src/resources/images/folder_delete.png new file mode 100644 index 00000000..3d5519dd Binary files /dev/null and b/src/resources/images/folder_delete.png differ diff --git a/src/resources/images/folder_document.png b/src/resources/images/folder_document.png new file mode 100644 index 00000000..dff701fe Binary files /dev/null and b/src/resources/images/folder_document.png differ diff --git a/src/resources/images/folder_edit.png b/src/resources/images/folder_edit.png new file mode 100644 index 00000000..2450c273 Binary files /dev/null and b/src/resources/images/folder_edit.png differ diff --git a/src/resources/images/folder_forbidden.png b/src/resources/images/folder_forbidden.png new file mode 100644 index 00000000..04dbcf24 Binary files /dev/null and b/src/resources/images/folder_forbidden.png differ diff --git a/src/resources/images/folder_gear.png b/src/resources/images/folder_gear.png new file mode 100644 index 00000000..2c0b71b5 Binary files /dev/null and b/src/resources/images/folder_gear.png differ diff --git a/src/resources/images/folder_information.png b/src/resources/images/folder_information.png new file mode 100644 index 00000000..5d636ae5 Binary files /dev/null and b/src/resources/images/folder_information.png differ diff --git a/src/resources/images/folder_into.png b/src/resources/images/folder_into.png new file mode 100644 index 00000000..af9c5a1b Binary files /dev/null and b/src/resources/images/folder_into.png differ diff --git a/src/resources/images/folder_lock.png b/src/resources/images/folder_lock.png new file mode 100644 index 00000000..dc353045 Binary files /dev/null and b/src/resources/images/folder_lock.png differ diff --git a/src/resources/images/folder_movie.png b/src/resources/images/folder_movie.png new file mode 100644 index 00000000..375ae525 Binary files /dev/null and b/src/resources/images/folder_movie.png differ diff --git a/src/resources/images/folder_music.png b/src/resources/images/folder_music.png new file mode 100644 index 00000000..e284a42e Binary files /dev/null and b/src/resources/images/folder_music.png differ diff --git a/src/resources/images/folder_network.png b/src/resources/images/folder_network.png new file mode 100644 index 00000000..f6790d79 Binary files /dev/null and b/src/resources/images/folder_network.png differ diff --git a/src/resources/images/folder_new.png b/src/resources/images/folder_new.png new file mode 100644 index 00000000..acdbd75b Binary files /dev/null and b/src/resources/images/folder_new.png differ diff --git a/src/resources/images/folder_ok.png b/src/resources/images/folder_ok.png new file mode 100644 index 00000000..6dbf0445 Binary files /dev/null and b/src/resources/images/folder_ok.png differ diff --git a/src/resources/images/folder_out.png b/src/resources/images/folder_out.png new file mode 100644 index 00000000..5a4ff4eb Binary files /dev/null and b/src/resources/images/folder_out.png differ diff --git a/src/resources/images/folder_preferences.png b/src/resources/images/folder_preferences.png new file mode 100644 index 00000000..86f4cb1f Binary files /dev/null and b/src/resources/images/folder_preferences.png differ diff --git a/src/resources/images/folder_refresh.png b/src/resources/images/folder_refresh.png new file mode 100644 index 00000000..d6f3bb33 Binary files /dev/null and b/src/resources/images/folder_refresh.png differ diff --git a/src/resources/images/folder_time.png b/src/resources/images/folder_time.png new file mode 100644 index 00000000..e3a461ee Binary files /dev/null and b/src/resources/images/folder_time.png differ diff --git a/src/resources/images/folder_up.png b/src/resources/images/folder_up.png new file mode 100644 index 00000000..6a84350b Binary files /dev/null and b/src/resources/images/folder_up.png differ diff --git a/src/resources/images/folder_view.png b/src/resources/images/folder_view.png new file mode 100644 index 00000000..14888118 Binary files /dev/null and b/src/resources/images/folder_view.png differ diff --git a/src/resources/images/folder_warning.png b/src/resources/images/folder_warning.png new file mode 100644 index 00000000..073305ce Binary files /dev/null and b/src/resources/images/folder_warning.png differ diff --git a/src/resources/images/folder_window.png b/src/resources/images/folder_window.png new file mode 100644 index 00000000..0eff6943 Binary files /dev/null and b/src/resources/images/folder_window.png differ diff --git a/src/resources/images/folders.png b/src/resources/images/folders.png new file mode 100644 index 00000000..44a6c978 Binary files /dev/null and b/src/resources/images/folders.png differ diff --git a/src/resources/images/font.png b/src/resources/images/font.png new file mode 100644 index 00000000..a87f6e45 Binary files /dev/null and b/src/resources/images/font.png differ diff --git a/src/resources/images/forward.gif b/src/resources/images/forward.gif new file mode 100644 index 00000000..720defaa Binary files /dev/null and b/src/resources/images/forward.gif differ diff --git a/src/resources/images/funnel_down.png b/src/resources/images/funnel_down.png new file mode 100644 index 00000000..397f18c3 Binary files /dev/null and b/src/resources/images/funnel_down.png differ diff --git a/src/resources/images/gray-background.png b/src/resources/images/gray-background.png new file mode 100644 index 00000000..fa086796 Binary files /dev/null and b/src/resources/images/gray-background.png differ diff --git a/src/resources/images/green-ball.gif b/src/resources/images/green-ball.gif new file mode 100644 index 00000000..ac7c8bd0 Binary files /dev/null and b/src/resources/images/green-ball.gif differ diff --git a/src/resources/images/green-ball.png b/src/resources/images/green-ball.png new file mode 100644 index 00000000..5ab02fc7 Binary files /dev/null and b/src/resources/images/green-ball.png differ diff --git a/src/resources/images/header-stretch.gif b/src/resources/images/header-stretch.gif new file mode 100644 index 00000000..7b50b534 Binary files /dev/null and b/src/resources/images/header-stretch.gif differ diff --git a/src/resources/images/help2.png b/src/resources/images/help2.png new file mode 100644 index 00000000..25ce5088 Binary files /dev/null and b/src/resources/images/help2.png differ diff --git a/src/resources/images/help_16x16.png b/src/resources/images/help_16x16.png new file mode 100644 index 00000000..25ce5088 Binary files /dev/null and b/src/resources/images/help_16x16.png differ diff --git a/src/resources/images/help_24x24.png b/src/resources/images/help_24x24.png new file mode 100644 index 00000000..0a3fcc3a Binary files /dev/null and b/src/resources/images/help_24x24.png differ diff --git a/src/resources/images/history-24x24.png b/src/resources/images/history-24x24.png new file mode 100644 index 00000000..c5af4d1f Binary files /dev/null and b/src/resources/images/history-24x24.png differ diff --git a/src/resources/images/history.png b/src/resources/images/history.png new file mode 100644 index 00000000..1fca9c47 Binary files /dev/null and b/src/resources/images/history.png differ diff --git a/src/resources/images/id_card.png b/src/resources/images/id_card.png new file mode 100644 index 00000000..a8e9d6f7 Binary files /dev/null and b/src/resources/images/id_card.png differ diff --git a/src/resources/images/im_away.png b/src/resources/images/im_away.png new file mode 100644 index 00000000..a98f68b7 Binary files /dev/null and b/src/resources/images/im_away.png differ diff --git a/src/resources/images/im_dnd.png b/src/resources/images/im_dnd.png new file mode 100644 index 00000000..49e3ed67 Binary files /dev/null and b/src/resources/images/im_dnd.png differ diff --git a/src/resources/images/im_free_chat.png b/src/resources/images/im_free_chat.png new file mode 100644 index 00000000..e89d7de2 Binary files /dev/null and b/src/resources/images/im_free_chat.png differ diff --git a/src/resources/images/information.png b/src/resources/images/information.png new file mode 100644 index 00000000..4017adde Binary files /dev/null and b/src/resources/images/information.png differ diff --git a/src/resources/images/initials.gif b/src/resources/images/initials.gif new file mode 100644 index 00000000..45d2a019 Binary files /dev/null and b/src/resources/images/initials.gif differ diff --git a/src/resources/images/join_groupchat.png b/src/resources/images/join_groupchat.png new file mode 100644 index 00000000..89ee6bf2 Binary files /dev/null and b/src/resources/images/join_groupchat.png differ diff --git a/src/resources/images/leftArrow.gif b/src/resources/images/leftArrow.gif new file mode 100644 index 00000000..f851578c Binary files /dev/null and b/src/resources/images/leftArrow.gif differ diff --git a/src/resources/images/link-16x16.png b/src/resources/images/link-16x16.png new file mode 100644 index 00000000..81f1909d Binary files /dev/null and b/src/resources/images/link-16x16.png differ diff --git a/src/resources/images/link_delete.png b/src/resources/images/link_delete.png new file mode 100644 index 00000000..c2e86ef4 Binary files /dev/null and b/src/resources/images/link_delete.png differ diff --git a/src/resources/images/lock-16x16.png b/src/resources/images/lock-16x16.png new file mode 100644 index 00000000..8b81e7ff Binary files /dev/null and b/src/resources/images/lock-16x16.png differ diff --git a/src/resources/images/lock_and_keys.gif b/src/resources/images/lock_and_keys.gif new file mode 100644 index 00000000..8186fab7 Binary files /dev/null and b/src/resources/images/lock_and_keys.gif differ diff --git a/src/resources/images/lock_unlock-16x16.png b/src/resources/images/lock_unlock-16x16.png new file mode 100644 index 00000000..9dd5df34 Binary files /dev/null and b/src/resources/images/lock_unlock-16x16.png differ diff --git a/src/resources/images/login-key.png b/src/resources/images/login-key.png new file mode 100644 index 00000000..53d2a24e Binary files /dev/null and b/src/resources/images/login-key.png differ diff --git a/src/resources/images/login_dialog_background.png b/src/resources/images/login_dialog_background.png new file mode 100644 index 00000000..d9a17b8a Binary files /dev/null and b/src/resources/images/login_dialog_background.png differ diff --git a/src/resources/images/magician.png b/src/resources/images/magician.png new file mode 100644 index 00000000..f00147ff Binary files /dev/null and b/src/resources/images/magician.png differ diff --git a/src/resources/images/mail.png b/src/resources/images/mail.png new file mode 100644 index 00000000..c22b2a9b Binary files /dev/null and b/src/resources/images/mail.png differ diff --git a/src/resources/images/mail_16x16.png b/src/resources/images/mail_16x16.png new file mode 100644 index 00000000..2583b843 Binary files /dev/null and b/src/resources/images/mail_16x16.png differ diff --git a/src/resources/images/mail_32x32.png b/src/resources/images/mail_32x32.png new file mode 100644 index 00000000..0b7c44cb Binary files /dev/null and b/src/resources/images/mail_32x32.png differ diff --git a/src/resources/images/mail_forward_16x16.png b/src/resources/images/mail_forward_16x16.png new file mode 100644 index 00000000..92c427b0 Binary files /dev/null and b/src/resources/images/mail_forward_16x16.png differ diff --git a/src/resources/images/mail_into_16x16.png b/src/resources/images/mail_into_16x16.png new file mode 100644 index 00000000..a62c6079 Binary files /dev/null and b/src/resources/images/mail_into_16x16.png differ diff --git a/src/resources/images/megaphone.gif b/src/resources/images/megaphone.gif new file mode 100644 index 00000000..3fcaca62 Binary files /dev/null and b/src/resources/images/megaphone.gif differ diff --git a/src/resources/images/megaphone.png b/src/resources/images/megaphone.png new file mode 100644 index 00000000..53b04d8a Binary files /dev/null and b/src/resources/images/megaphone.png differ diff --git a/src/resources/images/message-32x32.png b/src/resources/images/message-32x32.png new file mode 100644 index 00000000..6b851f02 Binary files /dev/null and b/src/resources/images/message-32x32.png differ diff --git a/src/resources/images/message.icns b/src/resources/images/message.icns new file mode 100644 index 00000000..3edc8d34 Binary files /dev/null and b/src/resources/images/message.icns differ diff --git a/src/resources/images/message.ico b/src/resources/images/message.ico new file mode 100644 index 00000000..2b81131a Binary files /dev/null and b/src/resources/images/message.ico differ diff --git a/src/resources/images/message.png b/src/resources/images/message.png new file mode 100644 index 00000000..b964bbfb Binary files /dev/null and b/src/resources/images/message.png differ diff --git a/src/resources/images/message_away.png b/src/resources/images/message_away.png new file mode 100644 index 00000000..fead7b7b Binary files /dev/null and b/src/resources/images/message_away.png differ diff --git a/src/resources/images/message_dnd.png b/src/resources/images/message_dnd.png new file mode 100644 index 00000000..bf8f8224 Binary files /dev/null and b/src/resources/images/message_dnd.png differ diff --git a/src/resources/images/message_edit.png b/src/resources/images/message_edit.png new file mode 100644 index 00000000..3ec9cb2f Binary files /dev/null and b/src/resources/images/message_edit.png differ diff --git a/src/resources/images/metallic_down.png b/src/resources/images/metallic_down.png new file mode 100644 index 00000000..7179f85c Binary files /dev/null and b/src/resources/images/metallic_down.png differ diff --git a/src/resources/images/metallic_up.png b/src/resources/images/metallic_up.png new file mode 100644 index 00000000..a822b642 Binary files /dev/null and b/src/resources/images/metallic_up.png differ diff --git a/src/resources/images/minus-sign.png b/src/resources/images/minus-sign.png new file mode 100644 index 00000000..0d416ec2 Binary files /dev/null and b/src/resources/images/minus-sign.png differ diff --git a/src/resources/images/mobilephone.png b/src/resources/images/mobilephone.png new file mode 100644 index 00000000..0fe2c300 Binary files /dev/null and b/src/resources/images/mobilephone.png differ diff --git a/src/resources/images/moderator.gif b/src/resources/images/moderator.gif new file mode 100644 index 00000000..233c4d79 Binary files /dev/null and b/src/resources/images/moderator.gif differ diff --git a/src/resources/images/note_edit.png b/src/resources/images/note_edit.png new file mode 100644 index 00000000..4551107e Binary files /dev/null and b/src/resources/images/note_edit.png differ diff --git a/src/resources/images/notebook.png b/src/resources/images/notebook.png new file mode 100644 index 00000000..a01d2e73 Binary files /dev/null and b/src/resources/images/notebook.png differ diff --git a/src/resources/images/ok.gif b/src/resources/images/ok.gif new file mode 100644 index 00000000..1773be60 Binary files /dev/null and b/src/resources/images/ok.gif differ diff --git a/src/resources/images/on-phone.png b/src/resources/images/on-phone.png new file mode 100644 index 00000000..5354e16e Binary files /dev/null and b/src/resources/images/on-phone.png differ diff --git a/src/resources/images/openfile.gif b/src/resources/images/openfile.gif new file mode 100644 index 00000000..4a7553f0 Binary files /dev/null and b/src/resources/images/openfile.gif differ diff --git a/src/resources/images/option.png b/src/resources/images/option.png new file mode 100644 index 00000000..86075312 Binary files /dev/null and b/src/resources/images/option.png differ diff --git a/src/resources/images/pawn_glass_green.png b/src/resources/images/pawn_glass_green.png new file mode 100644 index 00000000..f44f10d1 Binary files /dev/null and b/src/resources/images/pawn_glass_green.png differ diff --git a/src/resources/images/pawn_glass_red.png b/src/resources/images/pawn_glass_red.png new file mode 100644 index 00000000..897b58f1 Binary files /dev/null and b/src/resources/images/pawn_glass_red.png differ diff --git a/src/resources/images/pawn_glass_white.png b/src/resources/images/pawn_glass_white.png new file mode 100644 index 00000000..b9527962 Binary files /dev/null and b/src/resources/images/pawn_glass_white.png differ diff --git a/src/resources/images/pawn_glass_yellow.png b/src/resources/images/pawn_glass_yellow.png new file mode 100644 index 00000000..0780d296 Binary files /dev/null and b/src/resources/images/pawn_glass_yellow.png differ diff --git a/src/resources/images/pdf.gif b/src/resources/images/pdf.gif new file mode 100644 index 00000000..40cea6bf Binary files /dev/null and b/src/resources/images/pdf.gif differ diff --git a/src/resources/images/photo_scenery.png b/src/resources/images/photo_scenery.png new file mode 100644 index 00000000..ceeebd35 Binary files /dev/null and b/src/resources/images/photo_scenery.png differ diff --git a/src/resources/images/pin_blue.png b/src/resources/images/pin_blue.png new file mode 100644 index 00000000..893b5723 Binary files /dev/null and b/src/resources/images/pin_blue.png differ diff --git a/src/resources/images/plug-sign.png b/src/resources/images/plug-sign.png new file mode 100644 index 00000000..9429408c Binary files /dev/null and b/src/resources/images/plug-sign.png differ diff --git a/src/resources/images/plugin-16x16.gif b/src/resources/images/plugin-16x16.gif new file mode 100644 index 00000000..084db525 Binary files /dev/null and b/src/resources/images/plugin-16x16.gif differ diff --git a/src/resources/images/plus-sign.png b/src/resources/images/plus-sign.png new file mode 100644 index 00000000..9429408c Binary files /dev/null and b/src/resources/images/plus-sign.png differ diff --git a/src/resources/images/pointer.gif b/src/resources/images/pointer.gif new file mode 100644 index 00000000..d4fa48e4 Binary files /dev/null and b/src/resources/images/pointer.gif differ diff --git a/src/resources/images/powered_by.png b/src/resources/images/powered_by.png new file mode 100644 index 00000000..11b5bed7 Binary files /dev/null and b/src/resources/images/powered_by.png differ diff --git a/src/resources/images/preferences.png b/src/resources/images/preferences.png new file mode 100644 index 00000000..ab54a83c Binary files /dev/null and b/src/resources/images/preferences.png differ diff --git a/src/resources/images/printer.png b/src/resources/images/printer.png new file mode 100644 index 00000000..be2aad4f Binary files /dev/null and b/src/resources/images/printer.png differ diff --git a/src/resources/images/profile.png b/src/resources/images/profile.png new file mode 100644 index 00000000..6dbcc2cb Binary files /dev/null and b/src/resources/images/profile.png differ diff --git a/src/resources/images/profile_24x24.png b/src/resources/images/profile_24x24.png new file mode 100644 index 00000000..70e78af3 Binary files /dev/null and b/src/resources/images/profile_24x24.png differ diff --git a/src/resources/images/profile_32x32.png b/src/resources/images/profile_32x32.png new file mode 100644 index 00000000..c50ae6de Binary files /dev/null and b/src/resources/images/profile_32x32.png differ diff --git a/src/resources/images/questionsAnswers.png b/src/resources/images/questionsAnswers.png new file mode 100644 index 00000000..0f9b23b2 Binary files /dev/null and b/src/resources/images/questionsAnswers.png differ diff --git a/src/resources/images/red-ball.png b/src/resources/images/red-ball.png new file mode 100644 index 00000000..a0c32e6d Binary files /dev/null and b/src/resources/images/red-ball.png differ diff --git a/src/resources/images/refresh.png b/src/resources/images/refresh.png new file mode 100644 index 00000000..870adb5f Binary files /dev/null and b/src/resources/images/refresh.png differ diff --git a/src/resources/images/save_as.png b/src/resources/images/save_as.png new file mode 100644 index 00000000..42ecadcf Binary files /dev/null and b/src/resources/images/save_as.png differ diff --git a/src/resources/images/scroll_refresh.png b/src/resources/images/scroll_refresh.png new file mode 100644 index 00000000..01528c8a Binary files /dev/null and b/src/resources/images/scroll_refresh.png differ diff --git a/src/resources/images/search_32x32.png b/src/resources/images/search_32x32.png new file mode 100644 index 00000000..0396d445 Binary files /dev/null and b/src/resources/images/search_32x32.png differ diff --git a/src/resources/images/search_user_16x16.png b/src/resources/images/search_user_16x16.png new file mode 100644 index 00000000..3edbece4 Binary files /dev/null and b/src/resources/images/search_user_16x16.png differ diff --git a/src/resources/images/secondary_background_image.png b/src/resources/images/secondary_background_image.png new file mode 100644 index 00000000..2980f2ec Binary files /dev/null and b/src/resources/images/secondary_background_image.png differ diff --git a/src/resources/images/send_file_24x24.png b/src/resources/images/send_file_24x24.png new file mode 100644 index 00000000..39c31646 Binary files /dev/null and b/src/resources/images/send_file_24x24.png differ diff --git a/src/resources/images/sendmail.png b/src/resources/images/sendmail.png new file mode 100644 index 00000000..92c427b0 Binary files /dev/null and b/src/resources/images/sendmail.png differ diff --git a/src/resources/images/server.png b/src/resources/images/server.png new file mode 100644 index 00000000..f7004f49 Binary files /dev/null and b/src/resources/images/server.png differ diff --git a/src/resources/images/sign.gif b/src/resources/images/sign.gif new file mode 100644 index 00000000..337409ba Binary files /dev/null and b/src/resources/images/sign.gif differ diff --git a/src/resources/images/smallCheck.png b/src/resources/images/smallCheck.png new file mode 100644 index 00000000..e250606d Binary files /dev/null and b/src/resources/images/smallCheck.png differ diff --git a/src/resources/images/smallDelete.png b/src/resources/images/smallDelete.png new file mode 100644 index 00000000..12941329 Binary files /dev/null and b/src/resources/images/smallDelete.png differ diff --git a/src/resources/images/smallStop.png b/src/resources/images/smallStop.png new file mode 100644 index 00000000..7b41b3d8 Binary files /dev/null and b/src/resources/images/smallStop.png differ diff --git a/src/resources/images/smallUserDelete.png b/src/resources/images/smallUserDelete.png new file mode 100644 index 00000000..57d5ec79 Binary files /dev/null and b/src/resources/images/smallUserDelete.png differ diff --git a/src/resources/images/smallUserEnter.png b/src/resources/images/smallUserEnter.png new file mode 100644 index 00000000..fe9cb6da Binary files /dev/null and b/src/resources/images/smallUserEnter.png differ diff --git a/src/resources/images/small_add.png b/src/resources/images/small_add.png new file mode 100644 index 00000000..ad794dc7 Binary files /dev/null and b/src/resources/images/small_add.png differ diff --git a/src/resources/images/small_agent.png b/src/resources/images/small_agent.png new file mode 100644 index 00000000..0a0e592b Binary files /dev/null and b/src/resources/images/small_agent.png differ diff --git a/src/resources/images/small_delete.png b/src/resources/images/small_delete.png new file mode 100644 index 00000000..0dbc6f35 Binary files /dev/null and b/src/resources/images/small_delete.png differ diff --git a/src/resources/images/small_entry.gif b/src/resources/images/small_entry.gif new file mode 100644 index 00000000..ed79a54b Binary files /dev/null and b/src/resources/images/small_entry.gif differ diff --git a/src/resources/images/small_profile.png b/src/resources/images/small_profile.png new file mode 100644 index 00000000..c7c9a478 Binary files /dev/null and b/src/resources/images/small_profile.png differ diff --git a/src/resources/images/spark.png b/src/resources/images/spark.png new file mode 100644 index 00000000..1a4ff8dd Binary files /dev/null and b/src/resources/images/spark.png differ diff --git a/src/resources/images/spark_100.jpg b/src/resources/images/spark_100.jpg new file mode 100644 index 00000000..5af9ef48 Binary files /dev/null and b/src/resources/images/spark_100.jpg differ diff --git a/src/resources/images/star_blue.png b/src/resources/images/star_blue.png new file mode 100644 index 00000000..2eb9db98 Binary files /dev/null and b/src/resources/images/star_blue.png differ diff --git a/src/resources/images/star_green.png b/src/resources/images/star_green.png new file mode 100644 index 00000000..0343b7f8 Binary files /dev/null and b/src/resources/images/star_green.png differ diff --git a/src/resources/images/star_grey.png b/src/resources/images/star_grey.png new file mode 100644 index 00000000..1ec5e478 Binary files /dev/null and b/src/resources/images/star_grey.png differ diff --git a/src/resources/images/star_red.png b/src/resources/images/star_red.png new file mode 100644 index 00000000..cffcdef9 Binary files /dev/null and b/src/resources/images/star_red.png differ diff --git a/src/resources/images/star_yellow.png b/src/resources/images/star_yellow.png new file mode 100644 index 00000000..9d695b19 Binary files /dev/null and b/src/resources/images/star_yellow.png differ diff --git a/src/resources/images/sticky.png b/src/resources/images/sticky.png new file mode 100644 index 00000000..06216e84 Binary files /dev/null and b/src/resources/images/sticky.png differ diff --git a/src/resources/images/stopwatch_pause.png b/src/resources/images/stopwatch_pause.png new file mode 100644 index 00000000..0e5e7aa8 Binary files /dev/null and b/src/resources/images/stopwatch_pause.png differ diff --git a/src/resources/images/telephone_24x24.png b/src/resources/images/telephone_24x24.png new file mode 100644 index 00000000..0f573642 Binary files /dev/null and b/src/resources/images/telephone_24x24.png differ diff --git a/src/resources/images/tellAFriend.gif b/src/resources/images/tellAFriend.gif new file mode 100644 index 00000000..65246562 Binary files /dev/null and b/src/resources/images/tellAFriend.gif differ diff --git a/src/resources/images/textFile.gif b/src/resources/images/textFile.gif new file mode 100644 index 00000000..a71e6405 Binary files /dev/null and b/src/resources/images/textFile.gif differ diff --git a/src/resources/images/text_bold.png b/src/resources/images/text_bold.png new file mode 100644 index 00000000..4fb8dd90 Binary files /dev/null and b/src/resources/images/text_bold.png differ diff --git a/src/resources/images/text_italics.png b/src/resources/images/text_italics.png new file mode 100644 index 00000000..14db2c1d Binary files /dev/null and b/src/resources/images/text_italics.png differ diff --git a/src/resources/images/text_loudspeaker.png b/src/resources/images/text_loudspeaker.png new file mode 100644 index 00000000..ae5e28d1 Binary files /dev/null and b/src/resources/images/text_loudspeaker.png differ diff --git a/src/resources/images/text_normal.png b/src/resources/images/text_normal.png new file mode 100644 index 00000000..f91fd536 Binary files /dev/null and b/src/resources/images/text_normal.png differ diff --git a/src/resources/images/text_ok.png b/src/resources/images/text_ok.png new file mode 100644 index 00000000..e034a2a4 Binary files /dev/null and b/src/resources/images/text_ok.png differ diff --git a/src/resources/images/text_underlined.png b/src/resources/images/text_underlined.png new file mode 100644 index 00000000..0e5fb06c Binary files /dev/null and b/src/resources/images/text_underlined.png differ diff --git a/src/resources/images/toolbar.png b/src/resources/images/toolbar.png new file mode 100644 index 00000000..02cb432b Binary files /dev/null and b/src/resources/images/toolbar.png differ diff --git a/src/resources/images/top_bottom_background_image.png b/src/resources/images/top_bottom_background_image.png new file mode 100644 index 00000000..32b3a7e9 Binary files /dev/null and b/src/resources/images/top_bottom_background_image.png differ diff --git a/src/resources/images/traffic-light.png b/src/resources/images/traffic-light.png new file mode 100644 index 00000000..f19f2004 Binary files /dev/null and b/src/resources/images/traffic-light.png differ diff --git a/src/resources/images/transfer-24x24.png b/src/resources/images/transfer-24x24.png new file mode 100644 index 00000000..32403b38 Binary files /dev/null and b/src/resources/images/transfer-24x24.png differ diff --git a/src/resources/images/txt.gif b/src/resources/images/txt.gif new file mode 100644 index 00000000..8964dd16 Binary files /dev/null and b/src/resources/images/txt.gif differ diff --git a/src/resources/images/user.png b/src/resources/images/user.png new file mode 100644 index 00000000..a1d1bac8 Binary files /dev/null and b/src/resources/images/user.png differ diff --git a/src/resources/images/user1.png b/src/resources/images/user1.png new file mode 100644 index 00000000..61a0351e Binary files /dev/null and b/src/resources/images/user1.png differ diff --git a/src/resources/images/user1_add.png b/src/resources/images/user1_add.png new file mode 100644 index 00000000..80274e4b Binary files /dev/null and b/src/resources/images/user1_add.png differ diff --git a/src/resources/images/user1_back.png b/src/resources/images/user1_back.png new file mode 100644 index 00000000..63325455 Binary files /dev/null and b/src/resources/images/user1_back.png differ diff --git a/src/resources/images/user1_earth.png b/src/resources/images/user1_earth.png new file mode 100644 index 00000000..b3b27714 Binary files /dev/null and b/src/resources/images/user1_earth.png differ diff --git a/src/resources/images/user1_information.png b/src/resources/images/user1_information.png new file mode 100644 index 00000000..e2617433 Binary files /dev/null and b/src/resources/images/user1_information.png differ diff --git a/src/resources/images/user1_message-16x16.png b/src/resources/images/user1_message-16x16.png new file mode 100644 index 00000000..d8a8a395 Binary files /dev/null and b/src/resources/images/user1_message-16x16.png differ diff --git a/src/resources/images/user1_message-24x24.png b/src/resources/images/user1_message-24x24.png new file mode 100644 index 00000000..34200b8f Binary files /dev/null and b/src/resources/images/user1_message-24x24.png differ diff --git a/src/resources/images/user1_mobilephone.png b/src/resources/images/user1_mobilephone.png new file mode 100644 index 00000000..175e626f Binary files /dev/null and b/src/resources/images/user1_mobilephone.png differ diff --git a/src/resources/images/user1_new.png b/src/resources/images/user1_new.png new file mode 100644 index 00000000..b00197f4 Binary files /dev/null and b/src/resources/images/user1_new.png differ diff --git a/src/resources/images/user1_time.png b/src/resources/images/user1_time.png new file mode 100644 index 00000000..c738207e Binary files /dev/null and b/src/resources/images/user1_time.png differ diff --git a/src/resources/images/user2.png b/src/resources/images/user2.png new file mode 100644 index 00000000..f72aa562 Binary files /dev/null and b/src/resources/images/user2.png differ diff --git a/src/resources/images/user4.png b/src/resources/images/user4.png new file mode 100644 index 00000000..0158ffd3 Binary files /dev/null and b/src/resources/images/user4.png differ diff --git a/src/resources/images/user_headset24.png b/src/resources/images/user_headset24.png new file mode 100644 index 00000000..61a0351e Binary files /dev/null and b/src/resources/images/user_headset24.png differ diff --git a/src/resources/images/users2.png b/src/resources/images/users2.png new file mode 100644 index 00000000..81b223d5 Binary files /dev/null and b/src/resources/images/users2.png differ diff --git a/src/resources/images/users_family.png b/src/resources/images/users_family.png new file mode 100644 index 00000000..0eee2416 Binary files /dev/null and b/src/resources/images/users_family.png differ diff --git a/src/resources/images/users_into.png b/src/resources/images/users_into.png new file mode 100644 index 00000000..ccc448c0 Binary files /dev/null and b/src/resources/images/users_into.png differ diff --git a/src/resources/images/validate.gif b/src/resources/images/validate.gif new file mode 100644 index 00000000..53954b68 Binary files /dev/null and b/src/resources/images/validate.gif differ diff --git a/src/resources/images/view.png b/src/resources/images/view.png new file mode 100644 index 00000000..15f056ca Binary files /dev/null and b/src/resources/images/view.png differ diff --git a/src/resources/images/yellow-ball.png b/src/resources/images/yellow-ball.png new file mode 100644 index 00000000..ad09b5c5 Binary files /dev/null and b/src/resources/images/yellow-ball.png differ diff --git a/src/resources/jniwrap.dll b/src/resources/jniwrap.dll new file mode 100644 index 00000000..5413a61a Binary files /dev/null and b/src/resources/jniwrap.dll differ diff --git a/src/resources/jniwrap.lic b/src/resources/jniwrap.lic new file mode 100644 index 00000000..1630afcd --- /dev/null +++ b/src/resources/jniwrap.lic @@ -0,0 +1,11 @@ +Wed Feb 16 17:01:31 CET 2005 +Matt Tucker +Jive Software +NEVER +Runtime +Not for development +win32/x86 +iz1zyegnkagy74v7zf5tuvaun52k1shkh3iq4uypmtvi4vrxl9ynqx5c7lj2vcb7o2a4b7tsiht5u98j +gp42eo8v8e5ha02b57mqetm1akul7pbe2e6qqb5aibi8wdlmxax1dhar9yxasnd0odnv8ephr0hetk6x +Version: 2.x +Product: JNIWrapper diff --git a/src/resources/macros/TurbineMacros.vm b/src/resources/macros/TurbineMacros.vm new file mode 100644 index 00000000..59ffe9fd --- /dev/null +++ b/src/resources/macros/TurbineMacros.vm @@ -0,0 +1,111 @@ +## ==================================================================== +## The Apache Software License, Version 1.1 +## +## Copyright (c) 2001-2003 The Apache Software Foundation. All rights +## reserved. +## +## 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 end-user documentation included with the redistribution, +## if any, must include the following acknowledgment: +## "This product includes software developed by the +## Apache Software Foundation (http://www.apache.org/)." +## Alternately, this acknowledgment may appear in the software itself, +## if and wherever such third-party acknowledgments normally appear. +## +## 4. The names "Apache" and "Apache Software Foundation" and +## "Apache Turbine" must not be used to endorse or promote products +## derived from this software without prior written permission. For +## written permission, please contact apache@apache.org. +## +## 5. Products derived from this software may not be called "Apache", +## "Apache Turbine", nor may "Apache" appear in their name, without +## prior written permission of the Apache Software Foundation. +## +## THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED 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 APACHE SOFTWARE FOUNDATION OR +## ITS 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. +## ==================================================================== +## +## This software consists of voluntary contributions made by many +## individuals on behalf of the Apache Software Foundation. For more +## information on the Apache Software Foundation, please see +## . +## +## @author Henning P. Schmiedehausen +## @version $Id: TurbineMacros.vm 18715 2005-04-05 17:10:46 -0700 (Tue, 05 Apr 2005) bill $ + +## +## Build the ... tag of a web page with VelocityOnly Layout +## + +#macro (TurbineHtmlHead) + + #if ($!page.Title) + $page.Title + #end + #if ($page.MetaTags.size() > 0) + #foreach($metaTag in $page.MetaTags.keySet()) + + #end + #end + #if ($page.HttpEquivs.size() > 0) + #foreach($httpEquiv in $page.HttpEquivs.keySet()) + + #end + #end + #if ($page.StyleSheets.size() > 0) + #foreach( $styleSheet in $page.StyleSheets ) + + #end + #end + #if ($page.Styles.size() > 0) + + #end + #if ($page.Scripts.size() > 0) + #foreach( $script in $page.Scripts ) + + #end + #end + +#end + +## +## Build the Tags for the Body start tag of a web page with VelocityOnly Layout +## +## + +#macro (TurbineHtmlBodyAttributes) +#if ($page.BodyAttributes.size() > 0) + #foreach( $attributeName in $page.BodyAttributes.keySet() ) + $attributeName = "$page.BodyAttributes.get($attributeName)" + #end +#end +#end diff --git a/src/resources/sounds/bell.wav b/src/resources/sounds/bell.wav new file mode 100644 index 00000000..a69824de Binary files /dev/null and b/src/resources/sounds/bell.wav differ diff --git a/src/resources/sounds/chat_request.wav b/src/resources/sounds/chat_request.wav new file mode 100644 index 00000000..82cfa3fb Binary files /dev/null and b/src/resources/sounds/chat_request.wav differ diff --git a/src/resources/sounds/incoming.wav b/src/resources/sounds/incoming.wav new file mode 100644 index 00000000..00a00445 Binary files /dev/null and b/src/resources/sounds/incoming.wav differ diff --git a/src/resources/sounds/outgoing.wav b/src/resources/sounds/outgoing.wav new file mode 100644 index 00000000..c82405f2 Binary files /dev/null and b/src/resources/sounds/outgoing.wav differ diff --git a/src/resources/sounds/presence_changed.wav b/src/resources/sounds/presence_changed.wav new file mode 100644 index 00000000..9169dcaf Binary files /dev/null and b/src/resources/sounds/presence_changed.wav differ diff --git a/src/resources/startup.bat b/src/resources/startup.bat new file mode 100644 index 00000000..f737a3c5 --- /dev/null +++ b/src/resources/startup.bat @@ -0,0 +1 @@ +java -Dappdir=.. -cp ../lib/spark.jar;../lib/base.jar;../lib/smack.jar;../lib/windows/jdic.jar;../lib/smackx.jar;../lib/smackx-debug.jar;../lib/dom4j.jar;../lib/activation.jar;../lib/mail.jar;../lib/xpp.jar;../lib/xstream.jar;../resources org.jivesoftware.Spark \ No newline at end of file diff --git a/src/resources/systeminfo.dll b/src/resources/systeminfo.dll new file mode 100644 index 00000000..c8168695 Binary files /dev/null and b/src/resources/systeminfo.dll differ