001    /*
002        Copyright (C) 2003 Adam Olsen
003    
004            This program is free software; you can redistribute it and/or modify
005        it under the terms of the GNU General Public License as published by
006        the Free Software Foundation; either version 1, or (at your option)
007        any later version.
008    
009        This program is distributed in the hope that it will be useful,
010        but WITHOUT ANY WARRANTY; without even the implied warranty of
011        MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
012        GNU General Public License for more details.
013    
014        You should have received a copy of the GNU General Public License
015        along with this program; if not, write to the Free Software
016        Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
017    */
018    
019    package com.valhalla.jbother;
020    
021    import java.awt.*;
022    import java.awt.event.*;
023    
024    import javax.swing.*;
025    import javax.swing.event.*;
026    import javax.swing.table.*;
027    
028    import org.jivesoftware.smackx.*;
029    import org.jivesoftware.smackx.packet.*;
030    import org.jivesoftware.smack.*;
031    
032    import java.util.*;
033    import com.valhalla.jbother.*;
034    import com.valhalla.gui.*;
035    
036    /**
037     * For browsing Jabber services
038     *
039     * Displays a dialog for browsing services on Jabber
040     * entities such as servers according to JEP-0030
041     *
042     * @author Adam olsen
043     * @version 1.0
044    **/
045    public class ServiceDiscoveryDialog extends JDialog
046    {
047            private ResourceBundle resources = ResourceBundle.getBundle( "JBotherBundle", Locale.getDefault() );
048            private String host;
049            private JButton closeButton = new JButton( resources.getString( "closeButton" ) );
050            private JButton searchButton = new JButton( resources.getString( "search" ) );
051            private ServiceTableModel tableModel = new ServiceTableModel( this );
052            private JTable table = new JTable( tableModel );
053            private JLabel title = new JLabel( resources.getString( "serviceDiscoveryManager" ) );
054            private JTextField serverField = new JTextField();
055            private JLabel status = new JLabel( resources.getString( "status" ) + ": " );
056            private JPanel bottomPanel = new JPanel();
057            private JPanel middlePanel = new JPanel( new BorderLayout( 5, 5 ) );
058            private JPanel topPanel = new JPanel();
059            private JLabel hostLabel = new JLabel( resources.getString( "host" ) + ": " );
060            private ServiceDiscoveryThread currentThread = null;
061            private ServiceDiscoveryDialog thisPointer = this;
062            private TablePopupMenu popupMenu = new TablePopupMenu( this, table );
063    
064            /**
065             * Creates a ServiceDiscoveryDialog with parent as it's parent
066             * @param parent the JFrame that owns this dialog
067            **/
068            public ServiceDiscoveryDialog( JFrame parent )
069            {
070                    super( parent );
071                    setTitle( resources.getString( "serviceDiscovery" ) );
072    
073                    initComponents();
074            }
075    
076            /**
077             * Sets up the Dialog layout
078             **/
079            private void initComponents()
080            {
081                    tableModel.setTable( table );
082                    JPanel panel = (JPanel)getContentPane();
083    
084                    panel.setLayout( new BorderLayout( 5, 5 ) );
085                    panel.setBorder( BorderFactory.createEmptyBorder( 5, 5, 5, 5 ) );
086    
087                    JScrollPane scrollPane = new JScrollPane( table );
088                    scrollPane.getViewport().setBackground( Color.WHITE );
089    
090                    topPanel.setLayout( new BorderLayout( 5, 5 ) );
091                    topPanel.add( hostLabel, BorderLayout.WEST );
092                    topPanel.add( serverField, BorderLayout.CENTER );
093    
094                    middlePanel.add( topPanel, BorderLayout.NORTH );
095    
096                    middlePanel.add( scrollPane, BorderLayout.CENTER );
097    
098                    panel.add( title, BorderLayout.NORTH );
099                    table.setBorder( BorderFactory.createEtchedBorder() );
100                    panel.add( middlePanel, BorderLayout.CENTER );
101    
102                    bottomPanel.setLayout( new BoxLayout( bottomPanel, BoxLayout.X_AXIS ) );
103                    bottomPanel.add( status );
104    
105                    bottomPanel.add( Box.createHorizontalGlue() );
106                    bottomPanel.add( searchButton );
107                    bottomPanel.add( closeButton );
108    
109                    panel.add( bottomPanel, BorderLayout.SOUTH );
110                    pack();
111                    setSize( new Dimension( 600, 300 ) );
112    
113                    if( BuddyList.getInstance().checkConnection() ) serverField.setText( BuddyList.getInstance().getConnection().getHost() );
114    
115                    ActionListener listener = new ActionListener()
116                    {
117                            public void actionPerformed( ActionEvent e )
118                            {
119                                    runServiceDiscovery( null );
120                            }
121                    };
122    
123                    serverField.addActionListener( listener );
124                    searchButton.addActionListener( listener );
125    
126                    Standard.cascadePlacement( this );
127    
128                    closeButton.addActionListener( new ActionListener()
129                    {
130                            public void actionPerformed( ActionEvent e ) { dispose(); }
131                    } );
132    
133                    table.addMouseListener( new PopupMouseListener() );
134    
135                    runServiceDiscovery( null );
136            }
137    
138            /**
139             * Listens for mouse events
140             * @author Adam Olsen
141             * @version 1.0
142             **/
143            class PopupMouseListener extends MouseAdapter
144            {
145                    public void mousePressed( MouseEvent e ) { checkPop( e ); }
146                    public void mouseReleased( MouseEvent e ) { checkPop( e ); }
147                    public void mouseClicked( MouseEvent e )
148                    {
149                            if( e.getClickCount() >= 2 )
150                            {
151                                    int row = table.getSelectedRow();
152                                    if( row < 0 ) return;
153    
154                                    String id = (String)tableModel.getValueAt( row, 1 );
155                                    runServiceDiscovery( id );
156                            }
157                            else checkPop( e );
158                    }
159    
160                    public void checkPop( MouseEvent e )
161                    {
162                            if( e.isPopupTrigger() )
163                            {
164                                    popupMenu.popup( e );
165                            }
166                    }
167            }
168    
169            /**
170             * Starts the service discovery thread on the specified server
171             * @param server the server to run discovery on
172             **/
173            protected void runServiceDiscovery( String server )
174            {
175                    if( !BuddyList.getInstance().checkConnection() )
176                    {
177                            BuddyList.getInstance().connectionError();
178                            return;
179                    }
180    
181                    if( server != null ) serverField.setText( server );
182                    if( serverField.getText().equals( "" ) )
183                    {
184                            Toolkit.getDefaultToolkit().beep();
185                            return;
186                    }
187    
188                    status.setText( resources.getString( "status" ) + ": " + resources.getString( "collecting" ) + " ..." );
189                    tableModel.clear();
190    
191                    if( currentThread != null ) currentThread.abortDiscovery();
192                    currentThread = new ServiceDiscoveryThread();
193                    new Thread( currentThread ).start();
194            }
195    
196            /**
197             * The thread that actually collects disco information about the server
198             * @author Adam Olsen
199             * @version 1.0
200             **/
201            class ServiceDiscoveryThread implements Runnable
202            {
203                    private boolean stopped = false;
204                    private ArrayList discoItems = new ArrayList();
205                    public void run()
206                    {
207                            if( !BuddyList.getInstance().checkConnection() )
208                            {
209                                    BuddyList.getInstance().connectionError();
210                                    return;
211                            }
212    
213                            ServiceDiscoveryManager manager = new ServiceDiscoveryManager( BuddyList.getInstance().getConnection() );
214    
215                            // get the discover items for the server
216                            try {
217                                    DiscoverItems items = manager.discoverItems( serverField.getText() );
218                                    Iterator i = items.getItems();
219    
220                                    String top[] = new String[] { serverField.getText(), serverField.getText(), "", "", "" };
221                                    discoItems.add( top );
222                                    tableModel.addItem( top );
223    
224                                    while( i.hasNext() )
225                                    {
226                                            DiscoverItems.Item item = (DiscoverItems.Item)i.next();
227                                            if( stopped ) return;
228    
229                                            final String[] entry = new String[] { item.getName(), item.getEntityID(), "", item.getNode(), item.getAction() };
230                                            discoItems.add( entry );
231    
232                                            SwingUtilities.invokeLater( new Runnable()
233                                            {
234                                                    public void run()
235                                                    {
236                                                            tableModel.addItem( entry );
237                                                    }
238                                            } );
239                                    }
240    
241                                    for( int icount = 0; icount < discoItems.size(); icount++ )
242                                    {
243                                            String[] entry = (String[])discoItems.get( icount );
244                                            String id = entry[1];
245    
246                                            status.setText( resources.getString( "status" ) + ": " +
247                                                    resources.getString( "gettingFeatures" ) + " (" + id + ") ..." );
248    
249                                            // get the discover info about each item
250                                            DiscoverInfo info = null;
251    
252                                            try {
253                                                    info = manager.discoverInfo( id );
254                                            }
255                                            catch( XMPPException e ) { }
256    
257                                            // if the service discovery has been aborted, bail out
258                                            if( stopped ) return;
259                                            tableModel.setDisco( icount, info );
260    
261                                            if( info != null )
262                                            {
263                                                    final DiscoverInfo tempInfo = info;
264                                                    final int index = icount;
265                                                    SwingUtilities.invokeLater( new Runnable()
266                                                    {
267                                                            public void run()
268                                                            {
269                                                                    Iterator identities = tempInfo.getIdentities();
270                                                                    while( identities.hasNext() )
271                                                                    {
272                                                                            DiscoverInfo.Identity identity = (DiscoverInfo.Identity)identities.next();
273                                                                            if( stopped ) return;
274                                                                            // set the table information
275                                                                            tableModel.setItemInfo( index, identity.getName(), identity.getCategory() );
276                                                                    }
277                                                            }
278                                                    } );
279                                            }
280                                    }
281    
282                            }
283                            catch( XMPPException e )
284                            {
285                                    status.setText( resources.getString( "error" ) + ": " + e.getMessage() );
286                                    return;
287                            }
288    
289                            SwingUtilities.invokeLater( new Runnable()
290                            {
291                                    public void run()
292                                    {
293                                            status.setText( resources.getString( "status" ) + ": " + resources.getString( "completed" ) + "." );
294                                    }
295                            } );
296                    }
297    
298                    /**
299                     * Aborts the service discovery
300                     **/
301                    public void abortDiscovery() { this.stopped = true; }
302            }
303    }
304    
305    /**
306     * The popup menu for each of the disco items
307     * @author Adam Olsen
308     * @version 1.0
309     **/
310    class TablePopupMenu extends JPopupMenu
311    {
312            private ResourceBundle resources = ResourceBundle.getBundle( "JBotherBundle", Locale.getDefault() );
313            private JTable table;
314            private ServiceTableModel model;
315            private ServiceDiscoveryDialog dialog;
316            private JMenuItem browseItem = new JMenuItem( resources.getString( "browse" ) );
317            private JMenuItem registerItem = new JMenuItem( resources.getString( "register" ) );
318            private JMenuItem addItem = new JMenuItem( resources.getString( "addToRoster" ) );
319    
320            /**
321             * Default constructor
322             * @param dialog The ServiceDiscoveryDialog to connect this popup menu to
323             * @param table the table to connect this menu to
324             **/
325            public TablePopupMenu( ServiceDiscoveryDialog dialog, JTable table )
326            {
327                    this.dialog = dialog; this.table = table;
328                    model = (ServiceTableModel)table.getModel();
329                    final TablePopupMenu thisPointer = this;
330    
331                    add( addItem );
332                    add( browseItem );
333                    add( registerItem );
334    
335                    // show the add buddy dialog
336                    addItem.addActionListener( new ActionListener()
337                    {
338                            public void actionPerformed( ActionEvent e )
339                            {
340                                    AddBuddyDialog dialog = new AddBuddyDialog();
341                                    String id = getId();
342                                    dialog.setBuddyId( id );
343                                    dialog.setVisible( true );
344                            }
345                    } );
346    
347                    // the browse item runs service discovery on that item
348                    browseItem.addActionListener( new ActionListener()
349                    {
350                            public void actionPerformed( ActionEvent e )
351                            {
352                                    String id = getId();
353                                    thisPointer.dialog.runServiceDiscovery( id );
354                            }
355                    } );
356    
357                    // the register item forms a RegistrationForm for that item
358                    registerItem.addActionListener( new ActionListener()
359                    {
360                            public void actionPerformed( ActionEvent e )
361                            {
362                                    String id = getId();
363                                    new RegistrationForm( id ).getRegistrationInfo();
364                            }
365                    } );
366            }
367    
368            /**
369             * Returns the ID of the selected row
370             * @return the id of the row
371             **/
372            private String getId()
373            {
374                    int row = this.table.getSelectedRow();
375                    if( row < 0 ) return "";
376    
377                    String id = (String)model.getValueAt( row, 1 );
378                    return id;
379            }
380    
381            /**
382             * Shows the popup menu
383             * @param e the mouse event
384             **/
385            public void popup( MouseEvent e )
386            {
387                    int selectedRow = table.rowAtPoint( e.getPoint() );
388                    if( selectedRow < 0 ) return;
389                    table.setRowSelectionInterval( selectedRow, selectedRow );
390    
391                    /*String features = model.getFeatures( selectedRow );
392                    if( features.indexOf( "r" ) > -1 ) registerItem.setEnabled( true );
393                    else registerItem.setEnabled( false );*/
394    
395                    show( table, e.getX(), e.getY() );
396            }
397    }
398    
399    /**
400     * The table model for the ServiceDiscoveryDialog table
401     * @author Adam Olsen
402     * @version 1.0
403    **/
404    class ServiceTableModel extends AbstractTableModel
405    {
406            private ServiceDiscoveryDialog dialog;
407            private JTable table;
408            private ArrayList items = new ArrayList();
409            private String[] columns = new String[] { "Name", "JID", "Category", "Node", "Action" };
410            private ArrayList infos = new ArrayList();
411    
412            /**
413             * Sets the table value for this model
414             * @param table the table that this model represents
415            **/
416            public void setTable( JTable table )
417            {
418                    this.table = table;
419    
420                    TableColumn column = null;
421    
422                    // set the default column widths
423                    for( int i = 0; i < getColumnCount(); i++)
424                    {
425                            column = table.getColumnModel().getColumn( i );
426                            if( i < 2 ) column.setPreferredWidth( 150 ); // sport column is bigger
427                            else column.setPreferredWidth( 50 );
428                    }
429            }
430    
431            /**
432             * Default Constructor
433             * @param dialog the ServiceDiscoveryDialog that contains this table
434            */
435            public ServiceTableModel( ServiceDiscoveryDialog dialog ) { this.dialog = dialog; }
436    
437            /**
438             * gets the number of columns in this table
439             * @return the number of columns
440            */
441            public int getColumnCount() { return columns.length; }
442    
443            /**
444             * Gets the number of rows in the table
445             * @return the number of rows in the table
446            */
447            public int getRowCount() { return items.size(); }
448    
449            /**
450             * Returns the name of a specific column
451             * @param column the column who's name is wanted
452             * @return the name of the column
453            */
454            public String getColumnName( int column ) { return columns[column]; }
455    
456            /**
457             * Get the Object for a specific coordinate in the table
458             * @param row the row of the item
459             * @param column the column of the item
460             * @return the Object at the specified coordinates
461            */
462            public Object getValueAt( int row, int column )
463            {
464                    synchronized( items )
465                    {
466                            String[] item = (String[])items.get( row );
467                            return item[column];
468                    }
469            }
470    
471            /**
472             * gets the features of a specific item (row)
473             * @param row   the index of the row you want information for
474             * @return      a string containing all of the features the row supports
475            */
476            public String getFeatures( int row )
477            {
478                    synchronized( infos )
479                    {
480                            String info = "";
481    
482                            try {
483                                    info = (String)infos.get( row );
484                            }
485                            catch( Exception e ) { }
486    
487                            return info;
488                    }
489            }
490    
491            /**
492             * Sets the disco information once it's found
493             * @param index the index of the row you want to set the information about
494             * @param disco the information about the row
495            */
496            public void setDisco( int index, DiscoverInfo disco )
497            {
498                    synchronized( infos )
499                    {
500                            String info = "";
501                            if( disco != null && disco.containsFeature( "jabber:iq:register" ) ) info += "r";
502                            infos.add( index, info );
503                    }
504            }
505    
506            /**
507             * Adds a row to the table
508             * @param item  the array containing the item to add
509            */
510            public void addItem( String[] item )
511            {
512                    synchronized( items )
513                    {
514                            items.add( item );
515                            fireTableRowsInserted( items.size(), items.size() );
516                    }
517            }
518    
519            /**
520             * Sets information about a specific row
521             * @param row the row to set
522             * @param name the new name
523             * @param category the category of the row
524            */
525            public void setItemInfo( int row, String name, String category )
526            {
527                    synchronized( items )
528                    {
529                            String[] item = (String[])items.get( row );
530                            item[0] = name;
531                            item[2] = category;
532    
533                            fireTableRowsUpdated( row, row );
534                    }
535            }
536    
537            /**
538             * Clears the table
539            */
540            public void clear()
541            {
542                    items.clear();
543                    infos.clear();
544                    table.tableChanged( new TableModelEvent( this ) );
545            }
546    }