1 /*-
2  * Copyright (C) 2008 Erik Larsson
3  *
4  * This program is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 3 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 package org.catacombae.hfsexplorer.gui;
19 
20 import java.awt.event.ActionEvent;
21 import java.awt.event.ActionListener;
22 import java.io.File;
23 import java.io.FileNotFoundException;
24 import java.io.FileOutputStream;
25 import java.io.IOException;
26 import java.util.LinkedList;
27 import java.util.List;
28 import javax.swing.JDialog;
29 import javax.swing.JFileChooser;
30 import javax.swing.JOptionPane;
31 import javax.swing.ListSelectionModel;
32 import javax.swing.event.ListSelectionEvent;
33 import javax.swing.event.ListSelectionListener;
34 import org.catacombae.hfsexplorer.GUIUtil;
35 import org.catacombae.hfsexplorer.IOUtil;
36 import org.catacombae.util.Util;
37 import org.catacombae.hfsexplorer.fs.ResourceForkReader;
38 import org.catacombae.hfsexplorer.types.resff.ReferenceListEntry;
39 import org.catacombae.hfsexplorer.types.resff.ResourceMap;
40 import org.catacombae.hfsexplorer.types.resff.ResourceName;
41 import org.catacombae.hfsexplorer.types.resff.ResourceType;
42 import org.catacombae.io.ReadableRandomAccessStream;
43 import org.catacombae.util.Util.Pair;
44 
45 /**
46  * @author <a href="http://www.catacombae.org/" target="_top">Erik Larsson</a>
47  */
48 public class ResourceForkViewPanel extends javax.swing.JPanel {
49     private ResourceForkReader reader = null;
50 
51     /**
52      * An item as it is displayed in the list view over available resources.
53      * Its toString method decides how it is displayed to the user.
54      */
55     private class ListItem {
56         ResourceType type;
57         ReferenceListEntry entry;
58         ResourceName name;
59         long size;
60 
ListItem(ResourceType type, ReferenceListEntry entry, ResourceName name, long size)61         public ListItem(ResourceType type,
62                 ReferenceListEntry entry,
63                 ResourceName name,
64                 long size) {
65             this.type = type;
66             this.entry = entry;
67             this.name = name;
68             this.size = size;
69         }
70 
71         @Override
toString()72         public String toString() {
73             try {
74                 StringBuilder sb = new StringBuilder();
75                 sb.append(new String(type.getType(), "MacRoman"));
76 
77                 if(name != null)
78                     sb.append(" \"").append(new String(name.getName(), "MacRoman")).append("\"");
79                 return sb.toString();
80             } catch(Exception e) {
81                 e.printStackTrace();
82                 return "{" + e.getClass().getSimpleName() + " in resource id " + entry.getResourceID() + "}";
83             }
84         }
85     }
86 
87     /** Creates new form ResourceForkViewPanel */
ResourceForkViewPanel(ResourceForkReader startupReader)88     public ResourceForkViewPanel(ResourceForkReader startupReader) {
89         initComponents();
90         resourceList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
91 
92         loadResourceFork(startupReader);
93 
94         resourceList.addListSelectionListener(new ListSelectionListener() {
95 
96             public void valueChanged(ListSelectionEvent e) {
97                 Object o = resourceList.getSelectedValue();
98                 if(o instanceof ListItem)
99                     setSelectedItem((ListItem)o);
100                 else if(o != null)
101                     JOptionPane.showMessageDialog(resourceList, "Unexpected type in list: " + o.getClass());
102             }
103         });
104 
105         viewButton.addActionListener(new ActionListener() {
106 
107             public void actionPerformed(ActionEvent e) {
108                 Object selection = resourceList.getSelectedValue();
109                 if(selection != null && selection instanceof ListItem) {
110                     ListItem selectedItem = (ListItem) selection;
111                     JDialog d = new JDialog(JOptionPane.getFrameForComponent(ResourceForkViewPanel.this),
112                             selection.toString(), true);
113 
114                     DisplayTextFilePanel dtfp = new DisplayTextFilePanel();
115                     dtfp.loadStream(reader.getResourceStream(selectedItem.entry));
116 
117                     d.add(dtfp);
118                     d.pack();
119                     d.setLocationRelativeTo(null);
120                     d.setVisible(true);
121                 }
122             }
123 
124         });
125 
126         extractButton.addActionListener(new ActionListener() {
127             private JFileChooser fileChooser = new JFileChooser();
128             {
129                 fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
130                 fileChooser.setMultiSelectionEnabled(false);
131             }
132             public void actionPerformed(ActionEvent e) {
133                 Object selection = resourceList.getSelectedValue();
134                 if(selection != null && selection instanceof ListItem) {
135                     ListItem selectedItem = (ListItem) selection;
136 
137                     if(fileChooser.showSaveDialog(ResourceForkViewPanel.this) == JFileChooser.APPROVE_OPTION) {
138                         File saveFile = fileChooser.getSelectedFile();
139                         if(saveFile.exists()) {
140                             int res = JOptionPane.showConfirmDialog(ResourceForkViewPanel.this,
141                                     "The file already exists. Do you want to overwrite?",
142                                     "Confirm overwrite", JOptionPane.YES_NO_OPTION,
143                                     JOptionPane.QUESTION_MESSAGE);
144                             if(res != JOptionPane.YES_OPTION)
145                                 return;
146                         }
147 
148                         ReadableRandomAccessStream in = null;
149                         FileOutputStream fos = null;
150                         try {
151                             in = reader.getResourceStream(selectedItem.entry);
152                             fos = new FileOutputStream(saveFile);
153 
154                             IOUtil.streamCopy(in, fos, 65536);
155                         } catch(FileNotFoundException fnfe) {
156                             JOptionPane.showMessageDialog(ResourceForkViewPanel.this,
157                                     "Could not open file \"" + saveFile.getPath() + "\" for writing...",
158                                     "Error", JOptionPane.ERROR_MESSAGE);
159                         } catch(IOException ioe) {
160                             ioe.printStackTrace();
161                             GUIUtil.displayExceptionDialog(ioe, ResourceForkViewPanel.this);
162                         } finally {
163                             if(in != null)
164                                 in.close();
165                             if(fos != null) {
166                                 try {
167                                     fos.close();
168                                 } catch(IOException ex) {
169                                     ex.printStackTrace();
170                                     GUIUtil.displayExceptionDialog(ex, ResourceForkViewPanel.this);
171                                 }
172                             }
173                         }
174                     }
175                 }
176             }
177 
178         });
179     }
180 
loadResourceFork(ResourceForkReader reader)181     public final void loadResourceFork(ResourceForkReader reader) {
182         if(reader != null) {
183             ListItem[] allItems = listAllItems(reader);
184             resourceList.setEnabled(true);
185             resourceList.setListData(allItems);
186             resourceListLabel.setText("Resource list (" + allItems.length + " items):");
187         }
188         else {
189             resourceList.setEnabled(false);
190             resourceList.setListData(new Object[0]);
191             resourceListLabel.setText("Resource list:");
192         }
193         setSelectedItem(null);
194         this.reader = reader;
195     }
196 
listAllItems(ResourceForkReader reader)197     private ListItem[] listAllItems(ResourceForkReader reader) {
198         //System.err.println("listAllItems(): getting resource map");
199         ResourceMap resMap = reader.getResourceMap();
200 
201         LinkedList<ListItem> result = new LinkedList<ListItem>();
202 
203         //System.err.println("listAllItems(): getting reference list for " + resMap);
204         List<Pair<ResourceType, ReferenceListEntry[]>> refList = resMap.getReferenceList();
205         for(Pair<ResourceType, ReferenceListEntry[]> p : refList) {
206             ResourceType type = p.getA();
207             for(ReferenceListEntry entry : p.getB()) {
208                 //System.err.println("listAllItems(): getting name by reflist entry " + entry);
209                 ResourceName name = resMap.getNameByReferenceListEntry(entry);
210                 long size = reader.getDataLength(entry);
211 
212                 result.add(new ListItem(type, entry, name, size));
213             }
214         }
215 
216         return result.toArray(new ListItem[result.size()]);
217     }
218 
setSelectedItem(ListItem li)219     private void setSelectedItem(ListItem li) {
220         final boolean enabled;
221         if(li == null)
222             enabled = false;
223         else
224             enabled = true;
225 
226         extractButton.setEnabled(enabled);
227         viewButton.setEnabled(enabled);
228         nameField.setEnabled(enabled);
229         typeField.setEnabled(enabled);
230         idField.setEnabled(enabled);
231         sizeField.setEnabled(enabled);
232         attributesField.setEnabled(enabled);
233 
234         if(!enabled) {
235             nameField.setText("");
236             typeField.setText("");
237             idField.setText("");
238             sizeField.setText("");
239             attributesField.setText("");
240         }
241         else {
242             String nameString;
243             if(li.name != null) {
244                 try {
245                     nameString = new String(li.name.getName(), "MacRoman");
246                 } catch(Exception e) {
247                     e.printStackTrace();
248                     nameString = "[Could not decode: " + e.toString() + "]";
249                 }
250             }
251             else {
252                 nameString = null;
253             }
254 
255             String typeString;
256             try {
257                 typeString = new String(li.type.getType(), "MacRoman");
258             } catch(Exception e) {
259                 e.printStackTrace();
260                 typeString = "[Could not decode: " + e.toString() + "]";
261             }
262 
263             if(nameField == null) {
264                 nameField.setEnabled(false);
265                 nameField.setName("");
266             }
267             else
268                 nameField.setText(nameString);
269 
270             typeField.setText(typeString);
271             idField.setText("" + li.entry.getResourceID());
272             sizeField.setText(li.size + " bytes");
273             attributesField.setText("0x" + Util.toHexStringBE(li.entry.getResourceAttributes()));
274         }
275     }
276 
277     /** This method is called from within the constructor to
278      * initialize the form.
279      * WARNING: Do NOT modify this code. The content of this method is
280      * always regenerated by the Form Editor.
281      */
282     @SuppressWarnings("unchecked")
283     // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
initComponents()284     private void initComponents() {
285 
286         resourceListLabel = new javax.swing.JLabel();
287         resourceListScroller = new javax.swing.JScrollPane();
288         resourceList = new javax.swing.JList();
289         fieldsPanel = new javax.swing.JPanel();
290         nameLabel = new javax.swing.JLabel();
291         nameField = new javax.swing.JTextField();
292         typeLabel = new javax.swing.JLabel();
293         typeField = new javax.swing.JTextField();
294         idLabel = new javax.swing.JLabel();
295         idField = new javax.swing.JTextField();
296         sizeLabel = new javax.swing.JLabel();
297         sizeField = new javax.swing.JTextField();
298         attributesLabel = new javax.swing.JLabel();
299         attributesField = new javax.swing.JTextField();
300         extractButton = new javax.swing.JButton();
301         viewButton = new javax.swing.JButton();
302 
303         resourceListLabel.setText("[This label is set programmatically]");
304 
305         resourceList.setModel(new javax.swing.AbstractListModel() {
306             String[] strings = { "Item 1", "Item 2", "Item 3", "Item 4", "Item 5" };
307             public int getSize() { return strings.length; }
308             public Object getElementAt(int i) { return strings[i]; }
309         });
310         resourceListScroller.setViewportView(resourceList);
311 
312         nameLabel.setText("Name:");
313 
314         nameField.setEditable(false);
315         nameField.setText("jTextField1");
316         nameField.setOpaque(false);
317 
318         typeLabel.setText("Type:");
319 
320         typeField.setEditable(false);
321         typeField.setText("jTextField2");
322         typeField.setOpaque(false);
323 
324         idLabel.setText("ID:");
325 
326         idField.setEditable(false);
327         idField.setText("jTextField3");
328         idField.setOpaque(false);
329 
330         sizeLabel.setText("Size:");
331 
332         sizeField.setEditable(false);
333         sizeField.setText("jTextField4");
334         sizeField.setOpaque(false);
335 
336         attributesLabel.setText("Attributes:");
337 
338         attributesField.setEditable(false);
339         attributesField.setText("jTextField5");
340         attributesField.setOpaque(false);
341 
342         org.jdesktop.layout.GroupLayout fieldsPanelLayout = new org.jdesktop.layout.GroupLayout(fieldsPanel);
343         fieldsPanel.setLayout(fieldsPanelLayout);
344         fieldsPanelLayout.setHorizontalGroup(
345             fieldsPanelLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
346             .add(fieldsPanelLayout.createSequentialGroup()
347                 .add(fieldsPanelLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
348                     .add(nameLabel)
349                     .add(typeLabel)
350                     .add(idLabel)
351                     .add(sizeLabel)
352                     .add(attributesLabel))
353                 .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
354                 .add(fieldsPanelLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
355                     .add(attributesField, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 227, Short.MAX_VALUE)
356                     .add(sizeField, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 227, Short.MAX_VALUE)
357                     .add(idField, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 227, Short.MAX_VALUE)
358                     .add(nameField, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 227, Short.MAX_VALUE)
359                     .add(typeField, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 227, Short.MAX_VALUE)))
360         );
361         fieldsPanelLayout.setVerticalGroup(
362             fieldsPanelLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
363             .add(fieldsPanelLayout.createSequentialGroup()
364                 .add(fieldsPanelLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE)
365                     .add(nameLabel)
366                     .add(nameField, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE))
367                 .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
368                 .add(fieldsPanelLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE)
369                     .add(typeLabel)
370                     .add(typeField, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE))
371                 .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
372                 .add(fieldsPanelLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE)
373                     .add(idLabel)
374                     .add(idField, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE))
375                 .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
376                 .add(fieldsPanelLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE)
377                     .add(sizeLabel)
378                     .add(sizeField, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE))
379                 .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
380                 .add(fieldsPanelLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE)
381                     .add(attributesLabel)
382                     .add(attributesField, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)))
383         );
384 
385         extractButton.setText("Save to file...");
386 
387         viewButton.setText("View as text");
388 
389         org.jdesktop.layout.GroupLayout layout = new org.jdesktop.layout.GroupLayout(this);
390         this.setLayout(layout);
391         layout.setHorizontalGroup(
392             layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
393             .add(org.jdesktop.layout.GroupLayout.TRAILING, layout.createSequentialGroup()
394                 .addContainerGap()
395                 .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.TRAILING)
396                     .add(org.jdesktop.layout.GroupLayout.LEADING, resourceListScroller, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 283, Short.MAX_VALUE)
397                     .add(org.jdesktop.layout.GroupLayout.LEADING, resourceListLabel)
398                     .add(layout.createSequentialGroup()
399                         .add(viewButton)
400                         .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
401                         .add(extractButton))
402                     .add(org.jdesktop.layout.GroupLayout.LEADING, fieldsPanel, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
403                 .addContainerGap())
404         );
405         layout.setVerticalGroup(
406             layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
407             .add(layout.createSequentialGroup()
408                 .addContainerGap()
409                 .add(resourceListLabel)
410                 .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
411                 .add(resourceListScroller, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 99, Short.MAX_VALUE)
412                 .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
413                 .add(fieldsPanel, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)
414                 .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
415                 .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE)
416                     .add(extractButton)
417                     .add(viewButton))
418                 .addContainerGap())
419         );
420     }// </editor-fold>//GEN-END:initComponents
421 
422 
423     // Variables declaration - do not modify//GEN-BEGIN:variables
424     private javax.swing.JTextField attributesField;
425     private javax.swing.JLabel attributesLabel;
426     private javax.swing.JButton extractButton;
427     private javax.swing.JPanel fieldsPanel;
428     private javax.swing.JTextField idField;
429     private javax.swing.JLabel idLabel;
430     private javax.swing.JTextField nameField;
431     private javax.swing.JLabel nameLabel;
432     private javax.swing.JList resourceList;
433     private javax.swing.JLabel resourceListLabel;
434     private javax.swing.JScrollPane resourceListScroller;
435     private javax.swing.JTextField sizeField;
436     private javax.swing.JLabel sizeLabel;
437     private javax.swing.JTextField typeField;
438     private javax.swing.JLabel typeLabel;
439     private javax.swing.JButton viewButton;
440     // End of variables declaration//GEN-END:variables
441 
442 }
443