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 }