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 @@
+
+
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 |
+
+
+ +
| 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 Installation Guide |
+
Spark consists of the following: + +
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: + +
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.
+ + ++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. +
+ +
+
+
+
+
+Plugins are shipped as compressed JAR (Java Archive) files. The files in a plugin archive are as follows:
+ + + ++The plugin.xml file specifies the main Plugin class. A sample +file might look like the following: +
+ + + ++You only need to drop your newly created jar file into the plugins directory of your Spark client install. + +
+ + + + +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. +
+ ++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: + +
+ ++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.
+ + + ++All code samples can be found in the examples.jar file located here. +
+ +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 SetMainWindow,
+ * 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 ListAlerter 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:
+ *
+ *
+ *
+ * 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:
+ *
+ * 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:
+ *
+ * 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 ListCollapsiblePane
+ * 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 MapTransferListener 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; + +/** + * ThePreference 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 + "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 + "" + getNickname() + " " +
+ "JID: " + jid + " " +
+ "Status: " + status + " " + subscriptionStatus + awayText +
+ "
";
+ }
+
+
+ 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("").append(value).append("").append(from).append(": ").append(body).append(" ");
+
+ }
+ buf.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("").append(getElementName()).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:
+ *
+ * - If
o1 and o2 are the same object
+ * according to the == operator, return
+ * true.
+ * - Otherwise, if either
o1 or o2 is
+ * null, return false.
+ * - 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:
+ *
+ * - If
b1 and b2 are both TRUE or
+ * neither b1 nor b2 is TRUE,
+ * return true.
+ * - 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:
+ *
+ * - If
o1 and o2 are the same object
+ * according to the == operator, return
+ * false.
+ * - Otherwise, if either
o1 or o2 is
+ * null, return true.
+ * - 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:
+ *
+ * - If
b1 and b2 are both TRUE or
+ * neither b1 nor b2 is TRUE,
+ * return false.
+ * - 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("").append(getElementName()).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("").append(getElementName()).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 + ">")) {
+ key = (String)iter.next();
+ String value = (String)settingsMap.get(key);
+ buf.append("");
+ buf.append("<" + key + ">");
+ buf.append(value);
+ }
+
+ buf.append("" + getElementName() + ">");
+ 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