001/* 002 * jPOS Project [http://jpos.org] 003 * Copyright (C) 2000-2026 jPOS Software SRL 004 * 005 * This program is free software: you can redistribute it and/or modify 006 * it under the terms of the GNU Affero General Public License as 007 * published by the Free Software Foundation, either version 3 of the 008 * License, or (at your option) any later version. 009 * 010 * This program is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 013 * GNU Affero General Public License for more details. 014 * 015 * You should have received a copy of the GNU Affero General Public License 016 * along with this program. If not, see <http://www.gnu.org/licenses/>. 017 */ 018 019package org.jpos.ui; 020 021import org.jdom2.Element; 022import org.jdom2.JDOMException; 023import org.jpos.util.Log; 024 025import javax.swing.*; 026import java.awt.*; 027import java.awt.event.ActionListener; 028import java.net.MalformedURLException; 029import java.net.URL; 030import java.util.*; 031 032/** 033 * Central controller for the jPOS Swing-based GUI; manages UI components and their factories. 034 * @author Alejandro Revilla 035 * 036 * <p>jPOS UI main class</p> 037 * 038 * @see UIFactory 039 * 040 * See src/examples/ui/* for usage details 041 */ 042@SuppressWarnings({"unchecked", "deprecation"}) 043public class UI implements UIFactory, UIObjectFactory { 044 JFrame mainFrame; 045 Map registrar, mapping; 046 Element config; 047 UIObjectFactory objFactory; 048 049 Log log; 050 boolean destroyed = false; 051 static final ResourceBundle classMapping; 052 053 static { 054 classMapping = ResourceBundle.getBundle(UI.class.getName()); 055 } 056 /** 057 * Create a new UI object 058 */ 059 public UI () { 060 super (); 061 registrar = new HashMap (); 062 mapping = new HashMap (); 063 setObjectFactory (this); 064 } 065 /** 066 * Creates a new UI object 067 * @param config configuration element 068 */ 069 public UI (Element config) { 070 this (); 071 setConfig(config); 072 } 073 /** 074 * Assigns an object factory use to create new object instances. 075 * If no object factory is asigned, UI uses the default classloader 076 * 077 * @param objFactory reference to an Object Factory 078 */ 079 public void setObjectFactory (UIObjectFactory objFactory) { 080 this.objFactory = objFactory; 081 } 082 /** 083 * Sets the XML configuration element for this UI. 084 * @param config the Configuration element 085 */ 086 public void setConfig (Element config) { 087 this.config = config; 088 } 089 /** 090 * Sets the optional Log instance used for diagnostic output. 091 * @param log an optional Log instance 092 * @see org.jpos.util.Log 093 */ 094 public void setLog (Log log) { 095 this.log = log; 096 } 097 /** 098 * Returns the Log instance, or null if none was set. 099 * @return the Log, or null 100 */ 101 public Log getLog () { 102 return log; 103 } 104 /** 105 * UI uses a map to hold references to its components 106 * ("id" attribute) 107 * 108 * @return UI component registrar 109 */ 110 public Map getRegistrar () { 111 return registrar; 112 } 113 /** 114 * Returns the component registered under the given id. 115 * @param id Component id ("id" configuration attribute) 116 * @return the Object or null 117 */ 118 public Object get (String id) { 119 return registrar.get (id); 120 } 121 /** 122 * UI is itself a UIFactory. 123 * This strategy is used to recursively instantiate components 124 * inside a container 125 * 126 * @param ui reference to this UI instance 127 * @param e free form configuration Element 128 * @return JComponent 129 */ 130 public JComponent create (UI ui, Element e) { 131 return create(e); 132 } 133 /** 134 * UIObjectFactory implementation. 135 * uses default classloader 136 * @param clazz the Clazzzz 137 * @return the Object 138 * @throws Exception if unable to instantiate 139 * @see #setLog 140 */ 141 /** 142 * Instantiates an object by class name using the current thread's context class loader. 143 * @param clazz fully qualified class name 144 * @return new instance 145 * @throws Exception if the class cannot be found or instantiated 146 */ 147 public Object newInstance (String clazz) throws Exception { 148 ClassLoader cl = Thread.currentThread().getContextClassLoader (); 149 Class type = cl.loadClass (clazz); 150 return type.newInstance(); 151 } 152 /** 153 * Configures this UI from the stored XML element. 154 * @throws JDOMException on XML processing error 155 */ 156 public void configure () throws JDOMException { 157 configure (config); 158 } 159 /** 160 * reconfigure can be used in order to re-configure components 161 * inside a container (i.e. changing a panel in response to 162 * an event). 163 * @see org.jpos.ui.action.Redirect 164 * 165 * @param elementName the element name used as new configuration 166 * @param panelName panel ID (see "id" attribute) 167 */ 168 public void reconfigure (String elementName, String panelName) { 169 Container c = 170 panelName == null ? mainFrame.getContentPane() : (JComponent) get (panelName); 171 if (c != null) { 172 c.removeAll (); 173 c.add ( 174 createComponent (config.getChild (elementName)) 175 ); 176 if (c instanceof JComponent) { 177 c.revalidate(); 178 } 179 c.repaint (); 180 } 181 } 182 /** 183 * dispose this UI object 184 */ 185 public void dispose () { 186 /* This is the right code for the dispose, but it freezes in 187 JVM running under WinXP (in linux went fine.. I didn't 188 test it under other OS's) 189 (last version tested: JRE 1.5.0-beta2) 190 191 if (mainFrame != null) { 192 // dumpComponent (mainFrame); 193 mainFrame.dispose (); 194 */ 195 destroyed = true; 196 197 Iterator it = Arrays.asList(Frame.getFrames()).iterator(); 198 199 while (it.hasNext()) { 200 JFrame jf = (JFrame) it.next(); 201 removeComponent(jf); 202 } 203 } 204 /** 205 * Returns true if this UI object has been disposed. 206 * @return true if this UI object has been disposed and is no longer valid 207 */ 208 public boolean isDestroyed () { 209 return destroyed; 210 } 211 212 /** 213 * Configures the UI from the given XML element. 214 * @param ui the root UI configuration element 215 * @throws JDOMException on XML processing error 216 */ 217 protected void configure (Element ui) throws JDOMException { 218 setLookAndFeel (ui); 219 createMappings (ui); 220 createObjects (ui, "object"); 221 createObjects (ui, "action"); 222 if (!"ui".equals (ui.getName())) { 223 ui = ui.getChild ("ui"); 224 } 225 if (ui != null) { 226 JFrame frame = initFrame (ui); 227 Element mb = ui.getChild ("menubar"); 228 if (mb != null) 229 frame.setJMenuBar (buildMenuBar (mb)); 230 231 frame.setContentPane ( 232 createComponent (ui.getChild ("components")) 233 ); 234 if ("true".equals (ui.getAttributeValue ("full-screen"))) { 235 GraphicsDevice device = GraphicsEnvironment 236 .getLocalGraphicsEnvironment() 237 .getDefaultScreenDevice(); 238 frame.setUndecorated ( 239 "true".equals (ui.getAttributeValue ("undecorated")) 240 ); 241 device.setFullScreenWindow(frame); 242 } else { 243 frame.show (); 244 } 245 } 246 } 247 248 private void removeComponent (Component c) { 249 if (c instanceof Container) { 250 Container cont = (Container) c; 251 Component[] cc = cont.getComponents(); 252 253 for (Component aCc : cc) { 254 removeComponent(aCc); 255 } 256 cont.removeAll(); 257 } 258 } 259 260 // ##DEBUG## 261 private void dumpComponent (Component c) { 262 System.out.println (c.getClass().getName() + ":" + c.getBounds().getSize().toString()); 263 if (c instanceof Container) { 264 Component[] cc = ((Container) c).getComponents(); 265 for (Component aCc : cc) { 266 dumpComponent(aCc); 267 } 268 } 269 } 270 271 private JFrame initFrame (Element ui) { 272 Element caption = ui.getChild ("caption"); 273 mainFrame = caption == null ? 274 new JFrame () : 275 new JFrame (caption.getText()); 276 277 JOptionPane.setRootFrame (mainFrame); 278 279 mainFrame.getContentPane().setLayout(new BorderLayout()); 280 281 String close = ui.getAttributeValue ("close"); 282 283 if ("false".equals (close)) 284 mainFrame.setDefaultCloseOperation (JFrame.DO_NOTHING_ON_CLOSE); 285 else if ("exit".equals (close)) 286 mainFrame.setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE); 287 288 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); 289 mainFrame.setSize(getDimension (ui, screenSize)); 290 locateOnScreen(mainFrame); 291 return mainFrame; 292 } 293 294 private void locateOnScreen(Frame frame) { 295 Dimension paneSize = frame.getSize(); 296 Dimension screenSize = frame.getToolkit().getScreenSize(); 297 frame.setLocation( 298 (screenSize.width - paneSize.width) / 2, 299 (screenSize.height - paneSize.height) / 2); 300 } 301 private JMenuBar buildMenuBar (Element ui) { 302 JMenuBar mb = new JMenuBar (); 303 Iterator iter = ui.getChildren("menu").iterator(); 304 while (iter.hasNext()) 305 mb.add (menu ((Element) iter.next())); 306 307 return mb; 308 } 309 private JMenu menu (Element m) { 310 JMenu menu = new JMenu (m.getAttributeValue ("id")); 311 setItemAttributes (menu, m); 312 Iterator iter = m.getChildren ().iterator(); 313 while (iter.hasNext()) 314 addMenuItem(menu, (Element) iter.next()); 315 return menu; 316 } 317 private void addMenuItem (JMenu menu, Element m) { 318 String tag = m.getName (); 319 320 if ("menuitem".equals (tag)) { 321 JMenuItem item = new JMenuItem (m.getAttributeValue ("id")); 322 setItemAttributes (item, m); 323 menu.add (item); 324 } else if ("menuseparator".equals (tag)) { 325 menu.addSeparator (); 326 } else if ("button-group".equals (tag)) { 327 addButtonGroup (menu, m); 328 } else if ("check-box".equals (tag)) { 329 JCheckBoxMenuItem item = new JCheckBoxMenuItem ( 330 m.getAttributeValue ("id") 331 ); 332 setItemAttributes (item, m); 333 item.setState ( 334 "true".equals (m.getAttributeValue ("state")) 335 ); 336 menu.add (item); 337 } else if ("menu".equals (tag)) { 338 menu.add (menu (m)); 339 } 340 } 341 private void addButtonGroup (JMenu menu, Element m) { 342 ButtonGroup group = new ButtonGroup(); 343 Iterator iter = m.getChildren ("radio-button").iterator(); 344 while (iter.hasNext()) { 345 addRadioButton (menu, group, (Element) iter.next()); 346 } 347 } 348 private void addRadioButton (JMenu menu, ButtonGroup group, Element m) { 349 JRadioButtonMenuItem item = new JRadioButtonMenuItem 350 (m.getAttributeValue ("id")); 351 setItemAttributes (item, m); 352 item.setSelected ( 353 "true".equals (m.getAttributeValue ("selected")) 354 ); 355 group.add (item); 356 menu.add (item); 357 } 358 private Dimension getDimension (Element e, Dimension def) { 359 String w = e.getAttributeValue ("width"); 360 String h = e.getAttributeValue ("height"); 361 362 return new Dimension ( 363 w != null ? Integer.parseInt (w) : def.width, 364 h != null ? Integer.parseInt (h) : def.height 365 ); 366 } 367 private void setItemAttributes (AbstractButton b, Element e) 368 { 369 String s = e.getAttributeValue ("accesskey"); 370 if (s != null && s.length() == 1) 371 b.setMnemonic (s.charAt(0)); 372 373 String icon = e.getAttributeValue ("icon"); 374 if (icon != null) { 375 try { 376 b.setIcon (new ImageIcon (new URL (icon))); 377 } catch (MalformedURLException ex) { 378 ex.printStackTrace (); 379 } 380 } 381 b.setActionCommand (e.getAttributeValue ("command")); 382 String actionId = e.getAttributeValue ("action"); 383 if (actionId != null) { 384 b.addActionListener ((ActionListener) get (actionId)); 385 } 386 } 387 /** 388 * Applies the look-and-feel specified in the UI configuration element. 389 * @param ui the UI configuration element 390 */ 391 protected void setLookAndFeel (Element ui) { 392 String laf = ui.getAttributeValue ("look-and-feel"); 393 if (laf != null) { 394 try { 395 UIManager.setLookAndFeel (laf); 396 } catch (Exception e) { 397 warn (e); 398 } 399 } 400 } 401 private JComponent createComponent (Element e) { 402 if (e == null) 403 return new JPanel (); 404 405 JComponent component; 406 UIFactory factory = null; 407 String clazz = e.getAttributeValue ("class"); 408 if (clazz == null) 409 clazz = (String) mapping.get (e.getName()); 410 if (clazz == null) { 411 try { 412 clazz = classMapping.getString (e.getName()); 413 } catch (MissingResourceException ignored) { 414 // OK to happen on components handled by this factory 415 } 416 } 417 try { 418 if (clazz == null) 419 factory = this; 420 else 421 factory = (UIFactory) objFactory.newInstance (clazz.trim()); 422 423 component = factory.create (this, e); 424 setSize (component, e); 425 if (component instanceof AbstractButton) { 426 AbstractButton b = (AbstractButton) component; 427 b.setActionCommand (e.getAttributeValue ("command")); 428 String actionId = e.getAttributeValue ("action"); 429 if (actionId != null) { 430 b.addActionListener ((ActionListener) get (actionId)); 431 } 432 } 433 put (component, e); 434 435 Element script = e.getChild ("script"); 436 if (script != null) 437 component = doScript (component, script); 438 439 if ("true".equals (e.getAttributeValue ("scrollable"))) 440 component = new JScrollPane (component); 441 } catch (Exception ex) { 442 warn ("Error instantiating class " + clazz); 443 warn (ex); 444 component = new JLabel ("Error instantiating class " + clazz); 445 } 446 return component; 447 } 448 /** 449 * Applies any script element to the component; default implementation is a no-op. 450 * @param component the target component 451 * @param e the script XML element 452 * @return the component after script application 453 */ 454 protected JComponent doScript (JComponent component, Element e) { 455 return component; 456 } 457 private void setSize (JComponent c, Element e) { 458 String w = e.getAttributeValue ("width"); 459 String h = e.getAttributeValue ("height"); 460 Dimension d = c.getPreferredSize (); 461 double dw = d.getWidth (); 462 double dh = d.getHeight (); 463 if (w != null) 464 dw = Double.parseDouble (w); 465 if (h != null) 466 dh = Double.parseDouble (h); 467 if (w != null || h != null) { 468 d.setSize (dw, dh); 469 c.setPreferredSize (d); 470 } 471 } 472 /** 473 * Creates a Swing component from the given XML element descriptor. 474 * @param e the XML element describing the component 475 * @return the created JComponent, or {@code null} if none was produced 476 */ 477 public JComponent create (Element e) { 478 JComponent component = null; 479 480 Iterator iter = e.getChildren().iterator(); 481 for (int i=0; iter.hasNext(); i++) { 482 JComponent c = createComponent((Element) iter.next ()); 483 if (i == 0) 484 component = c; 485 else if (i == 1) { 486 JPanel p = new JPanel (); 487 p.add (component); 488 p.add (c); 489 component = p; 490 put (component, e); 491 } else { 492 component.add (c); 493 } 494 } 495 return component; 496 } 497 /** 498 * Returns the application's main frame. 499 * @return the main JFrame 500 */ 501 public JFrame getMainFrame() { 502 return mainFrame; 503 } 504 505 private void createObjects (Element e, String name) { 506 Iterator iter = e.getChildren (name).iterator (); 507 while (iter.hasNext()) { 508 try { 509 Element ee = (Element) iter.next (); 510 String clazz = ee.getAttributeValue ("class"); 511 Object obj = objFactory.newInstance (clazz.trim()); 512 if (obj instanceof UIAware) { 513 ((UIAware) obj).setUI (this, ee); 514 } 515 put (obj, ee); 516 } catch (Exception ex) { 517 warn (ex); 518 } 519 } 520 } 521 private void createMappings (Element e) { 522 Iterator iter = e.getChildren ("mapping").iterator (); 523 while (iter.hasNext()) { 524 try { 525 Element ee = (Element) iter.next (); 526 String name = ee.getAttributeValue ("name"); 527 String clazz = ee.getAttributeValue("factory"); 528 mapping.put(name, clazz); 529 } catch (Exception ex) { 530 warn (ex); 531 } 532 } 533 } 534 /** 535 * Logs a warning. 536 * @param obj the warning object 537 */ 538 protected void warn (Object obj) { 539 if (log != null) 540 log.warn (obj); 541 } 542 /** 543 * Logs a warning with an associated exception. 544 * @param obj the warning object 545 * @param ex the associated exception 546 */ 547 protected void warn (Object obj, Exception ex) { 548 if (log != null) 549 log.warn (obj, ex); 550 } 551 552 private void put (Object obj, Element e) { 553 String id = e.getAttributeValue ("id"); 554 if (id != null) { 555 registrar.put (id, obj); 556 } 557 } 558} 559