001/*
002 * IzPack - Copyright 2001-2005 Julien Ponge, All Rights Reserved.
003 * 
004 * http://www.izforge.com/izpack/
005 * http://developer.berlios.de/projects/izpack/
006 * 
007 * Copyright 2002 Marcus Wolschon
008 * Copyright 2002 Jan Blok
009 * Copyright 2004 Klaus Bartz
010 * 
011 * Licensed under the Apache License, Version 2.0 (the "License");
012 * you may not use this file except in compliance with the License.
013 * You may obtain a copy of the License at
014 * 
015 *     http://www.apache.org/licenses/LICENSE-2.0
016 *     
017 * Unless required by applicable law or agreed to in writing, software
018 * distributed under the License is distributed on an "AS IS" BASIS,
019 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
020 * See the License for the specific language governing permissions and
021 * limitations under the License.
022 */
023
024package com.izforge.izpack.panels;
025
026import java.awt.Color;
027import java.awt.Component;
028import java.awt.Dimension;
029import java.awt.GridBagConstraints;
030import java.awt.GridBagLayout;
031import java.awt.Insets;
032import java.awt.event.ActionEvent;
033import java.awt.event.ActionListener;
034import java.io.File;
035import java.io.InputStream;
036import java.util.HashMap;
037import java.util.Iterator;
038import java.util.List;
039import java.util.Map;
040
041import javax.swing.AbstractCellEditor;
042import javax.swing.BorderFactory;
043import javax.swing.Box;
044import javax.swing.BoxLayout;
045import javax.swing.JCheckBox;
046import javax.swing.JLabel;
047import javax.swing.JOptionPane;
048import javax.swing.JPanel;
049import javax.swing.JScrollPane;
050import javax.swing.JTable;
051import javax.swing.JTextArea;
052import javax.swing.ListSelectionModel;
053import javax.swing.border.Border;
054import javax.swing.event.ListSelectionEvent;
055import javax.swing.event.ListSelectionListener;
056import javax.swing.table.DefaultTableCellRenderer;
057import javax.swing.table.TableCellEditor;
058import javax.swing.table.TableCellRenderer;
059
060import net.n3.nanoxml.XMLElement;
061
062import com.izforge.izpack.LocaleDatabase;
063import com.izforge.izpack.Pack;
064import com.izforge.izpack.gui.LabelFactory;
065import com.izforge.izpack.installer.InstallData;
066import com.izforge.izpack.installer.InstallerFrame;
067import com.izforge.izpack.installer.IzPanel;
068import com.izforge.izpack.installer.ResourceManager;
069import com.izforge.izpack.util.IoHelper;
070import com.izforge.izpack.util.Debug;
071
072/**
073 * The base class for Packs panels. It brings the common member and methods of the different packs
074 * panels together. This class handles the common logic of pack selection. The derived class should
075 * be create the layout and other specific actions. There are some helper methods to simplify layout
076 * creation in the derived class.
077 * 
078 * @author Julien Ponge
079 * @author Klaus Bartz
080 */
081public abstract class PacksPanelBase extends IzPanel implements PacksPanelInterface,
082        ListSelectionListener
083{
084
085    // Common used Swing fields
086    /** The free space label. */
087    protected JLabel freeSpaceLabel;
088
089    /** The space label. */
090    protected JLabel spaceLabel;
091
092    /** The tip label. */
093    protected JTextArea descriptionArea;
094
095    /** The dependencies label. */
096    protected JTextArea dependencyArea;
097
098    /** The packs table. */
099    protected JTable packsTable;
100
101    /** The tablescroll. */
102    protected JScrollPane tableScroller;
103
104    // Non-GUI fields
105    /** Map that connects names with pack objects */
106    private Map names;
107
108    /** The bytes of the current pack. */
109    protected int bytes = 0;
110
111    /** The free bytes of the current selected disk. */
112    protected long freeBytes = 0;
113
114    /** Are there dependencies in the packs */
115    protected boolean dependenciesExist = false;
116
117    /** The packs locale database. */
118    private LocaleDatabase langpack = null;
119
120    /** The name of the XML file that specifies the panel langpack */
121    private static final String LANG_FILE_NAME = "packsLang.xml";
122
123    /**
124     * The constructor.
125     * 
126     * @param parent The parent window.
127     * @param idata The installation data.
128     */
129    public PacksPanelBase(InstallerFrame parent, InstallData idata)
130    {
131        super(parent, idata);
132        // Load langpack.
133        try
134        {
135            this.langpack = parent.langpack;
136            InputStream inputStream = ResourceManager.getInstance().getInputStream(LANG_FILE_NAME);
137            this.langpack.add(inputStream);
138        }
139        catch (Throwable exception)
140        {
141            Debug.trace(exception);
142        }
143        // init the map
144        computePacks(idata.availablePacks);
145
146        createNormalLayout();
147    }
148
149    /**
150     * The Implementation of this method should create the layout for the current class.
151     */
152    abstract protected void createNormalLayout();
153
154    /*
155     * (non-Javadoc)
156     * 
157     * @see com.izforge.izpack.panels.PacksPanelInterface#getLangpack()
158     */
159    public LocaleDatabase getLangpack()
160    {
161        return (langpack);
162    }
163
164    /*
165     * (non-Javadoc)
166     * 
167     * @see com.izforge.izpack.panels.PacksPanelInterface#getBytes()
168     */
169    public int getBytes()
170    {
171        return (bytes);
172    }
173
174    /*
175     * (non-Javadoc)
176     * 
177     * @see com.izforge.izpack.panels.PacksPanelInterface#setBytes(int)
178     */
179    public void setBytes(int bytes)
180    {
181        this.bytes = bytes;
182    }
183
184    /*
185     * (non-Javadoc)
186     * 
187     * @see com.izforge.izpack.panels.PacksPanelInterface#showSpaceRequired()
188     */
189    public void showSpaceRequired()
190    {
191        if (spaceLabel != null) spaceLabel.setText(Pack.toByteUnitsString(bytes));
192    }
193
194    /*
195     * (non-Javadoc)
196     * 
197     * @see com.izforge.izpack.panels.PacksPanelInterface#showFreeSpace()
198     */
199    public void showFreeSpace()
200    {
201        if (IoHelper.supported("getFreeSpace") && freeSpaceLabel != null)
202        {
203            String msg = null;
204            freeBytes = IoHelper.getFreeSpace(IoHelper.existingParent(
205                    new File(idata.getInstallPath())).getAbsolutePath());
206            if (freeBytes > 0x000000007fffffff)
207                msg = " > 2 GB";
208            else if (freeBytes < 0)
209                msg = parent.langpack.getString("PacksPanel.notAscertainable");
210            else
211                msg = Pack.toByteUnitsString((int) freeBytes);
212            freeSpaceLabel.setText(msg);
213        }
214    }
215
216    /**
217     * Indicates wether the panel has been validated or not.
218     * 
219     * @return true if the needed space is less than the free space, else false
220     */
221    public boolean isValidated()
222    {
223        if (IoHelper.supported("getFreeSpace") && freeBytes >= 0 && freeBytes <= bytes)
224        {
225            JOptionPane.showMessageDialog(this, parent.langpack
226                    .getString("PacksPanel.notEnoughSpace"), parent.langpack
227                    .getString("installer.error"), JOptionPane.ERROR_MESSAGE);
228            return (false);
229        }
230        return (true);
231    }
232
233    /**
234     * Asks to make the XML panel data.
235     * 
236     * @param panelRoot The XML tree to write the data in.
237     */
238    public void makeXMLData(XMLElement panelRoot)
239    {
240        new ImgPacksPanelAutomationHelper().makeXMLData(idata, panelRoot);
241    }
242
243    /*
244     * (non-Javadoc)
245     * 
246     * @see javax.swing.event.ListSelectionListener#valueChanged(javax.swing.event.ListSelectionEvent)
247     */
248    public void valueChanged(ListSelectionEvent e)
249    {
250        int i = packsTable.getSelectedRow();
251        if (i < 0) return;
252        // Operations for the description
253        if (descriptionArea != null)
254        {
255            Pack pack = (Pack) idata.availablePacks.get(i);
256            String desc = "";
257            String key = pack.id + ".description";
258            if (langpack != null && pack.id != null && !pack.id.equals(""))
259            {
260                desc = langpack.getString(key);
261            }
262            if (desc.equals("") || key.equals(desc))
263            {
264                desc = pack.description;
265            }
266            descriptionArea.setText(desc);
267        }
268        // Operation for the dependency listing
269        if (dependencyArea != null)
270        {
271            Pack pack = (Pack) idata.availablePacks.get(i);
272            List dep = pack.dependencies;
273            String list = "";
274            for (int j = 0; dep != null && j < dep.size(); j++)
275            {
276                String name = (String) dep.get(j);
277                // Internationalization code
278                Pack childPack = (Pack) names.get(name);
279                String childName = "";
280                String key = childPack.id;
281                if (langpack != null && childPack.id != null && !childPack.id.equals(""))
282                {
283                    childName = langpack.getString(key);
284                }
285                if (childName.equals("") || key.equals(childName))
286                {
287                    childName = childPack.name;
288                }
289                // End internationalization
290                list += childName;
291                if (j != dep.size() - 1) list += ", ";
292            }
293            dependencyArea.setText(list);
294        }
295    }
296
297    /**
298     * Layout helper method:<br>
299     * Creates an label with a message given by msgId and an icon given by the iconId. If layout and
300     * constraints are not null, the label will be added to layout with the given constraints. The
301     * label will be added to this object.
302     * 
303     * @param msgId identifier for the IzPack langpack
304     * @param iconId identifier for the IzPack icons
305     * @param layout layout to be used
306     * @param constraints constraints to be used
307     * @return the created label
308     */
309    protected JLabel createLabel(String msgId, String iconId, GridBagLayout layout,
310            GridBagConstraints constraints)
311    {
312        JLabel label = LabelFactory.create(parent.langpack.getString(msgId), parent.icons
313                .getImageIcon(iconId), JLabel.TRAILING);
314        if (layout != null && constraints != null) layout.addLayoutComponent(label, constraints);
315        add(label);
316        return (label);
317    }
318
319    /**
320     * Creates a panel containing a anonymous label on the left with the message for the given msgId
321     * and a label on the right side with initial no text. The right label will be returned. If
322     * layout and constraints are not null, the label will be added to layout with the given
323     * constraints. The panel will be added to this object.
324     * 
325     * @param msgId identifier for the IzPack langpack
326     * @param layout layout to be used
327     * @param constraints constraints to be used
328     * @return the created (right) label
329     */
330    protected JLabel createPanelWithLabel(String msgId, GridBagLayout layout,
331            GridBagConstraints constraints)
332    {
333        JPanel panel = new JPanel();
334        JLabel label = new JLabel();
335        if (label == null) label = new JLabel("");
336        panel.setAlignmentX(LEFT_ALIGNMENT);
337        panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS));
338        panel.add(LabelFactory.create(parent.langpack.getString(msgId)));
339        panel.add(Box.createHorizontalGlue());
340        panel.add(label);
341        if (layout != null && constraints != null) layout.addLayoutComponent(panel, constraints);
342        add(panel);
343        return (label);
344    }
345
346    /**
347     * Creates a text area with standard settings and the title given by the msgId. If scroller is
348     * not null, the create text area will be added to the scroller and the scroller to this object,
349     * else the text area will be added directly to this object. If layout and constraints are not
350     * null, the text area or scroller will be added to layout with the given constraints. The text
351     * area will be returned.
352     * 
353     * @param msgId identifier for the IzPack langpack
354     * @param scroller the scroller to be used
355     * @param layout layout to be used
356     * @param constraints constraints to be used
357     * @return the created text area
358     */
359    protected JTextArea createTextArea(String msgId, JScrollPane scroller, GridBagLayout layout,
360            GridBagConstraints constraints)
361    {
362        JTextArea area = new JTextArea();
363        area.setMargin(new Insets(2, 2, 2, 2));
364        area.setAlignmentX(LEFT_ALIGNMENT);
365        area.setCaretPosition(0);
366        area.setEditable(false);
367        area.setEditable(false);
368        area.setOpaque(false);
369        area.setLineWrap(true);
370        area.setWrapStyleWord(true);
371        area.setBorder(BorderFactory.createTitledBorder(parent.langpack.getString(msgId)));
372
373        if (layout != null && constraints != null)
374        {
375            if (scroller != null)
376            {
377                layout.addLayoutComponent(scroller, constraints);
378            }
379            else
380                layout.addLayoutComponent(area, constraints);
381        }
382        if (scroller != null)
383        {
384            scroller.setViewportView(area);
385            add(scroller);
386        }
387        else
388            add(area);
389        return (area);
390
391    }
392
393    /**
394     * Creates the table for the packs. All parameters are required. The table will be returned.
395     * 
396     * @param width of the table
397     * @param scroller the scroller to be used
398     * @param layout layout to be used
399     * @param constraints constraints to be used
400     * @return the created table
401     */
402    protected JTable createPacksTable(int width, JScrollPane scroller, GridBagLayout layout,
403            GridBagConstraints constraints)
404    {
405
406        JTable table = new JTable();
407        table.setBorder(BorderFactory.createEmptyBorder(0, 2, 0, 2));
408        table.setIntercellSpacing(new Dimension(0, 0));
409        table.setBackground(Color.white);
410        table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
411        table.getSelectionModel().addListSelectionListener(this);
412        table.setShowGrid(false);
413        scroller.setViewportView(table);
414        scroller.setAlignmentX(LEFT_ALIGNMENT);
415        scroller.getViewport().setBackground(Color.white);
416        scroller.setPreferredSize(new Dimension(width, (idata.guiPrefs.height / 3 + 30)));
417
418        if (layout != null && constraints != null)
419            layout.addLayoutComponent(scroller, constraints);
420        add(scroller);
421        return (table);
422    }
423
424    /**
425     * Computes pack related data like the names or the dependencies state.
426     * 
427     * @param packs
428     */
429    private void computePacks(List packs)
430    {
431        names = new HashMap();
432        dependenciesExist = false;
433        for (int i = 0; i < packs.size(); i++)
434        {
435            Pack pack = (Pack) packs.get(i);
436            names.put(pack.name, pack);
437            if (pack.dependencies != null) dependenciesExist = true;
438        }
439    }
440
441    /**
442     * Called when the panel becomes active. If a derived class implements this method also, it is
443     * recomanded to call this method with the super operator first.
444     * 
445     */
446    public void panelActivate()
447    {
448        try
449        {
450            packsTable.setModel(new PacksModel(idata.availablePacks, idata.selectedPacks, this));
451            CheckBoxEditorRenderer packSelectedRenderer = new CheckBoxEditorRenderer(false);
452            packsTable.getColumnModel().getColumn(0).setCellRenderer(packSelectedRenderer);
453            CheckBoxEditorRenderer packSelectedEditor = new CheckBoxEditorRenderer(true);
454            packsTable.getColumnModel().getColumn(0).setCellEditor(packSelectedEditor);
455            packsTable.getColumnModel().getColumn(0).setMaxWidth(40);
456            DefaultTableCellRenderer renderer1 = new DefaultTableCellRenderer() {
457
458                /**
459                 * 
460                 */
461                private static final long serialVersionUID = 3256438101604710708L;
462
463                public void setBorder(Border b)
464                {
465                }
466            };
467            packsTable.getColumnModel().getColumn(1).setCellRenderer(renderer1);
468            DefaultTableCellRenderer renderer2 = new DefaultTableCellRenderer() {
469
470                /**
471                 * 
472                 */
473                private static final long serialVersionUID = 4121128130480976185L;
474
475                public void setBorder(Border b)
476                {
477                }
478
479                // public void setFont(Font f)
480                // {
481                // super.setFont(new Font("Monospaced",Font.PLAIN,11));
482                // }
483            };
484            renderer2.setHorizontalAlignment(JLabel.RIGHT);
485            packsTable.getColumnModel().getColumn(2).setCellRenderer(renderer2);
486            packsTable.getColumnModel().getColumn(2).setMaxWidth(100);
487
488            // remove header,so we don't need more strings
489            tableScroller.remove(packsTable.getTableHeader());
490            tableScroller.setColumnHeaderView(null);
491            tableScroller.setColumnHeader(null);
492
493            // set the JCheckBoxes to the currently selected panels. The
494            // selection might have changed in another panel
495            java.util.Iterator iter = idata.availablePacks.iterator();
496            bytes = 0;
497            while (iter.hasNext())
498            {
499                Pack p = (Pack) iter.next();
500                if (p.required)
501                {
502                    bytes += p.nbytes;
503                    continue;
504                }
505                if (idata.selectedPacks.contains(p)) bytes += p.nbytes;
506            }
507        }
508        catch (Exception e)
509        {
510            e.printStackTrace();
511        }
512        showSpaceRequired();
513        showFreeSpace();
514    }
515
516    /*
517     * (non-Javadoc)
518     * 
519     * @see com.izforge.izpack.installer.IzPanel#getSummaryBody()
520     */
521    public String getSummaryBody()
522    {
523        StringBuffer retval = new StringBuffer(256);
524        Iterator iter = idata.selectedPacks.iterator();
525        boolean first = true;
526        while (iter.hasNext())
527        {
528            if (!first)
529            {
530                retval.append("<br>");
531            }
532            first = false;
533            Pack pack = (Pack) iter.next();
534            if (langpack != null && pack.id != null && !pack.id.equals(""))
535            {
536                retval.append(langpack.getString(pack.id));
537            }
538            else
539                retval.append(pack.name);
540        }
541        return (retval.toString());
542    }
543
544    static class CheckBoxEditorRenderer extends AbstractCellEditor implements TableCellRenderer,
545            TableCellEditor, ActionListener
546    {
547
548        /**
549         * 
550         */
551        private static final long serialVersionUID = 4049072731222061879L;
552
553        private JCheckBox display;
554
555        public CheckBoxEditorRenderer(boolean useAsEditor)
556        {
557            display = new JCheckBox();
558            display.setHorizontalAlignment(JLabel.CENTER);
559            if (useAsEditor) display.addActionListener(this);
560
561        }
562
563        public Component getTableCellRendererComponent(JTable table, Object value,
564                boolean isSelected, boolean hasFocus, int row, int column)
565        {
566            if (isSelected)
567            {
568                display.setForeground(table.getSelectionForeground());
569                display.setBackground(table.getSelectionBackground());
570            }
571            else
572            {
573                display.setForeground(table.getForeground());
574                display.setBackground(table.getBackground());
575            }
576            int state = ((Integer) value).intValue();
577            display.setSelected((value != null && Math.abs(state) == 1));
578            display.setEnabled(state >= 0);
579            return display;
580        }
581
582        /**
583         * @see javax.swing.table.TableCellEditor#getTableCellEditorComponent(javax.swing.JTable,
584         * java.lang.Object, boolean, int, int)
585         */
586        public Component getTableCellEditorComponent(JTable table, Object value,
587                boolean isSelected, int row, int column)
588        {
589            return getTableCellRendererComponent(table, value, isSelected, false, row, column);
590        }
591
592        public Object getCellEditorValue()
593        {
594            return new Integer(display.isSelected() ? 1 : 0);
595        }
596
597        public void actionPerformed(ActionEvent e)
598        {
599            stopCellEditing();
600        }
601    }
602
603}