View Javadoc

1   /*
2    * jcurl java curling software framework https://JCurl.mro.name Copyright (C)
3    * 2005-2009 M. Rohrmoser
4    * 
5    * This program is free software; you can redistribute it and/or modify it under
6    * the terms of the GNU General Public License as published by the Free Software
7    * Foundation; either version 2 of the License, or (at your option) any later
8    * version.
9    * 
10   * This program is distributed in the hope that it will be useful, but WITHOUT
11   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12   * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
13   * details.
14   * 
15   * You should have received a copy of the GNU General Public License along with
16   * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
17   * Place, Suite 330, Boston, MA 02111-1307 USA
18   */
19  
20  package org.jcurl.demo.smack;
21  
22  import java.awt.BorderLayout;
23  import java.awt.Frame;
24  import java.awt.GridBagConstraints;
25  import java.awt.GridBagLayout;
26  import java.awt.Insets;
27  import java.io.File;
28  import java.io.IOException;
29  import java.io.InputStream;
30  import java.util.Properties;
31  import java.util.regex.Matcher;
32  import java.util.regex.Pattern;
33  
34  import javax.swing.Box;
35  import javax.swing.JButton;
36  import javax.swing.JCheckBoxMenuItem;
37  import javax.swing.JComponent;
38  import javax.swing.JDialog;
39  import javax.swing.JFileChooser;
40  import javax.swing.JLabel;
41  import javax.swing.JMenu;
42  import javax.swing.JMenuBar;
43  import javax.swing.JMenuItem;
44  import javax.swing.JPanel;
45  import javax.swing.JScrollPane;
46  import javax.swing.JSeparator;
47  import javax.swing.JSplitPane;
48  import javax.swing.JTextField;
49  import javax.swing.ScrollPaneConstants;
50  import javax.swing.SwingUtilities;
51  import javax.swing.border.EmptyBorder;
52  import javax.swing.filechooser.FileFilter;
53  
54  import org.apache.commons.logging.Log;
55  import org.jcurl.core.api.MutableObject;
56  import org.jcurl.core.log.JCLoggerFactory;
57  import org.jcurl.core.ui.FileNameExtensionFilter;
58  import org.jcurl.demo.tactics.JCurlShotPlanner;
59  import org.jdesktop.application.Action;
60  import org.jdesktop.application.ApplicationContext;
61  import org.jdesktop.application.ResourceMap;
62  import org.jdesktop.application.SingleFrameApplication;
63  import org.jdesktop.application.Task.BlockingScope;
64  import org.jivesoftware.smack.ChatManager;
65  import org.jivesoftware.smack.XMPPConnection;
66  import org.jivesoftware.smack.XMPPException;
67  
68  /**
69   * A very simple Jabber/Xmpp client. This a merely an experiment to gain
70   * experience.
71   * <p>
72   * The goal is to add collaborative editing to and instant messaging e.g.
73   * {@link JCurlShotPlanner}. <a
74   * href="http://en.wikipedia.org/wiki/J.C.R._Licklider">J. C. R. Licklider</a>
75   * suggested this <b>1968</b> in his paper <a
76   * href="http://gatekeeper.dec.com/pub/DEC/SRC/publications/taylor/licklider-taylor.pdf">The
77   * Computer as a Communication Device</a>. So much about the "fast-lived" IT
78   * business.
79   * </p>
80   * 
81   * @author <a href="mailto:JCurl@mro.name">M. Rohrmoser </a>
82   * @version $Id: JCurlSmackClient.java 1031 2009-07-23 15:06:05Z mro $
83   */
84  public class JCurlSmackClient extends SingleFrameApplication {
85  	static class GuiUtil {
86  
87  		private static final Pattern check = Pattern.compile("\\[\\](.+)");
88  		private static final Insets zeroInsets = new Insets(0, 0, 0, 0);
89  		private final ApplicationContext act;
90  
91  		public GuiUtil(final ApplicationContext act) {
92  			this.act = act;
93  		}
94  
95  		/**
96  		 * Create a simple about box JDialog that displays the standard
97  		 * Application resources, like {@code Application.title} and
98  		 * {@code Application.description}. The about box's labels and fields
99  		 * are configured by resources that are injected when the about box is
100 		 * shown (see SingleFrameApplication#show). The resources are defined in
101 		 * the application resource file: resources/DocumentExample.properties.
102 		 * 
103 		 * From:
104 		 * https://appframework.dev.java.net/downloads/AppFramework-1.03-src.zip
105 		 * DocumentExample
106 		 */
107 		private JDialog createAboutBox(final Frame owner) {
108 			final JPanel panel = new JPanel(new GridBagLayout());
109 			panel.setBorder(new EmptyBorder(0, 28, 16, 28)); // top, left,
110 			// bottom, right
111 			final JLabel titleLabel = new JLabel();
112 			titleLabel.setName("aboutTitleLabel");
113 			final GridBagConstraints c = new GridBagConstraints();
114 			initGridBagConstraints(c);
115 			c.anchor = GridBagConstraints.WEST;
116 			c.gridwidth = GridBagConstraints.REMAINDER;
117 			c.fill = GridBagConstraints.HORIZONTAL;
118 			c.ipady = 32;
119 			c.weightx = 1.0;
120 			panel.add(titleLabel, c);
121 			final String[] fields = { "description", "version", "vendor",
122 					"home" };
123 			for (final String field : fields) {
124 				final JLabel label = new JLabel();
125 				label.setName(field + "Label");
126 				initGridBagConstraints(c);
127 				// c.anchor = GridBagConstraints.BASELINE_TRAILING; 1.6 ONLY
128 				c.anchor = GridBagConstraints.EAST;
129 				panel.add(label, c);
130 				initGridBagConstraints(c);
131 				c.weightx = 1.0;
132 				c.gridwidth = GridBagConstraints.REMAINDER;
133 				c.fill = GridBagConstraints.HORIZONTAL;
134 				final JTextField textField = new JTextField();
135 				textField.setName(field + "TextField");
136 				textField.setEditable(false);
137 				textField.setBorder(null);
138 				panel.add(textField, c);
139 			}
140 			final JButton closeAboutButton = new JButton();
141 			closeAboutButton.setAction(findAction("closeAboutBox"));
142 			initGridBagConstraints(c);
143 			c.anchor = GridBagConstraints.EAST;
144 			c.gridx = 1;
145 			panel.add(closeAboutButton, c);
146 			final JDialog dialog = new JDialog(owner);
147 			dialog.setName("aboutDialog");
148 			dialog.add(panel, BorderLayout.CENTER);
149 			return dialog;
150 		}
151 
152 		private JFileChooser createFileChooser(final File base,
153 				final String resourceName, final FileFilter filter) {
154 			final JFileChooser fc = new JFileChooser(base);
155 			fc.setName(resourceName);
156 			fc.setMultiSelectionEnabled(false);
157 			fc.setAcceptAllFileFilterUsed(true);
158 			fc.setFileFilter(filter);
159 			getContext().getResourceMap().injectComponents(fc);
160 			return fc;
161 		}
162 
163 		private FileNameExtensionFilter createFileFilter(
164 				final String resourceName, final String... extensions) {
165 			final ResourceMap appResourceMap = getContext().getResourceMap();
166 			final String key = resourceName + ".description";
167 			final String desc = appResourceMap.getString(key);
168 			return new FileNameExtensionFilter(desc == null ? key : desc,
169 					extensions);
170 		}
171 
172 		private JMenu createMenu(final String menuName,
173 				final String[] actionNames) {
174 			return createMenu(menuName, actionNames, null);
175 		}
176 
177 		private JMenu createMenu(final String menuName,
178 				final String[] actionNames, JMenu menu) {
179 			if (menu == null)
180 				menu = new JMenu();
181 			menu.setName(menuName);
182 			for (String actionName : actionNames)
183 				if (actionName.equals("---"))
184 					menu.add(new JSeparator());
185 				else {
186 					final JMenuItem menuItem;
187 					{
188 						final Matcher m = check.matcher(actionName);
189 						if (m.matches()) {
190 							actionName = m.group(1);
191 							menuItem = new JCheckBoxMenuItem();
192 						} else
193 							menuItem = new JMenuItem();
194 					}
195 					menuItem.setAction(findAction(actionName));
196 					menuItem.setIcon(null);
197 					menu.add(menuItem);
198 				}
199 			return menu;
200 		}
201 
202 		private File ensureSuffix(final File dst,
203 				final FileNameExtensionFilter pat) {
204 			if (pat.accept(dst))
205 				return dst;
206 			return new File(dst.getAbsoluteFile() + "."
207 					+ pat.getExtensions()[0]);
208 		}
209 
210 		private javax.swing.Action findAction(final String actionName) {
211 			return getContext().getActionMap().get(actionName);
212 		};
213 
214 		public ApplicationContext getContext() {
215 			return act;
216 		};
217 
218 		private void initGridBagConstraints(final GridBagConstraints c) {
219 			c.anchor = GridBagConstraints.CENTER;
220 			c.fill = GridBagConstraints.NONE;
221 			c.gridwidth = 1;
222 			c.gridheight = 1;
223 			c.gridx = GridBagConstraints.RELATIVE;
224 			c.gridy = GridBagConstraints.RELATIVE;
225 			c.insets = zeroInsets;
226 			c.ipadx = 4; // not the usual default
227 			c.ipady = 4; // not the usual default
228 			c.weightx = 0.0;
229 			c.weighty = 0.0;
230 		};
231 	}
232 
233 	static class XmppAccount extends MutableObject {
234 		private static final Log lo = JCLoggerFactory
235 				.getLogger(XmppAccount.class);
236 		private transient XMPPConnection conn;
237 		private CharSequence pwd;
238 		private XmppAddress uid;
239 
240 		public XMPPConnection getConn() {
241 			return conn;
242 		}
243 
244 		public CharSequence getPwd() {
245 			return pwd;
246 		}
247 
248 		public XmppAddress getUid() {
249 			return uid;
250 		}
251 
252 		public XMPPConnection login(final String resource) throws XMPPException {
253 			logout();
254 			final XMPPConnection conn = new XMPPConnection(uid.getHost());
255 			conn.connect();
256 			lo.info("connected: " + conn.isConnected() + ", secure: "
257 					+ conn.isSecureConnection() + ", TLS: " + conn.isUsingTLS()
258 					+ ", compressed: " + conn.isUsingCompression());
259 			conn.login(uid.getAccount(), pwd.toString(), resource);
260 			setConn(conn);
261 			return getConn();
262 		}
263 
264 		public void logout() {
265 			if (conn != null && conn.isConnected())
266 				conn.disconnect();
267 			setConn(null);
268 		}
269 
270 		private void setConn(final XMPPConnection conn) {
271 			final XMPPConnection old = this.conn;
272 			firePropertyChange("conn", old, this.conn = conn);
273 		}
274 
275 		public void setPwd(final CharSequence pwd) {
276 			this.pwd = pwd;
277 		}
278 
279 		public void setUid(final XmppAddress uid) {
280 			this.uid = uid;
281 		}
282 	}
283 
284 	private static final Log log = JCLoggerFactory
285 			.getLogger(JCurlSmackClient.class);
286 
287 	private static Properties loadClassProps(final Class<?> c, Properties p)
288 			throws IOException {
289 		if (p == null)
290 			p = new Properties();
291 		final String r = "/" + c.getName().replace('.', '/') + ".properties";
292 		final InputStream i = c.getResourceAsStream(r);
293 		if (i == null) {
294 			System.err.println("Add a file '." + r
295 					+ "' to the classpath containing the properties:");
296 			System.err
297 					.println("\nacc_uid = foo@foo.org # a jabber account name");
298 			System.err.println("\nacc_pwd = .. # a jabber account password");
299 		}
300 		p.load(i);
301 		return p;
302 	}
303 
304 	public static void main(final String[] args) {
305 		launch(JCurlSmackClient.class, args);
306 	}
307 
308 	private JDialog aboutBox = null;
309 	private final XmppAccount acc = new XmppAccount();
310 	private final GuiUtil gui = new GuiUtil(getContext());
311 	private JCheckBoxMenuItem miRoster;
312 	private final String resource;
313 	private final ChatSimpleSwingBean sca = new ChatSimpleSwingBean();
314 	private final ChatLogSimpleSwingBean slo = new ChatLogSimpleSwingBean();
315 	private final RosterSimpleSwingBean sro = new RosterSimpleSwingBean();
316 
317 	public JCurlSmackClient() {
318 		resource = this.getClass().getSimpleName();
319 		sro.setStar(Pattern.compile("[^/]+/" + resource));
320 	}
321 
322 	@Action
323 	public void closeAboutBox() {
324 		if (aboutBox == null)
325 			return;
326 		aboutBox.setVisible(false);
327 		aboutBox = null;
328 	}
329 
330 	private JMenuBar createMenuBar() {
331 		final JMenuBar menuBar = new JMenuBar();
332 
333 		final String[] xmppMenuActionNames = { "xmppAccount", "[]xmppSession",
334 				"[]xmppChat", "xmppPreferences", "---", "[]xmppRoster", "---",
335 				"quit" };
336 		final JMenu menu = gui.createMenu("xmppMenu", xmppMenuActionNames);
337 		miRoster = (JCheckBoxMenuItem) menu.getMenuComponent(5);
338 		menuBar.add(menu);
339 
340 		final String[] helpMenuActionNames = { "showAboutBox" };
341 		menuBar.add(gui.createMenu("helpMenu", helpMenuActionNames));
342 
343 		return menuBar;
344 	}
345 
346 	public boolean isAlwaysFalse() {
347 		return false;
348 	}
349 
350 	/** Show the about box dialog. */
351 	@Action(block = BlockingScope.COMPONENT)
352 	public void showAboutBox() {
353 		if (aboutBox == null)
354 			aboutBox = gui.createAboutBox(getMainFrame());
355 		show(aboutBox);
356 	}
357 
358 	@Override
359 	protected void startup() {
360 		getMainFrame().setJMenuBar(createMenuBar());
361 		miRoster.setSelected(true);
362 
363 		final JComponent pv = new JPanel();
364 		pv.setLayout(new BorderLayout());
365 		final Box conversation = Box.createVerticalBox();
366 		pv.add(new JScrollPane(conversation,
367 				ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS,
368 				ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED),
369 				BorderLayout.CENTER);
370 		pv.add(new JScrollPane(sca,
371 				ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER,
372 				ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS),
373 				BorderLayout.SOUTH);
374 
375 		final JSplitPane ph = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
376 		ph.setResizeWeight(0.8);
377 		ph.add(pv, JSplitPane.LEFT);
378 		ph.add(new JScrollPane(sro), JSplitPane.RIGHT);
379 
380 		xmppRoster();
381 		show(ph);
382 
383 		// connect the jabber account
384 		new Thread(new Runnable() {
385 			public void run() {
386 				try {
387 					// get uid + pwd from a .properties file
388 					final Properties p = loadClassProps(JCurlSmackClient.class,
389 							null);
390 					acc.setUid(XmppAddress.parse(p.getProperty("acc_uid")));
391 					acc.setPwd(p.getProperty("acc_pwd"));
392 					// login and get the present buddies
393 					acc.login(resource);
394 					SwingUtilities.invokeLater(new Runnable() {
395 						public void run() {
396 							sro.setConn(acc.getConn());
397 							// Wire up xmpp stuff:
398 							final ChatManager cm = sro.getChatManager();
399 							cm.addChatListener(sca);
400 							cm.addChatListener(slo);
401 						}
402 					});
403 				} catch (final IOException e) {
404 					throw new RuntimeException("Unhandled", e);
405 				} catch (final XMPPException e) {
406 					throw new RuntimeException("Unhandled", e);
407 				}
408 			}
409 		}).start();
410 	}
411 
412 	/** Xmpp Menu Action */
413 	@Action
414 	public void xmppAccount() {
415 		log.info("");
416 	}
417 
418 	/** Xmpp Menu Action */
419 	@Action(enabledProperty = "alwaysFalse")
420 	public void xmppChat() {
421 		log.info("");
422 	}
423 
424 	/** Xmpp Menu Action */
425 	@Action(enabledProperty = "alwaysFalse")
426 	public void xmppPreferences() {
427 		log.info("");
428 	}
429 
430 	/** Xmpp Menu Action */
431 	@Action
432 	public void xmppRoster() {
433 		sro.setEnabled(miRoster.isSelected());
434 		// sro.getParent().setVisible(miRoster.isSelected());
435 	}
436 
437 	/** Xmpp Menu Action */
438 	@Action
439 	public void xmppSession() {
440 		log.info("");
441 	}
442 }