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.BorderLayout;
022    import java.awt.Dimension;
023    import java.awt.GridBagConstraints;
024    import java.awt.GridBagLayout;
025    import java.awt.event.ActionEvent;
026    import java.awt.event.ActionListener;
027    import java.util.Locale;
028    import java.util.ResourceBundle;
029    import java.util.Vector;
030    
031    import javax.swing.BorderFactory;
032    import javax.swing.Box;
033    import javax.swing.BoxLayout;
034    import javax.swing.JButton;
035    import javax.swing.JDialog;
036    import javax.swing.JLabel;
037    import javax.swing.JPanel;
038    import javax.swing.JScrollPane;
039    import javax.swing.JTabbedPane;
040    import javax.swing.JTextField;
041    import javax.swing.SwingUtilities;
042    import javax.swing.text.JTextComponent;
043    
044    import org.jivesoftware.smack.PacketCollector;
045    import org.jivesoftware.smack.SmackConfiguration;
046    import org.jivesoftware.smack.XMPPConnection;
047    import org.jivesoftware.smack.XMPPException;
048    import org.jivesoftware.smack.filter.PacketIDFilter;
049    import org.jivesoftware.smack.packet.IQ;
050    import org.jivesoftware.smackx.packet.*;
051    import org.jivesoftware.smackx.packet.*;
052    import com.valhalla.jbother.jabber.smack.*;
053    
054    import com.valhalla.gui.*;
055    
056    
057    import com.valhalla.jbother.jabber.smack.LastActivity;
058    
059    /**
060     * A dialog that collects and shows information about a Jabber user Shows a
061     * dialog with several fields and starts an information collecting field for
062     * each one. As each piece of information is found it fills out the fields
063     *
064     * @author Adam Olsen
065     * @author Andrey Zakirov
066     * @version 1.0
067     */
068    public class InformationViewerDialog extends JDialog implements WaitDialogListener {
069        private ResourceBundle resources = ResourceBundle.getBundle(
070                "JBotherBundle", Locale.getDefault());
071    
072        private JButton okButton = new JButton(resources.getString("okButton"));
073        private JButton retrieve = new JButton(resources.getString("retrieve"));
074        private JButton save = new JButton(resources.getString("saveButton"));
075        private String user;
076        private JPanel mainPanel;
077        private JTabbedPane pane = new JTabbedPane();
078        private Vector fields = new Vector();
079        private GridBagConstraints c = new GridBagConstraints();
080        private MJTextField name = new MJTextField();
081        private MJTextField last = new MJTextField();
082        private MJTextField birthday = new MJTextField();
083        private MJTextField nickname = new MJTextField();
084        private MJTextField email = new MJTextField();
085        private MJTextField homepage = new MJTextField();
086        private MJTextField phone = new MJTextField();
087    
088        // location
089        private MJTextField street1 = new MJTextField();
090        private MJTextField street2 = new MJTextField();
091        private MJTextField city = new MJTextField();
092        private MJTextField state = new MJTextField();
093        private MJTextField zip = new MJTextField();
094        private MJTextField country = new MJTextField();
095    
096        // work
097        private MJTextField company = new MJTextField();
098        private MJTextField department = new MJTextField();
099        private MJTextField position = new MJTextField();
100        private MJTextField role = new MJTextField();
101        private MJTextArea about = new MJTextArea();
102        private WaitDialog wait = new WaitDialog(this, this, resources
103                .getString("pleaseWait"));
104        private MJTextField clientField = new MJTextField();
105        private MJTextField timeField = new MJTextField();
106        private MJTextField lastField = new MJTextField();
107        private boolean personal;
108        protected boolean cancelled = false;
109    
110        /**
111         * Default constructor
112         *
113         * @param user
114         *            the user you want to collect information about
115         */
116        public InformationViewerDialog(String user, boolean personal) {
117            super(BuddyList.getInstance().getContainerFrame());
118            this.personal = personal;
119            this.user = user;
120            if (!personal)
121                setTitle(resources.getString("information") + " " + user);
122            else
123                setTitle(resources.getString("editInformation"));
124    
125            if (!BuddyList.getInstance().checkConnection()) {
126                BuddyList.getInstance().connectionError();
127                return;
128            }
129    
130            about.setWrapStyleWord(true);
131            mainPanel = (JPanel) getContentPane();
132    
133            mainPanel.setLayout(new BorderLayout());
134            mainPanel.add(pane, BorderLayout.CENTER);
135    
136            JPanel buttonPanel = new JPanel();
137            buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.X_AXIS));
138            buttonPanel.add(Box.createHorizontalGlue());
139            buttonPanel.add(retrieve);
140            if (personal)
141                buttonPanel.add(save);
142            buttonPanel.add(okButton);
143            //buttonPanel.add( Box.createHorizontalGlue() );
144            buttonPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
145    
146            mainPanel.add(buttonPanel, BorderLayout.SOUTH);
147    
148            addGeneralItems();
149            addLocationItems();
150            addWorkItems();
151            addAboutItems();
152    
153            if (!personal) {
154                addClientItems();
155            }
156    
157            pack();
158            Dimension dim = getSize();
159            setSize(new Dimension(450, (int) dim.getHeight()));
160    
161            setLocationRelativeTo(null);
162            DialogTracker.addDialog(this, false, true);
163            collectInformation();
164    
165            okButton.addActionListener(new ActionListener() {
166                public void actionPerformed(ActionEvent e) {
167                    DialogTracker.removeDialog(InformationViewerDialog.this);
168                }
169            });
170    
171            retrieve.addActionListener(new ActionListener() {
172                public void actionPerformed(ActionEvent e) {
173                    collectInformation();
174                }
175            });
176    
177            save.addActionListener(new ActionListener() {
178                public void actionPerformed(ActionEvent e) {
179                    wait.setVisible(true);
180                    cancelled = false;
181                    disableAll();
182                    new Thread(new SaveVCard()).start();
183                }
184            });
185        }
186    
187        public void cancel()
188        {
189            cancelled = true;
190        }
191    
192        private void collectInformation() {
193            wait.setVisible(true);
194            disableAll();
195            new Thread(new VCardCollector()).start();
196    
197            if (!personal) {
198                new Thread(new VersionCollector(clientField, this)).start();
199                new Thread(new TimeCollector(timeField, this)).start();
200                new Thread(new LastCollector(lastField, this)).start();
201            }
202        }
203    
204        public InformationViewerDialog(String user) {
205            this(user, false);
206        }
207    
208        /**
209         * Saves a the VCard
210         */
211        private class SaveVCard implements Runnable {
212            public void run() {
213                if( cancelled ) return;
214                VCard card = new VCard();
215                card.setFirstName(name.getText());
216                card.setLastName(last.getText());
217                card.setNickName(nickname.getText());
218    
219                card.setEmailHome(email.getText());
220                card.setPhoneHome("VOICE", phone.getText());
221                card.setField("BDAY", birthday.getText());
222    
223                card.setAddressFieldHome("STREET", street1.getText());
224                card.setAddressFieldHome("LOCALITY", city.getText());
225                card.setAddressFieldHome("REGION", state.getText());
226                card.setAddressFieldHome("PCODE", zip.getText());
227                card.setAddressFieldHome("CTRY", country.getText());
228    
229                card.setOrganization(company.getText());
230                card.setOrganizationUnit(department.getText());
231                card.setField("TITLE", position.getText());
232                card.setField("ROLE", role.getText());
233                card.setField("DESC", about.getText());
234    
235                try {
236                    card.save(BuddyList.getInstance().getConnection());
237                } catch (NullPointerException npe) {
238                    npe.printStackTrace();
239                }
240                catch( XMPPException ex ) { }
241    
242                if( cancelled ) return;
243    
244                SwingUtilities.invokeLater(new Runnable() {
245                    public void run() {
246                        wait.setVisible(false);
247                        enableAll();
248                    }
249                });
250            }
251        }
252    
253        /**
254         * Collects a VCard for a user and fills in the InformationViewer form
255         * fields
256         */
257        private class VCardCollector implements Runnable {
258            /**
259             * Cancels this thread
260             */
261            public void cancel()
262            {
263                cancelled = true;
264            }
265    
266            /**
267             * Called by Thread.start()
268             */
269            public void run() {
270                VCard temp = new VCard();
271                if( cancelled ) return;
272    
273                try {
274                    if (!personal) {
275                        temp.load(BuddyList.getInstance().getConnection(),
276                                user);
277                    } else {
278                        temp.load(BuddyList.getInstance().getConnection());
279                    }
280                } catch (NullPointerException npe) {
281                } catch (XMPPException ex) {
282                }
283    
284                final VCard card = temp;
285                if( cancelled ) return;
286    
287                SwingUtilities.invokeLater(new Runnable() {
288                    public void run() {
289                        if (card != null) {
290                            name.setText(card.getFirstName());
291                            last.setText(card.getLastName());
292                            nickname.setText(card.getNickName());
293                            birthday.setText(card.getField("BDAY"));
294                            email.setText(card.getEmailHome());
295                            phone.setText(card.getPhoneHome("VOICE"));
296    
297                            street1.setText(card.getAddressFieldHome("STREET"));
298                            city.setText(card.getAddressFieldHome("LOCALITY"));
299                            state.setText(card.getAddressFieldHome("REGION"));
300                            zip.setText(card.getAddressFieldHome("PCODE"));
301                            country.setText(card.getAddressFieldHome("CTRY"));
302    
303                            company.setText(card.getOrganization());
304                            department.setText(card.getOrganizationUnit());
305                            position.setText(card.getField("TITLE"));
306                            role.setText(card.getField("ROLE"));
307                            about.setText(card.getField("DESC"));
308                            about.setCaretPosition(0);
309                        }
310    
311                        if (personal)
312                            enableAll();
313                        wait.setVisible(false);
314                        validate();
315                        setVisible(true);
316                    }
317                });
318            }
319        }
320    
321        /**
322         * Creates the "About" tab
323         */
324        private void addAboutItems() {
325            JPanel panel = new JPanel(new BorderLayout());
326            panel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
327            panel.add(new JScrollPane(about), BorderLayout.CENTER);
328            fields.add(about);
329    
330            pane.add(panel, resources.getString("aboutUser"));
331        }
332    
333        /**
334         * Creates the "work" tab
335         */
336        private void addWorkItems() {
337            GridBagLayout grid = new GridBagLayout();
338            JPanel work = createNewPanel(grid);
339    
340            addItem(work, resources.getString("company"), company, grid);
341            addItem(work, resources.getString("department"), department, grid);
342            addItem(work, resources.getString("position"), position, grid);
343            addItem(work, resources.getString("role"), role, grid);
344    
345            pane.add(work, resources.getString("work"));
346            padEnd(work, grid);
347        }
348    
349        /**
350         * Creates the "general" tab
351         */
352        private void addGeneralItems() {
353            GridBagLayout grid = new GridBagLayout();
354            JPanel general = createNewPanel(grid);
355    
356            addItem(general, resources.getString("name"), name, grid);
357            addItem(general, resources.getString("last"), last, grid);
358            addItem(general, resources.getString("nickname"), nickname, grid);
359            addItem(general, resources.getString("birthday"), birthday, grid);
360            addItem(general, resources.getString("email"), email, grid);
361            addItem(general, resources.getString("phone"), phone, grid);
362    
363            pane.add(general, resources.getString("general"));
364            padEnd(general, grid);
365        }
366    
367        /**
368         * Adds the "location" tab items
369         */
370        private void addLocationItems() {
371            GridBagLayout grid = new GridBagLayout();
372            JPanel location = createNewPanel(grid);
373    
374            addItem(location, resources.getString("street"), street1, grid);
375            addItem(location, resources.getString("city"), city, grid);
376            addItem(location, resources.getString("state"), state, grid);
377            addItem(location, resources.getString("zip"), zip, grid);
378            addItem(location, resources.getString("country"), country, grid);
379    
380            pane.add(location, resources.getString("location"));
381            padEnd(location, grid);
382        }
383    
384        /**
385         * Adds the different fields to the displayed form
386         */
387        private void addClientItems() {
388            GridBagLayout grid = new GridBagLayout();
389            JPanel client = createNewPanel(grid);
390    
391            /* gets jabber:iq:version information */
392            addItem(client, resources.getString("clientInformation"), clientField,
393                    grid);
394            addItem(client, resources.getString("timeInformation"), timeField, grid);
395            addItem(client, "Idle for", lastField, grid);
396            pane.add(client, resources.getString("misc"));
397            padEnd(client, grid);
398        }
399    
400        /**
401         * Creates a new panel suitable for the Information Viewer
402         * @param grid  The gridbag to use
403         * @return a new JPanel with appropriate constraints
404         */
405        private JPanel createNewPanel(GridBagLayout grid) {
406            JPanel panel = new JPanel(grid);
407            panel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
408    
409            c.gridx = 0;
410            c.gridy = 0;
411            c.gridwidth = 1;
412            c.weighty = 0;
413            c.anchor = GridBagConstraints.NORTHWEST;
414            return panel;
415        }
416    
417        /**
418         * Disables all the fields in the dialog
419         * Appropriate for when you are viewing another users 
420         * information (that you can't change)
421         */
422        private void disableAll() {
423            for (int i = 0; i < fields.size(); i++) {
424                JTextComponent field = (JTextComponent) fields.get(i);
425                field.setEditable(false);
426            }
427            validate();
428        }
429    
430        /**
431         * Enables all the fields in the dialog
432         * Used for when you are changing your own information.
433         */
434        private void enableAll() {
435            for (int i = 0; i < fields.size(); i++) {
436                JTextComponent field = (JTextComponent) fields.get(i);
437                field.setEditable(true);
438            }
439            validate();
440        }
441    
442        /**
443         * Adds a blank panel that takes all excess space on the panel
444         * @param panel The panel to add this spacer to
445         * @param grid The grid that's associated with this panel
446         */
447        private void padEnd(JPanel panel, GridBagLayout grid) {
448            JLabel blank = new JLabel("");
449            c.gridwidth = 2;
450            c.weighty = 1;
451            grid.setConstraints(blank, c);
452            panel.add(blank);
453        }
454    
455        /**
456         * Adds a field and starts the thread that will collect it's information
457         *
458         * @param key the name of the field
459         * @param informationThread the thread that will collect the information
460         */
461        private void addItem(JPanel panel, String l, MJTextField field,
462                GridBagLayout grid) {
463            c.fill = GridBagConstraints.HORIZONTAL;
464    
465            if (!l.equals(""))
466                l = l + ": ";
467            JLabel label = new JLabel(l);
468            label.setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 5));
469            c.ipadx = 2;
470            c.ipady = 2;
471            c.weightx = .1;
472            c.gridx = 0;
473            grid.setConstraints(label, c);
474            panel.add(label);
475            c.gridx++;
476            c.weightx = 1;
477            grid.setConstraints(field, c);
478            panel.add(field);
479            fields.add(field);
480            c.gridy++;
481        }
482    
483        public String getUser() {
484            return user;
485        }
486    }
487    
488    /**
489     * An <code>InformationCollector</code> that collects jabbber:iq:version
490     * information
491     *
492     * @author Adam Olsen
493     * @version 1.0
494     */
495    
496    class VersionCollector implements Runnable {
497        InformationViewerDialog dialog;
498    
499        JTextField field;
500    
501        /**
502         * The default constructor
503         *
504         * @param dialog
505         *            the InformationViewerDialog that called this thread
506         * @param id
507         *            the field id
508         */
509        public VersionCollector(MJTextField field, InformationViewerDialog dialog) {
510            this.field = field;
511            this.dialog = dialog;
512        }
513    
514        /**
515         * Called by the enclosing <code>java.util.Thread</code> The run() method
516         * is responsible for collecting the requested information and setting the
517         * field in the InformationViewerDialog
518         */
519        public void run() {
520            XMPPConnection con = BuddyList.getInstance().getConnection();
521            if( dialog.cancelled ) return;
522    
523            Version request = new Version();
524            request.setType(IQ.Type.GET);
525            request.setTo(dialog.getUser());
526    
527            // Create a packet collector to listen for a response.
528            PacketCollector collector = con
529                    .createPacketCollector(new PacketIDFilter(request.getPacketID()));
530    
531            con.sendPacket(request);
532    
533            // Wait up to 5 seconds for a result.
534            IQ result = (IQ) collector.nextResult(SmackConfiguration
535                    .getPacketReplyTimeout());
536            if( dialog.cancelled ) return;
537    
538            if (result != null && result.getType() == IQ.Type.RESULT) {
539                Version v = (Version) result;
540    
541                field.setText(v.getName() + " " + v.getVersion() + " / "
542                        + v.getOs());
543            } else
544                field.setText("N/A");
545            field.validate();
546        }
547    }
548    
549    /**
550     * @author Adam Olsen
551     * @version 1.0
552     */
553    
554    class TimeCollector implements Runnable {
555        InformationViewerDialog dialog;
556    
557        JTextField field;
558    
559        /**
560         * The default constructor
561         *
562         * @param dialog
563         *            the InformationViewerDialog that called this thread
564         * @param id
565         *            the field id
566         */
567        public TimeCollector(MJTextField field, InformationViewerDialog dialog) {
568            this.field = field;
569            this.dialog = dialog;
570        }
571    
572        /**
573         * Called by the enclosing <code>java.util.Thread</code> The run() method
574         * is responsible for collecting the requested information and setting the
575         * field in the InformationViewerDialog
576         */
577        public void run() {
578            if( dialog.cancelled ) return;
579    
580            XMPPConnection con = BuddyList.getInstance().getConnection();
581    
582            Time request = new Time();
583            request.setType(IQ.Type.GET);
584            request.setTo(dialog.getUser());
585    
586            // Create a packet collector to listen for a response.
587            PacketCollector collector = con
588                    .createPacketCollector(new PacketIDFilter(request.getPacketID()));
589    
590            con.sendPacket(request);
591    
592    
593            // Wait up to 5 seconds for a result.
594            IQ result = (IQ) collector.nextResult(SmackConfiguration
595                    .getPacketReplyTimeout());
596            collector.cancel();
597            if( dialog.cancelled ) return;
598    
599            if (result != null && result.getType() == IQ.Type.RESULT) {
600                Time t = (Time) result;
601    
602                field.setText(t.getDisplay());
603            } else
604                field.setText("N/A");
605            field.validate();
606        }
607    }
608    
609    /**
610     * Collects information in order to show the idle time of a user
611     */
612    class LastCollector implements Runnable {
613        InformationViewerDialog dialog;
614    
615        JTextField field;
616    
617        /**
618         * The default constructor
619         *
620         * @param dialog
621         *            the InformationViewerDialog that called this thread
622         * @param id
623         *            the field id
624         */
625        public LastCollector(MJTextField field, InformationViewerDialog dialog) {
626            this.field = field;
627            this.dialog = dialog;
628        }
629    
630        /**
631         * Called by the enclosing <code>java.util.Thread</code> The run() method
632         * is responsible for collecting the requested information and setting the
633         * field in the InformationViewerDialog
634         */
635        public void run() {
636            if( dialog.cancelled ) return;
637    
638            XMPPConnection con = BuddyList.getInstance().getConnection();
639    
640            LastActivity request = new LastActivity();
641            request.setType(IQ.Type.GET);
642            request.setTo(dialog.getUser());
643    
644            // Create a packet collector to listen for a response.
645            PacketCollector collector = con
646                    .createPacketCollector(new PacketIDFilter(request.getPacketID()));
647    
648            con.sendPacket(request);
649    
650            // Wait up to 5 seconds for a result.
651            IQ result = (IQ) collector.nextResult(SmackConfiguration
652                    .getPacketReplyTimeout());
653            if( dialog.cancelled ) return;
654    
655            if (result != null && result.getType() == IQ.Type.RESULT) {
656                LastActivity t = (LastActivity) result;
657    
658                field.setText(t.showTime());
659            } else
660                field.setText("N/A");
661            field.validate();
662        }
663    }