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