OSDN Git Service

キャラクターデータの初回インボード時に必ず失敗していた問題の修正
[charactermanaj/CharacterManaJ.git] / src / main / java / charactermanaj / ui / ImportWizardDialog.java
1 package charactermanaj.ui;
2
3 import java.awt.BorderLayout;
4 import java.awt.CardLayout;
5 import java.awt.Color;
6 import java.awt.Component;
7 import java.awt.Container;
8 import java.awt.Cursor;
9 import java.awt.Dimension;
10 import java.awt.Font;
11 import java.awt.GridBagConstraints;
12 import java.awt.GridBagLayout;
13 import java.awt.Insets;
14 import java.awt.Rectangle;
15 import java.awt.Toolkit;
16 import java.awt.dnd.DropTarget;
17 import java.awt.event.ActionEvent;
18 import java.awt.event.ActionListener;
19 import java.awt.event.ComponentAdapter;
20 import java.awt.event.ComponentEvent;
21 import java.awt.event.ComponentListener;
22 import java.awt.event.KeyEvent;
23 import java.awt.event.WindowAdapter;
24 import java.awt.event.WindowEvent;
25 import java.awt.image.BufferedImage;
26 import java.io.File;
27 import java.io.FileNotFoundException;
28 import java.io.IOException;
29 import java.net.URI;
30 import java.sql.Timestamp;
31 import java.text.MessageFormat;
32 import java.util.AbstractCollection;
33 import java.util.ArrayList;
34 import java.util.Arrays;
35 import java.util.Collection;
36 import java.util.Collections;
37 import java.util.Comparator;
38 import java.util.HashMap;
39 import java.util.HashSet;
40 import java.util.IdentityHashMap;
41 import java.util.Iterator;
42 import java.util.LinkedList;
43 import java.util.List;
44 import java.util.Map;
45 import java.util.Properties;
46 import java.util.Set;
47
48 import javax.swing.AbstractAction;
49 import javax.swing.Action;
50 import javax.swing.ActionMap;
51 import javax.swing.BorderFactory;
52 import javax.swing.Box;
53 import javax.swing.BoxLayout;
54 import javax.swing.ButtonGroup;
55 import javax.swing.InputMap;
56 import javax.swing.JButton;
57 import javax.swing.JCheckBox;
58 import javax.swing.JComponent;
59 import javax.swing.JDialog;
60 import javax.swing.JFileChooser;
61 import javax.swing.JFrame;
62 import javax.swing.JLabel;
63 import javax.swing.JOptionPane;
64 import javax.swing.JPanel;
65 import javax.swing.JPopupMenu;
66 import javax.swing.JRadioButton;
67 import javax.swing.JRootPane;
68 import javax.swing.JScrollPane;
69 import javax.swing.JTable;
70 import javax.swing.JTextArea;
71 import javax.swing.JTextField;
72 import javax.swing.KeyStroke;
73 import javax.swing.ListSelectionModel;
74 import javax.swing.event.ChangeEvent;
75 import javax.swing.event.ChangeListener;
76 import javax.swing.event.DocumentEvent;
77 import javax.swing.event.DocumentListener;
78 import javax.swing.event.TableModelEvent;
79 import javax.swing.event.TableModelListener;
80 import javax.swing.table.TableCellRenderer;
81 import javax.swing.table.TableColumnModel;
82
83 import charactermanaj.Main;
84 import charactermanaj.graphics.io.PNGFileImageHeader;
85 import charactermanaj.model.AppConfig;
86 import charactermanaj.model.CharacterData;
87 import charactermanaj.model.CustomLayerOrder;
88 import charactermanaj.model.CustomLayerOrderKey;
89 import charactermanaj.model.PartsAuthorInfo;
90 import charactermanaj.model.PartsCategory;
91 import charactermanaj.model.PartsIdentifier;
92 import charactermanaj.model.PartsManageData;
93 import charactermanaj.model.PartsSet;
94 import charactermanaj.model.PartsSpec;
95 import charactermanaj.model.io.AbstractCharacterDataArchiveFile.CategoryLayerPair;
96 import charactermanaj.model.io.AbstractCharacterDataArchiveFile.PartsImageContent;
97 import charactermanaj.model.io.CharacterDataFileReaderWriterFactory;
98 import charactermanaj.model.io.CharacterDataPersistent;
99 import charactermanaj.model.io.ImportModel;
100 import charactermanaj.ui.model.AbstractTableModelWithComboBoxModel;
101 import charactermanaj.ui.progress.ProgressHandle;
102 import charactermanaj.ui.progress.Worker;
103 import charactermanaj.ui.progress.WorkerException;
104 import charactermanaj.ui.progress.WorkerWithProgessDialog;
105 import charactermanaj.ui.util.FileDropTarget;
106 import charactermanaj.ui.util.ScaleSupport;
107 import charactermanaj.util.DownloadUtils;
108 import charactermanaj.util.DownloadUtils.HeadResponse;
109 import charactermanaj.util.ErrorMessageHelper;
110 import charactermanaj.util.LocalizedResourcePropertyLoader;
111
112
113 /**
114  * インポートウィザードダイアログ.<br>
115  *
116  * @author seraphy
117  */
118 public class ImportWizardDialog extends JDialog {
119
120         private static final long serialVersionUID = 1L;
121
122
123         protected static final String STRINGS_RESOURCE = "languages/importwizdialog";
124
125
126         public static final int EXIT_PROFILE_UPDATED = 1;
127
128         public static final int EXIT_PROFILE_CREATED = 2;
129
130         public static final int EXIT_CANCELED = 0;
131
132
133         /**
134          * インポートウィザードの実行結果.<br>
135          */
136         private int exitCode = EXIT_CANCELED;
137
138         /**
139          * インポートされたキャラクターデータ.<br>
140          */
141         private CharacterData importedCharacterData;
142
143
144         /**
145          * 現在表示中もしくは選択中のプロファイル.<br>
146          * 新規の場合はnull
147          */
148         private final CharacterData targetCd;
149
150
151         private CardLayout mainPanelLayout;
152
153         private ImportWizardCardPanel activePanel;
154
155         private AbstractAction actNext;
156
157         private AbstractAction actPrev;
158
159         private AbstractAction actFinish;
160
161
162         protected ImportFileSelectPanel importFileSelectPanel;
163
164         protected ImportTypeSelectPanel importTypeSelectPanel;
165
166         protected ImportPartsSelectPanel importPartsSelectPanel;
167
168         protected ImportPresetSelectPanel importPresetSelectPanel;
169
170         protected ImportModel importModel = new ImportModel();
171
172         /**
173          * プロファイルにパーツデータ・プリセットデータをインポートします.<br>
174          *
175          * @param parent
176          *            親フレーム
177          * @param current
178          *            更新対象となる現在のプロファイル(新規インポートの場合はnull)
179          */
180         public ImportWizardDialog(JFrame parent, CharacterData current) {
181                 super(parent, true);
182                 this.targetCd = current;
183                 if (targetCd != null && !targetCd.getDocBase().getScheme().equals("file")) {
184                         throw new IllegalArgumentException("ファイルベース以外のキャラクターデータにインポートできません");
185                 }
186                 if (targetCd != null && !targetCd.isValid()) {
187                         throw new IllegalArgumentException("妥当でないキャラクターデータにはインポートできません");
188                 }
189                 initComponent();
190         }
191
192         /**
193          * プロファイルにパーツデータ・プリセットデータをインポートします.<br>
194          *
195          * @param parent
196          *            親ダイアログ
197          * @param current
198          *            選択していてるプロファイル、新規インポートの場合はnull
199          */
200         public ImportWizardDialog(JDialog parent, CharacterData current) {
201                 super(parent, true);
202                 this.targetCd = current;
203                 if (targetCd != null && !targetCd.getDocBase().getScheme().equals("file")) {
204                         throw new IllegalArgumentException("ファイルベース以外のキャラクターデータにインポートできません");
205                 }
206                 if (targetCd != null && !targetCd.isValid()) {
207                         throw new IllegalArgumentException("妥当でないキャラクターデータにはインポートできません");
208                 }
209                 initComponent();
210         }
211
212         /**
213          * インポート先のキャラクターデータを返す。新規の場合はnull
214          * @return インポート先、新規の場合はnull
215          */
216         public CharacterData getTargetCharacterData() {
217                 if (targetCd != null && !targetCd.getDocBase().getScheme().equals("file")) {
218                         throw new IllegalStateException("ファイルベース以外のキャラクターデータにインポートできません: " + targetCd);
219                 }
220                 return targetCd;
221         }
222
223         /**
224          * @param initFiles
225          *            アーカイブファィルまたはディレクトリの初期選択、なければnullまたは空
226          */
227         public void initSelectFile(File initFile) {
228                 importFileSelectPanel.setSelectFile(initFile);
229         }
230
231         public void initSelectURL(String url) {
232                 importFileSelectPanel.initSelectURL(url);
233         }
234
235         /**
236          * ウィザードダイアログのコンポーネントを初期化します.<br>
237          * currentがnullの場合は新規インポート、そうでない場合は更新インポートとります。
238          */
239         private void initComponent() {
240
241                 setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
242                 addWindowListener(new WindowAdapter() {
243                         @Override
244                         public void windowClosing(WindowEvent e) {
245                                 onClose();
246                         }
247                 });
248
249                 Properties strings = LocalizedResourcePropertyLoader.getCachedInstance()
250                                 .getLocalizedProperties(STRINGS_RESOURCE);
251
252                 // タイトル
253                 if (targetCd == null) {
254                         setTitle(strings.getProperty("title.new"));
255                 } else {
256                         setTitle(strings.getProperty("title.update"));
257                 }
258
259                 // メインパネル
260
261                 ScaleSupport scaleSupport = ScaleSupport.getInstance(this);
262
263                 Container contentPane = getContentPane();
264                 contentPane.setLayout(new BorderLayout());
265
266                 final JPanel mainPanel = new JPanel();
267                 mainPanel.setBorder(BorderFactory.createEtchedBorder());
268                 int gap = (int)(5 * scaleSupport.getManualScaleX());
269                 this.mainPanelLayout = new CardLayout(gap, gap);
270                 mainPanel.setLayout(mainPanelLayout);
271                 contentPane.add(mainPanel, BorderLayout.CENTER);
272
273                 ChangeListener changeListener = new ChangeListener() {
274                         public void stateChanged(ChangeEvent e) {
275                                 updateBtnPanelState();
276                         }
277                 };
278
279                 ComponentListener componentListener = new ComponentAdapter() {
280                         public void componentShown(ComponentEvent e) {
281                                 onComponentShown((ImportWizardCardPanel) e.getComponent());
282                         }
283                 };
284
285
286                 // アクション
287
288                 this.actNext = new AbstractAction(strings.getProperty("btn.next")) {
289                         private static final long serialVersionUID = 1L;
290                         public void actionPerformed(ActionEvent e) {
291                                 setEnableButtons(false);
292                                 String nextPanelName = doNext();
293                                 if (nextPanelName != null) {
294                                         mainPanelLayout.show(mainPanel, nextPanelName);
295                                 } else {
296                                         // 移動先ページ名なければ、現在のページでボタン状態を再設定する.
297                                         // 移動先ページ名がある場合、実際に移動し表示されるまでディセーブルのままとする.
298                                         updateBtnPanelState();
299                                 }
300                         }
301                 };
302                 this.actPrev = new AbstractAction(strings.getProperty("btn.prev")) {
303                         private static final long serialVersionUID = 1L;
304                         public void actionPerformed(ActionEvent e) {
305                                 setEnableButtons(false);
306                                 String prevPanelName = doPrevious();
307                                 if (prevPanelName != null) {
308                                         mainPanelLayout.show(mainPanel, prevPanelName);
309                                 } else {
310                                         updateBtnPanelState();
311                                 }
312                         }
313                 };
314                 this.actFinish = new AbstractAction(strings.getProperty("btn.finish")) {
315                         private static final long serialVersionUID = 1L;
316                         public void actionPerformed(ActionEvent e) {
317                                 onFinish();
318                         }
319                 };
320                 AbstractAction actCancel = new AbstractAction(strings.getProperty("btn.cancel")) {
321                         private static final long serialVersionUID = 1L;
322                         public void actionPerformed(ActionEvent e) {
323                                 onClose();
324                         }
325                 };
326
327                 // ImportFileSelectPanel
328                 this.importFileSelectPanel = new ImportFileSelectPanel(scaleSupport);
329                 this.importFileSelectPanel.addComponentListener(componentListener);
330                 this.importFileSelectPanel.addChangeListener(changeListener);
331                 mainPanel.add(this.importFileSelectPanel, ImportFileSelectPanel.PANEL_NAME);
332
333                 // ImportTypeSelectPanel
334                 this.importTypeSelectPanel = new ImportTypeSelectPanel(scaleSupport);
335                 this.importTypeSelectPanel.addComponentListener(componentListener);
336                 this.importTypeSelectPanel.addChangeListener(changeListener);
337                 mainPanel.add(this.importTypeSelectPanel, ImportTypeSelectPanel.PANEL_NAME);
338
339                 // ImportPartsSelectPanel
340                 this.importPartsSelectPanel = new ImportPartsSelectPanel(scaleSupport);
341                 this.importPartsSelectPanel.addComponentListener(componentListener);
342                 this.importPartsSelectPanel.addChangeListener(changeListener);
343                 mainPanel.add(this.importPartsSelectPanel, ImportPartsSelectPanel.PANEL_NAME);
344
345                 // ImportPresetSelectPanel
346                 this.importPresetSelectPanel = new ImportPresetSelectPanel(scaleSupport);
347                 this.importPresetSelectPanel.addComponentListener(componentListener);
348                 this.importPresetSelectPanel.addChangeListener(changeListener);
349                 mainPanel.add(this.importPresetSelectPanel, ImportPresetSelectPanel.PANEL_NAME);
350
351
352                 // button panel
353
354                 JPanel btnPanel = new JPanel();
355                 int mergin = (int)(3 * scaleSupport.getManualScaleX());
356                 btnPanel.setBorder(BorderFactory.createEmptyBorder(mergin, mergin, mergin, mergin * 15)); // 3,3,3,45
357                 GridBagLayout btnPanelLayout = new GridBagLayout();
358                 btnPanel.setLayout(btnPanelLayout);
359
360                 actPrev.setEnabled(false);
361                 actNext.setEnabled(false);
362                 actFinish.setEnabled(false);
363
364                 GridBagConstraints gbc = new GridBagConstraints();
365
366                 gbc.gridx = 0;
367                 gbc.gridy = 0;
368                 gbc.gridheight = 1;
369                 gbc.gridwidth = 1;
370                 gbc.anchor = GridBagConstraints.EAST;
371                 gbc.fill = GridBagConstraints.BOTH;
372                 gbc.ipadx = 0;
373                 gbc.ipady = 0;
374                 gbc.insets = new Insets(mergin, mergin, mergin, mergin);
375                 gbc.weightx = 1.;
376                 gbc.weighty = 0.;
377                 btnPanel.add(Box.createHorizontalGlue(), gbc);
378
379                 gbc.gridx = Main.isLinuxOrMacOSX() ? 2 : 1;
380                 gbc.gridy = 0;
381                 gbc.weightx = 0.;
382                 btnPanel.add(new JButton(this.actPrev), gbc);
383
384                 gbc.gridx = Main.isLinuxOrMacOSX() ? 3 : 2;
385                 gbc.gridy = 0;
386                 JButton btnNext = new JButton(this.actNext);
387                 btnPanel.add(btnNext, gbc);
388
389                 gbc.gridx = Main.isLinuxOrMacOSX() ? 4 : 3;
390                 gbc.gridy = 0;
391                 btnPanel.add(new JButton(this.actFinish), gbc);
392
393                 gbc.gridx = Main.isLinuxOrMacOSX() ? 1 : 4;
394                 gbc.gridy = 0;
395                 JButton btnCancel = new JButton(actCancel);
396                 btnPanel.add(btnCancel, gbc);
397
398                 contentPane.add(btnPanel, BorderLayout.SOUTH);
399
400                 // インプットマップ/アクションマップ
401
402                 Toolkit tk = Toolkit.getDefaultToolkit();
403                 JRootPane rootPane = getRootPane();
404
405                 rootPane.setDefaultButton(btnNext);
406
407                 InputMap im = rootPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
408                 ActionMap am = rootPane.getActionMap();
409                 im.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "closeImportWizDialog");
410                 im.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, tk.getMenuShortcutKeyMask()), "closeImportWizDialog");
411                 am.put("closeImportWizDialog", actCancel);
412
413                 // 表示
414
415                 Dimension dim = new Dimension(500, 500);
416                 // HiDpi環境でのスケールを考慮したウィンドウサイズに補正する
417                 dim = scaleSupport.manualScaled(dim);
418                 setSize(dim);
419                 setLocationRelativeTo(getParent());
420
421                 mainPanelLayout.first(mainPanel);
422                 updateBtnPanelState();
423         }
424
425         protected void onComponentShown(JPanel panel) {
426                 ImportWizardCardPanel activePanel = (ImportWizardCardPanel) panel;
427                 activePanel.onActive(this, this.activePanel);
428                 this.activePanel = activePanel;
429                 updateBtnPanelState();
430         }
431
432         protected void updateBtnPanelState() {
433                 if (activePanel != null) {
434                         actPrev.setEnabled(activePanel.isReadyPrevious());
435                         actNext.setEnabled(activePanel.isReadyNext());
436                         actFinish.setEnabled(activePanel.isReadyFinish());
437
438                 } else {
439                         setEnableButtons(false);
440                 }
441         }
442
443         public void setEnableButtons(boolean enabled) {
444                 actPrev.setEnabled(enabled);
445                 actNext.setEnabled(enabled);
446                 actFinish.setEnabled(enabled);
447         }
448
449         protected String doNext() {
450                 if (activePanel == null) {
451                         throw new IllegalStateException();
452                 }
453                 String nextPanelName;
454
455                 setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
456                 try {
457                         nextPanelName = activePanel.doNext();
458                 } finally {
459                         setCursor(Cursor.getDefaultCursor());
460                 }
461                 return nextPanelName;
462         }
463
464         protected String doPrevious() {
465                 if (activePanel == null) {
466                         throw new IllegalStateException();
467                 }
468                 return activePanel.doPrevious();
469         }
470
471         protected void onClose() {
472                 Properties strings = LocalizedResourcePropertyLoader.getCachedInstance()
473                                 .getLocalizedProperties(STRINGS_RESOURCE);
474
475                 if (JOptionPane.showConfirmDialog(this,
476                                 strings.getProperty("confirm.close"),
477                                 strings.getProperty("confirm"),
478                                 JOptionPane.YES_NO_OPTION,
479                                 JOptionPane.QUESTION_MESSAGE) != JOptionPane.YES_OPTION) {
480                         return;
481                 }
482
483                 // アーカイブを閉じる.
484                 importFileSelectPanel.closeArchive();
485
486                 // キャンセル
487                 this.exitCode = EXIT_CANCELED;
488                 this.importedCharacterData = null;
489
490                 // ウィンドウを閉じる
491                 dispose();
492         }
493
494         /**
495          * インポートの実行.<br>
496          */
497         protected void onFinish() {
498                 Properties strings = LocalizedResourcePropertyLoader.getCachedInstance()
499                                 .getLocalizedProperties(ImportWizardDialog.STRINGS_RESOURCE);
500
501                 try {
502                         // 新規プロファイル作成、または更新の実行
503                         setEnableButtons(false);
504                         setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
505                         int exitCode;
506                         CharacterData importedCharacterData;
507                         try {
508                                 if (targetCd == null) {
509                                         // 新規作成
510                                         importedCharacterData = createNewProfile();
511                                         exitCode = EXIT_PROFILE_CREATED;
512                                 } else {
513                                         // 更新
514                                         importedCharacterData = updateProfile(targetCd.duplicateBasicInfo());
515                                         exitCode = EXIT_PROFILE_UPDATED;
516                                 }
517                         } finally {
518                                 setCursor(Cursor.getDefaultCursor());
519                         }
520
521                         // アーカイブを閉じる
522                         importFileSelectPanel.closeArchive();
523
524                         // 完了メッセージ
525                         JOptionPane.showMessageDialog(this, strings.getProperty("complete"));
526
527                         // 完了後、ウィンドウを閉じる.
528                         this.exitCode = exitCode;
529                         this.importedCharacterData = importedCharacterData;
530                         dispose();
531
532                 } catch (Exception ex) {
533                         ErrorMessageHelper.showErrorDialog(this, ex);
534                         // ディセーブルにしていたボタンをパネルの状態に戻す.
535                         updateBtnPanelState();
536                 }
537         }
538
539         /**
540          * ウィザードが閉じられた場合の終了コード. {@link #EXIT_PROFILE_UPDATED}であればプロファイルが更新されており,<br>
541          * {@link #EXIT_PROFILE_CREATED}であればプロファイルが作成されている.<br>
542          * {@link #EXIT_CANCELED}であればキャンセルされている.<br>
543          *
544          * @return 終了コード
545          */
546         public int getExitCode() {
547                 return exitCode;
548         }
549
550         /**
551          * 新規または更新されたプロファイル、キャンセルされた場合はnull
552          *
553          * @return プロファイル
554          */
555         public CharacterData getImportedCharacterData() {
556                 return importedCharacterData;
557         }
558
559         /**
560          * アーカイブからの新規プロファイルの作成
561          *
562          * @return 作成された新規プロファイル
563          * @throws IOException
564          *             失敗
565          */
566         protected CharacterData createNewProfile() throws IOException {
567                 CharacterData cd = importModel.getCharacterData();
568                 if (cd == null || !cd.isValid()) {
569                         throw new IllegalStateException("imported caharcer data is invalid." + cd);
570                 }
571
572                 CharacterDataPersistent persist = CharacterDataPersistent.getInstance();
573
574                 CharacterData characterData = cd.duplicateBasicInfo(); // インポートしたキャラクターデータ
575                 Map<CustomLayerOrderKey, List<CustomLayerOrder>> customLayerPatterns = importModel.getCustomLayerPatternMap();
576
577                 // キャラクターセット名と作者名を設定する
578                 characterData.setName(importTypeSelectPanel.getCharacterName());
579                 characterData.setAuthor(importTypeSelectPanel.getAuthor());
580
581                 // プリセットをインポートする場合
582                 characterData.clearPartsSets(false);
583                 if (importTypeSelectPanel.isImportPreset()) {
584                         for (PartsSet partsSet : importPresetSelectPanel.getSelectedPartsSets()) {
585                                 PartsSet ps = partsSet.clone();
586                                 ps.setPresetParts(true);
587                                 characterData.addPartsSet(ps);
588                         }
589                         characterData.setDefaultPartsSetId(importPresetSelectPanel.getPrefferedDefaultPartsSetId());
590                 }
591
592                 // プロファイルの新規作成
593                 // docBaseが設定されて返される.
594                 persist.createProfile(characterData, customLayerPatterns);
595
596                 // インポートするパーツの更新
597                 if (importTypeSelectPanel.isImportPartsImages()) {
598                         // パーツのコピー
599                         Collection<PartsImageContent> partsImageContents
600                                 = importPartsSelectPanel.getSelectedPartsImageContents();
601                         importModel.copyPartsImageContents(partsImageContents, characterData);
602
603                         // パーツ管理情報の登録
604                         PartsManageData partsManageData = importModel.getPartsManageData();
605                         importModel.updatePartsManageData(partsImageContents, partsManageData, null, characterData);
606                 }
607
608                 // インポートするピクチャの更新
609                 if (importTypeSelectPanel.isImportSampleImage()) {
610                         BufferedImage samplePicture = importModel.getSamplePicture();
611                         if (samplePicture != null) {
612                                 persist.saveSamplePicture(characterData, samplePicture);
613                         }
614                 }
615
616                 return characterData;
617         }
618
619         /**
620          * プロファイルの更新
621          *
622          * @return 更新されたプロファイル
623          * @throws IOException
624          *             失敗
625          */
626         protected CharacterData updateProfile(CharacterData characterData) throws IOException {
627                 CharacterDataPersistent persist = CharacterDataPersistent.getInstance();
628
629                 boolean imported = false;
630                 boolean modCharacterDef = false;
631                 boolean modFavories = false;
632
633                 // インポートするパーツの更新
634                 if (importTypeSelectPanel.isImportPartsImages()) {
635                         // パーツのコピー
636                         Collection<PartsImageContent> partsImageContents
637                                 = importPartsSelectPanel.getSelectedPartsImageContents();
638                         importModel.copyPartsImageContents(partsImageContents, characterData);
639
640                         // パーツ管理情報の追記・更新
641                         PartsManageData partsManageData = importModel.getPartsManageData();
642                         importModel.updatePartsManageData(partsImageContents, partsManageData, characterData, characterData);
643
644                         imported = true;
645                 }
646
647                 // インポートするピクチャの更新
648                 if (importTypeSelectPanel.isImportSampleImage()) {
649                         BufferedImage samplePicture = importModel.getSamplePicture();
650                         if (samplePicture != null) {
651                                 persist.saveSamplePicture(characterData, samplePicture);
652                                 imported = true;
653                         }
654                 }
655
656                 // インポートするパーツセットの更新
657                 if (importTypeSelectPanel.isImportPreset()) {
658                         for (PartsSet partsSet : importPresetSelectPanel.getSelectedPartsSets()) {
659                                 PartsSet ps = partsSet.clone();
660                                 ps.setPresetParts(false);
661                                 characterData.addPartsSet(ps);
662                         }
663                         imported = true;
664                         modCharacterDef = true;
665                         modFavories = true;
666                 }
667
668                 // 説明の更新
669                 if (importTypeSelectPanel.isAddDescription() && imported) {
670                         URI archivedFile = importModel.getImportSource();
671                         String note = importTypeSelectPanel.getAdditionalDescription();
672                         if (note != null && note.length() > 0) {
673                                 String description = characterData.getDescription();
674                                 if (description == null) {
675                                         description = "";
676                                 }
677                                 String lf = System.getProperty("line.separator");
678                                 Timestamp tm = new Timestamp(System.currentTimeMillis());
679                                 description += lf + "--- import: " + tm + " : " + archivedFile + " ---" + lf;
680                                 description += note + lf;
681                                 characterData.setDescription(description);
682                                 modCharacterDef = true;
683                         }
684                 }
685
686                 // キャラクター定義の更新
687                 if (modCharacterDef) {
688                         persist.updateProfile(characterData); // キャラクター定義の構造に変化なし
689                         targetCd.setDescription(characterData.getDescription()); // 現在保持しているCdの説明文は更新しておく(念のため)
690                 }
691                 // お気に入りの更新
692                 if (modFavories) {
693                         persist.saveFavorites(characterData);
694                 }
695
696                 return characterData;
697         }
698
699
700 }
701
702 /**
703  * タブの抽象基底クラス.<br>
704  *
705  * @author seraphy
706  */
707 abstract class ImportWizardCardPanel extends JPanel {
708
709         private static final long serialVersionUID = 1L;
710
711         private LinkedList<ChangeListener> listeners = new LinkedList<ChangeListener>();
712
713         public void addChangeListener(ChangeListener l) {
714                 if (l != null) {
715                         listeners.add(l);
716                 }
717         }
718
719         public void removeChangeListener(ChangeListener l) {
720                 if (l != null) {
721                         listeners.remove(l);
722                 }
723         }
724
725         public void fireChangeEvent() {
726                 ChangeEvent e = new ChangeEvent(this);
727                 for (ChangeListener l : listeners) {
728                         l.stateChanged(e);
729                 }
730         }
731
732         public void onActive(ImportWizardDialog parent, ImportWizardCardPanel previousPanel) {
733                 // なにもしない
734         }
735
736         public boolean isReadyPrevious() {
737                 return false;
738         }
739
740         public boolean isReadyNext() {
741                 return false;
742         }
743
744         public boolean isReadyFinish() {
745                 return false;
746         }
747
748         public String doNext() {
749                 throw new UnsupportedOperationException();
750         }
751
752         public String doPrevious() {
753                 throw new UnsupportedOperationException();
754         }
755 }
756
757 /**
758  * ファイル選択パネル
759  *
760  * @author seraphy
761  */
762 class ImportFileSelectPanel extends ImportWizardCardPanel {
763
764         private static final long serialVersionUID = 1L;
765
766         public static final String PANEL_NAME = "fileSelectPanel";
767
768         /**
769          * アーカイブ用ファイルダイアログ
770          */
771         private static ArchiveFileDialog archiveFileDialog = new ArchiveFileDialog();
772
773         private ImportWizardDialog parent;
774
775         /**
776          * ファイル名を指定してインポート
777          */
778         private JRadioButton radioArchiveFile;
779
780         /**
781          * ファイル名入力ボックス
782          */
783         private JTextField txtArchiveFile;
784
785         /**
786          * ファイル選択ボタン
787          */
788         private Action actChooseFile;
789
790
791         /**
792          * ディレクトリを指定してインポート
793          */
794         private JRadioButton radioDirectory;
795
796         /**
797          * ディレクトリ入力ボックス
798          */
799         private JTextField txtDirectory;
800
801         /**
802          * ディレクトリ選択ボタン
803          */
804         private Action actChooseDirectory;
805
806
807         /**
808          * URLを指定してインポート
809          */
810         private JRadioButton radioURL;
811
812         /**
813          * URL入力ボックス
814          */
815         private JTextField txtURL;
816
817
818         /* 以下、対象ファイルの読み取り結果 */
819
820
821         public ImportFileSelectPanel(ScaleSupport scaleSupport) {
822                 setLayout(new BorderLayout());
823
824                 Properties strings = LocalizedResourcePropertyLoader.getCachedInstance()
825                                 .getLocalizedProperties(ImportWizardDialog.STRINGS_RESOURCE);
826
827                 DocumentListener documentListener = new DocumentListener() {
828                         public void removeUpdate(DocumentEvent e) {
829                                 fireEvent();
830                         }
831                         public void insertUpdate(DocumentEvent e) {
832                                 fireEvent();
833                         }
834                         public void changedUpdate(DocumentEvent e) {
835                                 fireEvent();
836                         }
837                         protected void fireEvent() {
838                                 fireChangeEvent();
839                         }
840                 };
841
842                 txtArchiveFile = new JTextField();
843                 txtDirectory = new JTextField();
844                 txtURL = new JTextField();
845
846                 txtArchiveFile.getDocument().addDocumentListener(documentListener);
847                 txtDirectory.getDocument().addDocumentListener(documentListener);
848                 txtURL.getDocument().addDocumentListener(documentListener);
849
850                 actChooseFile = new AbstractAction(strings.getProperty("browse")) {
851                         private static final long serialVersionUID = 1L;
852                         public void actionPerformed(ActionEvent e) {
853                                 onChooseFile();
854                         }
855                 };
856
857                 actChooseDirectory = new AbstractAction(strings.getProperty("browse")) {
858                         private static final long serialVersionUID = 1L;
859                         public void actionPerformed(ActionEvent e) {
860                                 onChooseDirectory();
861                         }
862                 };
863
864
865                 JPanel fileChoosePanel = new JPanel();
866                 GridBagLayout fileChoosePanelLayout = new GridBagLayout();
867                 fileChoosePanel.setLayout(fileChoosePanelLayout);
868
869                 radioArchiveFile = new JRadioButton(strings.getProperty("importingArchiveFile"));
870                 radioDirectory = new JRadioButton(strings.getProperty("importingDirectory"));
871                 radioURL = new JRadioButton(strings.getProperty("importingURL"));
872
873                 ChangeListener radioChangeListener = new ChangeListener() {
874                         public void stateChanged(ChangeEvent e) {
875                                 updateUIState();
876                                 fireChangeEvent();
877                         }
878                 };
879                 radioArchiveFile.addChangeListener(radioChangeListener);
880                 radioDirectory.addChangeListener(radioChangeListener);
881                 radioURL.addChangeListener(radioChangeListener);
882
883                 ButtonGroup btnGroup = new ButtonGroup();
884                 btnGroup.add(radioArchiveFile);
885                 btnGroup.add(radioDirectory);
886                 btnGroup.add(radioURL);
887
888                 // アーカイブからのインポートをデフォルトとする
889                 radioArchiveFile.setSelected(true);
890
891                 GridBagConstraints gbc = new GridBagConstraints();
892
893                 gbc.ipadx = 0;
894                 gbc.ipady = 0;
895                 int gap = (int)(3 * scaleSupport.getManualScaleX());
896                 gbc.insets = new Insets(gap, gap, gap, gap);
897                 gbc.anchor = GridBagConstraints.WEST;
898                 gbc.fill = GridBagConstraints.BOTH;
899
900                 // アーカイブ
901                 gbc.gridx = 0;
902                 gbc.gridy = 0;
903                 gbc.weightx = 1.;
904                 gbc.weighty = 0.;
905                 gbc.gridheight = 1;
906                 gbc.gridwidth = 3;
907                 fileChoosePanel.add(radioArchiveFile, gbc);
908
909                 gbc.gridx = 0;
910                 gbc.gridy = 1;
911                 gbc.gridwidth = 1;
912                 gbc.ipadx = 45;
913                 gbc.weightx = 0;
914                 fileChoosePanel.add(Box.createHorizontalGlue(), gbc);
915
916                 gbc.gridx = 1;
917                 gbc.gridy = 1;
918                 gbc.ipadx = 0;
919                 gbc.weightx = 1.;
920                 fileChoosePanel.add(txtArchiveFile, gbc);
921
922                 gbc.gridx = 2;
923                 gbc.gridy = 1;
924                 gbc.ipadx = 0;
925                 gbc.weightx = 0.;
926                 fileChoosePanel.add(new JButton(actChooseFile), gbc);
927
928                 // ディレクトり
929                 gbc.gridx = 0;
930                 gbc.gridy = 2;
931                 gbc.ipadx = 0;
932                 gbc.gridwidth = 3;
933                 gbc.weightx = 1.;
934                 fileChoosePanel.add(radioDirectory, gbc);
935
936                 gbc.gridx = 0;
937                 gbc.gridy = 3;
938                 gbc.ipadx = 45;
939                 gbc.gridwidth = 1;
940                 gbc.weightx = 0;
941                 fileChoosePanel.add(Box.createHorizontalGlue(), gbc);
942
943                 gbc.gridx = 1;
944                 gbc.gridy = 3;
945                 gbc.ipadx = 0;
946                 gbc.weightx = 1.;
947                 fileChoosePanel.add(txtDirectory, gbc);
948
949                 gbc.gridx = 2;
950                 gbc.gridy = 3;
951                 gbc.ipadx = 0;
952                 gbc.weightx = 0.;
953                 fileChoosePanel.add(new JButton(actChooseDirectory), gbc);
954
955                 // URL
956                 gbc.gridx = 0;
957                 gbc.gridy = 4;
958                 gbc.weightx = 1.;
959                 gbc.weighty = 0.;
960                 gbc.gridheight = 1;
961                 gbc.gridwidth = 3;
962                 fileChoosePanel.add(radioURL, gbc);
963
964                 gbc.gridx = 0;
965                 gbc.gridy = 4;
966                 gbc.gridwidth = 1;
967                 gbc.ipadx = 45;
968                 gbc.weightx = 0;
969                 fileChoosePanel.add(Box.createHorizontalGlue(), gbc);
970
971                 gbc.gridx = 1;
972                 gbc.gridy = 5;
973                 gbc.ipadx = 0;
974                 gbc.weightx = 1.;
975                 fileChoosePanel.add(txtURL, gbc);
976
977                 // パディング
978                 gbc.gridx = 0;
979                 gbc.gridy = 6;
980                 gbc.ipadx = 0;
981                 gbc.gridwidth = 3;
982                 gbc.weightx = 1.;
983                 gbc.weighty = 1.;
984                 fileChoosePanel.add(Box.createGlue(), gbc);
985
986                 add(fileChoosePanel, BorderLayout.CENTER);
987
988                 // ドロップターゲット
989                 new DropTarget(this, new FileDropTarget() {
990                         @Override
991                         protected void onDropFiles(List<File> dropFiles) {
992                                 if (dropFiles == null || dropFiles.isEmpty()) {
993                                         return;
994                                 }
995                                 File dropFile = dropFiles.get(0);
996                                 setSelectFile(dropFile);
997                         }
998
999                         @Override
1000                         protected void onException(Exception ex) {
1001                                 ErrorMessageHelper.showErrorDialog(ImportFileSelectPanel.this, ex);
1002                         }
1003                 });
1004
1005                 updateUIState();
1006         }
1007
1008         /**
1009          * アーカイブファイルまたはディレクトリを選択状態とする.<br>
1010          * nullの場合は選択を解除する.
1011          *
1012          * @param dropFile
1013          *            アーカイブファイルまたはディレクトリ、もしくはnull
1014          */
1015         public void setSelectFile(File dropFile) {
1016                 if (dropFile == null) {
1017                         // 選択なしの場合
1018                         txtDirectory.setText("");
1019                         txtArchiveFile.setText("");
1020                         txtURL.setText("");
1021                         radioDirectory.setSelected(false);
1022                         radioArchiveFile.setSelected(false);
1023                         radioURL.setSelected(false);
1024
1025                 } else if (dropFile.isDirectory()) {
1026                         // ディレクトリの場合
1027                         txtDirectory.setText(dropFile.getPath());
1028                         radioDirectory.setSelected(true);
1029
1030                 } else if (dropFile.isFile()) {
1031                         // ファイルの場合
1032                         txtArchiveFile.setText(dropFile.getPath());
1033                         radioArchiveFile.setSelected(true);
1034                 }
1035         }
1036
1037         /**
1038          * URL入力を選択状態とする
1039          * @param url URL
1040          */
1041         public void initSelectURL(String url) {
1042                 if (url != null && (url.startsWith("http://") || url.startsWith("https://"))) {
1043                         // URL指定の場合
1044                         txtURL.setText(url);
1045                         radioURL.setSelected(true);
1046
1047                 } else {
1048                         // 選択なしの場合
1049                         txtDirectory.setText("");
1050                         txtArchiveFile.setText("");
1051                         txtURL.setText("");
1052                         radioDirectory.setSelected(false);
1053                         radioArchiveFile.setSelected(false);
1054                         radioURL.setSelected(false);
1055                 }
1056         }
1057
1058         protected void updateUIState() {
1059                 boolean enableArchiveFile = radioArchiveFile.isSelected();
1060                 boolean enableDirectory = radioDirectory.isSelected();
1061                 boolean enableURL = radioURL.isSelected();
1062
1063                 txtArchiveFile.setEnabled(enableArchiveFile);
1064                 actChooseFile.setEnabled(enableArchiveFile);
1065
1066                 txtDirectory.setEnabled(enableDirectory);
1067                 actChooseDirectory.setEnabled(enableDirectory);
1068
1069                 txtURL.setEnabled(enableURL);
1070         }
1071
1072         protected void onChooseFile() {
1073                 File initFile = null;
1074                 if (txtArchiveFile.getText().trim().length() > 0) {
1075                         initFile = new File(txtArchiveFile.getText());
1076                 }
1077                 File file = archiveFileDialog.showOpenDialog(this, initFile);
1078                 if (file != null) {
1079                         txtArchiveFile.setText(file.getPath());
1080                         fireChangeEvent();
1081                 }
1082         }
1083
1084         protected void onChooseDirectory() {
1085                 String directoryTxt = txtDirectory.getText();
1086                 JFileChooser dirChooser = new JFileChooser(directoryTxt);
1087                 dirChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
1088
1089                 if (dirChooser.showOpenDialog(this) != JFileChooser.APPROVE_OPTION) {
1090                         return;
1091                 }
1092                 File dir = dirChooser.getSelectedFile();
1093                 if (dir != null) {
1094                         txtDirectory.setText(dir.getPath());
1095                         fireChangeEvent();
1096                 }
1097         }
1098
1099         @Override
1100         public boolean isReadyNext() {
1101                 if (radioArchiveFile.isSelected()) {
1102                         String fileTxt = txtArchiveFile.getText();
1103                         if (fileTxt != null && fileTxt.trim().length() > 0) {
1104                                 return true;
1105                         }
1106                 } else if (radioDirectory.isSelected()) {
1107                         String directoryTxt = txtDirectory.getText();
1108                         if (directoryTxt != null && directoryTxt.trim().length() > 0) {
1109                                 return true;
1110                         }
1111                 } else if (radioURL.isSelected()) {
1112                         String url = txtURL.getText();
1113                         if (url != null && (url.startsWith("http://") || url.startsWith("https://"))) {
1114                                 return true;
1115                         }
1116                 }
1117                 return false;
1118         }
1119
1120         @Override
1121         public void onActive(ImportWizardDialog parent, ImportWizardCardPanel previousPanel) {
1122                 this.parent = parent;
1123
1124                 // 開いているアーカイブがあれば閉じる
1125                 closeArchive();
1126         }
1127
1128         /**
1129          * 開いているアーカイブがあればクローズする.
1130          */
1131         public void closeArchive() {
1132                 try {
1133                         parent.importModel.closeImportSource();
1134
1135                 } catch (IOException ex) {
1136                         ErrorMessageHelper.showErrorDialog(this, ex);
1137                         // エラーが発生しても、とりあえず無視する.
1138                 }
1139         }
1140
1141         @Override
1142         public String doNext() {
1143                 if (!isReadyNext()) {
1144                         return null;
1145                 }
1146
1147                 Properties strings = LocalizedResourcePropertyLoader.getCachedInstance()
1148                         .getLocalizedProperties(ImportWizardDialog.STRINGS_RESOURCE);
1149
1150                 URI importArchive;
1151                 if (radioArchiveFile.isSelected()) {
1152                         // ファイルによるインポート
1153                         File file = new File(txtArchiveFile.getText());
1154                         if (!file.exists() || !file.isFile()) {
1155                                 JOptionPane.showMessageDialog(this, strings
1156                                                 .getProperty("fileNotFound"), "ERROR",
1157                                                 JOptionPane.ERROR_MESSAGE);
1158                                 return null;
1159                         }
1160                         importArchive = file.toURI();
1161
1162                 } else if (radioDirectory.isSelected()) {
1163                         // ディレクトリによるインポート
1164                         File file = new File(txtDirectory.getText());
1165                         if ( !file.exists() || !file.isDirectory()) {
1166                                 JOptionPane.showMessageDialog(this, strings
1167                                                 .getProperty("directoryNotFound"), "ERROR",
1168                                                 JOptionPane.ERROR_MESSAGE);
1169                                 return null;
1170                         }
1171                         importArchive = file.toURI();
1172
1173                 } else if (radioURL.isSelected()) {
1174                         // URLからダウンロード
1175                         File tmpFile = loadTemporaryFromURL(txtURL.getText());
1176                         if (tmpFile == null) {
1177                                 // エラーメッセージは表示済みのため、単にnullでnextを拒否する
1178                                 return null;
1179                         }
1180                         importArchive = tmpFile.toURI();
1181
1182                 } else {
1183                         // それ以外はサポートしていない.
1184                         return null;
1185                 }
1186
1187                 try {
1188                         parent.importModel.openImportSource(importArchive, parent.getTargetCharacterData());
1189
1190                         // ワーカースレッドでアーカイブの読み込みを行う.
1191                         Worker<Void> worker = new Worker<Void>() {
1192                                 public Void doWork(ProgressHandle progressHandle) throws IOException {
1193                                         parent.importModel.loadContents(progressHandle);
1194                                         return null;
1195                                 }
1196                         };
1197
1198                         WorkerWithProgessDialog<Void> dlg = new WorkerWithProgessDialog<Void>(parent, worker);
1199                         dlg.startAndWait();
1200
1201                         // 読み込めたら次ページへ
1202                         return ImportTypeSelectPanel.PANEL_NAME;
1203
1204                 } catch (WorkerException ex) {
1205                         ErrorMessageHelper.showErrorDialog(this, ex.getCause());
1206
1207                 } catch (Exception ex) {
1208                         ErrorMessageHelper.showErrorDialog(this, ex);
1209                 }
1210
1211                 return null;
1212         }
1213
1214         /**
1215          * 指定されたURLからテンポラリにダウンロードして、そのファイルを返す。
1216          * URLが適切でない場合、もしくはエラーが発生した場合はnullを返す。
1217          * @param url URL
1218          * @return ダウンロードしたファイル
1219          */
1220         private File loadTemporaryFromURL(final String url) {
1221                 final Properties strings = LocalizedResourcePropertyLoader.getCachedInstance()
1222                                 .getLocalizedProperties(ImportWizardDialog.STRINGS_RESOURCE);
1223                 final AppConfig appConfig = AppConfig.getInstance();
1224                 final DownloadUtils downloader = new DownloadUtils();
1225                 downloader.setImpersonateUserAgent(appConfig.getImpersonateUserAgent());
1226                 downloader.setDeleteDownloadFileOnExit(appConfig.isDeleteDownloadFileOnExit());
1227
1228                 try {
1229                         // ワーカースレッドでアーカイブの読み込みを行う.
1230                         Worker<File> worker = new Worker<File>() {
1231                                 public File doWork(ProgressHandle progressHandle) throws IOException {
1232                                         progressHandle.setIndeterminate(true);
1233                                         progressHandle.setCaption(strings.getProperty("downloading.checkhead"));
1234
1235                                         HeadResponse headResponse = downloader.getHead(url);
1236
1237                                         String contentType = headResponse.getContentType();
1238                                         String ext = headResponse.getDotExtension();
1239                                         CharacterDataFileReaderWriterFactory archiveRdWrFactory =
1240                                                         CharacterDataFileReaderWriterFactory.getInstance();
1241                                         if (contentType == null || !contentType.startsWith("application/") ||
1242                                                         !archiveRdWrFactory.isSupportedFile(ext)) {
1243                                                 // コンテンツタイプが不明、もしくはバイナリではない
1244                                                 // あるいは、ファイル名がzip, jar, cmjのいずれでもない場合
1245                                                 return null;
1246                                         }
1247
1248                                         progressHandle.setCaption(strings.getProperty("downloading.waitForDownload"));
1249                                         return downloader.downloadTemporary(headResponse);
1250                                 }
1251                         };
1252
1253                         WorkerWithProgessDialog<File> dlg = new WorkerWithProgessDialog<File>(parent, worker);
1254                         try {
1255                                 File tempFile = dlg.startAndWait();
1256                                 if (tempFile != null) {
1257                                         return tempFile;
1258                                 }
1259                                 JOptionPane.showMessageDialog(this, strings.getProperty("downloading.invalidFileType"),
1260                                                 "ERROR", JOptionPane.ERROR_MESSAGE);
1261                                 return null;
1262
1263                         } catch (WorkerException ex) {
1264                                 Throwable iex = ex.getCause();
1265                                 throw (iex == null) ? ex : (Exception) iex;
1266                         }
1267
1268                 } catch (FileNotFoundException ex) {
1269                         JOptionPane.showMessageDialog(this, strings.getProperty("downloading.notFound"),
1270                                         "ERROR", JOptionPane.ERROR_MESSAGE);
1271
1272                 } catch (Exception ex) {
1273                         ErrorMessageHelper.showErrorDialog(this, ex);
1274                 }
1275                 return null;
1276         }
1277 }
1278
1279 class URLTableRow {
1280
1281         private String downloadURL;
1282
1283         private String author;
1284
1285         public String getAuthor() {
1286                 return author;
1287         }
1288
1289         public String getDownloadURL() {
1290                 return downloadURL;
1291         }
1292
1293         public void setAuthor(String author) {
1294                 this.author = author;
1295         }
1296
1297         public void setDownloadURL(String downloadURL) {
1298                 this.downloadURL = downloadURL;
1299         }
1300
1301 }
1302
1303 class URLTableModel extends AbstractTableModelWithComboBoxModel<URLTableRow> {
1304
1305         private static final long serialVersionUID = 7075478118793390224L;
1306
1307         private static final String[] COLUMN_NAMES;
1308
1309         private static final int[] COLUMN_WIDTHS;
1310
1311         static {
1312                 COLUMN_NAMES = new String[] {
1313                                 "作者",
1314                                 "URL",
1315                 };
1316                 COLUMN_WIDTHS = new int[] {
1317                                 100,
1318                                 300,
1319                 };
1320         }
1321
1322         @Override
1323         public String getColumnName(int column) {
1324                 return COLUMN_NAMES[column];
1325         }
1326
1327         public int getColumnCount() {
1328                 return COLUMN_NAMES.length;
1329         }
1330
1331         public Object getValueAt(int rowIndex, int columnIndex) {
1332                 URLTableRow row = getRow(rowIndex);
1333                 switch (columnIndex) {
1334                 case 0:
1335                         return row.getAuthor();
1336                 case 1:
1337                         return row.getDownloadURL();
1338                 }
1339                 return "";
1340         }
1341
1342         @Override
1343         public Class<?> getColumnClass(int columnIndex) {
1344                 return String.class;
1345         }
1346
1347
1348         public void adjustColumnModel(TableColumnModel columnModel, double scale) {
1349                 for (int idx = 0; idx < COLUMN_WIDTHS.length; idx++) {
1350                         columnModel.getColumn(idx).setPreferredWidth((int)(COLUMN_WIDTHS[idx] * scale));
1351                 }
1352         }
1353
1354         public void initModel(CharacterData characterData) {
1355                 clear();
1356                 HashMap<String, String> downloadUrlsMap = new HashMap<String, String>();
1357                 if (characterData != null) {
1358                         for (PartsCategory category : characterData.getPartsCategories()) {
1359                                 for (Map.Entry<PartsIdentifier, PartsSpec> entry : characterData
1360                                                 .getPartsSpecMap(category).entrySet()) {
1361                                         PartsSpec partsSpec = entry.getValue();
1362                                         String author = partsSpec.getAuthor();
1363                                         String downloadURL = partsSpec.getDownloadURL();
1364                                         if (downloadURL != null && downloadURL.trim().length() > 0) {
1365                                                 if (author == null || author.trim().length() == 0) {
1366                                                         author = "";
1367                                                 }
1368                                                 downloadUrlsMap.put(downloadURL, author);
1369                                         }
1370                                 }
1371                         }
1372                 }
1373
1374                 for (Map.Entry<String, String> entry : downloadUrlsMap.entrySet()) {
1375                         String downloadURL = entry.getKey();
1376                         String author = entry.getValue();
1377                         URLTableRow row = new URLTableRow();
1378                         row.setDownloadURL(downloadURL);
1379                         row.setAuthor(author);
1380                         addRow(row);
1381                 }
1382
1383                 Collections.sort(elements, new Comparator<URLTableRow>() {
1384                         public int compare(URLTableRow o1, URLTableRow o2) {
1385                                 int ret = o1.getAuthor().compareTo(o2.getAuthor());
1386                                 if (ret == 0) {
1387                                         ret = o1.getDownloadURL().compareTo(o2.getDownloadURL());
1388                                 }
1389                                 return ret;
1390                         }
1391                 });
1392
1393                 fireTableDataChanged();
1394         }
1395 }
1396
1397
1398 /**
1399  * ファイル選択パネル
1400  *
1401  * @author seraphy
1402  */
1403 class ImportTypeSelectPanel extends ImportWizardCardPanel {
1404
1405         private static final long serialVersionUID = 1L;
1406
1407         public static final String PANEL_NAME = "importTypeSelectPanel";
1408
1409         private ImportWizardDialog parent;
1410
1411         private SamplePicturePanel samplePicturePanel;
1412
1413         private JTextField txtCharacterId;
1414
1415         private JTextField txtCharacterRev;
1416
1417         private JTextField txtCharacterName;
1418
1419         private JTextField txtAuthor;
1420
1421         private JTextArea txtDescription;
1422
1423         private JCheckBox chkPartsImages;
1424
1425         private JCheckBox chkPresets;
1426
1427         private JCheckBox chkSampleImage;
1428
1429         private JCheckBox chkAddDescription;
1430
1431         private String additionalDescription;
1432
1433
1434         /* 以下、選択結果 */
1435
1436         public ImportTypeSelectPanel(ScaleSupport scaleSupport) {
1437                 Properties strings = LocalizedResourcePropertyLoader.getCachedInstance()
1438                                 .getLocalizedProperties(ImportWizardDialog.STRINGS_RESOURCE);
1439
1440                 GridBagLayout basicPanelLayout = new GridBagLayout();
1441                 GridBagConstraints gbc = new GridBagConstraints();
1442                 setLayout(basicPanelLayout);
1443
1444                 JPanel contentsSpecPanel = new JPanel();
1445                 int mergin = (int)(5 * scaleSupport.getManualScaleX());
1446                 contentsSpecPanel.setBorder(BorderFactory.createCompoundBorder(
1447                                 BorderFactory.createEmptyBorder(mergin, mergin, mergin, mergin),
1448                                 BorderFactory.createTitledBorder(strings.getProperty("basic.contentsSpec"))));
1449                 BoxLayout contentsSpecPanelLayout = new BoxLayout(contentsSpecPanel, BoxLayout.PAGE_AXIS);
1450                 contentsSpecPanel.setLayout(contentsSpecPanelLayout);
1451
1452                 chkPartsImages = new JCheckBox(strings.getProperty("basic.chk.partsImages"));
1453                 chkPresets = new JCheckBox(strings.getProperty("basic.chk.presets"));
1454                 chkSampleImage = new JCheckBox(strings.getProperty("basic.chk.samplePicture"));
1455
1456                 contentsSpecPanel.add(chkPartsImages);
1457                 contentsSpecPanel.add(chkPresets);
1458                 contentsSpecPanel.add(chkSampleImage);
1459
1460                 //
1461
1462                 JPanel archiveInfoPanel = new JPanel();
1463                 archiveInfoPanel.setBorder(BorderFactory.createCompoundBorder(BorderFactory
1464                                 .createEmptyBorder(mergin, mergin, mergin, mergin),
1465                                 BorderFactory.createTitledBorder(strings.getProperty("basic.archiveInfo"))));
1466                 Dimension archiveInfoPanelMinSize = scaleSupport.manualScaled(new Dimension(300, 200));
1467                 archiveInfoPanel.setMinimumSize(archiveInfoPanelMinSize);
1468                 archiveInfoPanel.setPreferredSize(archiveInfoPanelMinSize);
1469                 GridBagLayout commentPanelLayout = new GridBagLayout();
1470                 archiveInfoPanel.setLayout(commentPanelLayout);
1471
1472                 gbc.gridx = 0;
1473                 gbc.gridy = 0;
1474                 gbc.gridheight = 1;
1475                 gbc.gridwidth = 2;
1476                 gbc.weightx = 0.;
1477                 gbc.weighty = 0.;
1478                 int gap = (int)(3 * scaleSupport.getManualScaleX());
1479                 gbc.insets = new Insets(gap, gap, gap, gap);
1480                 gbc.anchor = GridBagConstraints.WEST;
1481                 gbc.fill = GridBagConstraints.BOTH;
1482
1483                 gbc.gridx = 0;
1484                 gbc.gridy = 1;
1485                 gbc.gridheight = 1;
1486                 gbc.gridwidth = 1;
1487                 gbc.weightx = 0.;
1488                 gbc.weighty = 0.;
1489                 archiveInfoPanel.add(new JLabel(strings.getProperty("basic.profileId"), JLabel.RIGHT), gbc);
1490
1491                 gbc.gridx = 1;
1492                 gbc.gridy = 1;
1493                 gbc.gridheight = 1;
1494                 gbc.gridwidth = 1;
1495                 gbc.weightx = 0.;
1496                 gbc.weighty = 0.;
1497                 txtCharacterId = new JTextField();
1498                 txtCharacterId.setEditable(false); // 読み取り専用
1499                 txtCharacterId.setEnabled(false);
1500                 archiveInfoPanel.add(txtCharacterId, gbc);
1501
1502                 gbc.gridx = 0;
1503                 gbc.gridy = 2;
1504                 gbc.gridheight = 1;
1505                 gbc.gridwidth = 1;
1506                 gbc.weightx = 0.;
1507                 gbc.weighty = 0.;
1508                 archiveInfoPanel.add(new JLabel(strings.getProperty("basic.profileRev"), JLabel.RIGHT), gbc);
1509
1510                 gbc.gridx = 1;
1511                 gbc.gridy = 2;
1512                 gbc.gridheight = 1;
1513                 gbc.gridwidth = 1;
1514                 gbc.weightx = 0.;
1515                 gbc.weighty = 0.;
1516                 txtCharacterRev = new JTextField();
1517                 txtCharacterRev.setEditable(false); // 読み取り専用
1518                 txtCharacterRev.setEnabled(false);
1519                 archiveInfoPanel.add(txtCharacterRev, gbc);
1520
1521                 gbc.gridx = 0;
1522                 gbc.gridy = 3;
1523                 gbc.gridheight = 1;
1524                 gbc.gridwidth = 1;
1525                 gbc.weightx = 0.;
1526                 gbc.weighty = 0.;
1527                 archiveInfoPanel.add(new JLabel(strings.getProperty("basic.profileName"), JLabel.RIGHT), gbc);
1528
1529                 gbc.gridx = 1;
1530                 gbc.gridy = 3;
1531                 gbc.gridheight = 1;
1532                 gbc.gridwidth = 1;
1533                 gbc.weightx = 0.;
1534                 gbc.weighty = 0.;
1535                 txtCharacterName = new JTextField();
1536                 archiveInfoPanel.add(txtCharacterName, gbc);
1537
1538                 gbc.gridx = 0;
1539                 gbc.gridy = 4;
1540                 gbc.gridheight = 1;
1541                 gbc.gridwidth = 1;
1542                 gbc.weightx = 0.;
1543                 gbc.weighty = 0.;
1544                 archiveInfoPanel.add(
1545                                 new JLabel(strings.getProperty("author"), JLabel.RIGHT), gbc);
1546
1547                 gbc.gridx = 1;
1548                 gbc.gridy = 4;
1549                 gbc.gridwidth = 1;
1550                 gbc.weightx = 1.;
1551                 txtAuthor = new JTextField();
1552                 archiveInfoPanel.add(txtAuthor, gbc);
1553
1554                 gbc.gridx = 0;
1555                 gbc.gridy = 5;
1556                 gbc.gridwidth = 1;
1557                 gbc.gridheight = 1;
1558                 gbc.weightx = 0.;
1559                 archiveInfoPanel.add(new JLabel(strings.getProperty("description"),
1560                                 JLabel.RIGHT), gbc);
1561
1562                 gbc.gridx = 1;
1563                 gbc.gridy = 5;
1564                 gbc.gridwidth = 1;
1565                 gbc.gridheight = 5;
1566                 gbc.weighty = 1.;
1567                 gbc.weightx = 1.;
1568                 txtDescription = new JTextArea(); // 説明は更新可能にしておく。
1569                 archiveInfoPanel.add(new JScrollPane(txtDescription), gbc);
1570
1571                 gbc.gridx = 0;
1572                 gbc.gridy = 10;
1573                 gbc.gridheight = 1;
1574                 gbc.gridwidth = 2;
1575                 gbc.weightx = 0.;
1576                 gbc.weighty = 0.;
1577                 gbc.weighty = 0.;
1578                 gbc.weightx = 0.;
1579                 chkAddDescription = new JCheckBox(strings.getProperty("appendDescription"));
1580                 archiveInfoPanel.add(chkAddDescription, gbc);
1581
1582                 // /
1583
1584                 samplePicturePanel = new SamplePicturePanel();
1585                 JScrollPane samplePicturePanelSP = new JScrollPane(samplePicturePanel);
1586                 samplePicturePanelSP.setBorder(null);
1587                 JPanel samplePictureTitledPanel = new JPanel(new BorderLayout());
1588                 samplePictureTitledPanel.add(samplePicturePanelSP, BorderLayout.CENTER);
1589                 samplePictureTitledPanel.setBorder(BorderFactory.createCompoundBorder(
1590                                 BorderFactory.createEmptyBorder(mergin, mergin, mergin, mergin),
1591                                 BorderFactory.createTitledBorder(strings.getProperty("basic.sampleImage"))));
1592
1593                 // /
1594
1595                 gbc.gridx = 0;
1596                 gbc.gridy = 0;
1597                 gbc.gridheight = 1;
1598                 gbc.gridwidth = 2;
1599                 gbc.weightx = 1.;
1600                 gbc.weighty = 0.;
1601                 gbc.anchor = GridBagConstraints.WEST;
1602                 gbc.fill = GridBagConstraints.BOTH;
1603                 add(contentsSpecPanel, gbc);
1604
1605                 gbc.gridx = 0;
1606                 gbc.gridy = 1;
1607                 gbc.gridheight = 1;
1608                 gbc.gridwidth = 1;
1609                 gbc.weightx = 0.;
1610                 gbc.weighty = 1.;
1611                 gbc.anchor = GridBagConstraints.WEST;
1612                 gbc.fill = GridBagConstraints.BOTH;
1613                 add(archiveInfoPanel, gbc);
1614
1615                 gbc.gridx = 1;
1616                 gbc.gridy = 1;
1617                 gbc.gridheight = 1;
1618                 gbc.gridwidth = 1;
1619                 gbc.weightx = 1;
1620                 gbc.weighty = 1.;
1621                 gbc.anchor = GridBagConstraints.WEST;
1622                 gbc.fill = GridBagConstraints.BOTH;
1623                 add(samplePictureTitledPanel, gbc);
1624
1625                 // アクションリスナ
1626
1627                 ActionListener modListener = new ActionListener() {
1628                         public void actionPerformed(ActionEvent e) {
1629                                 fireChangeEvent();
1630                         }
1631                 };
1632
1633                 chkPartsImages.addActionListener(modListener);
1634                 chkPresets.addActionListener(modListener);
1635                 chkSampleImage.addActionListener(modListener);
1636                 chkAddDescription.addActionListener(modListener);
1637         }
1638
1639         @Override
1640         public void onActive(ImportWizardDialog parent, ImportWizardCardPanel previousPanel) {
1641                 this.parent = parent;
1642
1643                 if (previousPanel == parent.importPartsSelectPanel) {
1644                         return;
1645                 }
1646
1647                 Properties strings = LocalizedResourcePropertyLoader.getCachedInstance()
1648                                 .getLocalizedProperties(ImportWizardDialog.STRINGS_RESOURCE);
1649
1650                 // 呼び出しもと情報
1651                 CharacterData current = parent.getTargetCharacterData();
1652
1653                 // キャラクター定義情報
1654                 CharacterData cd = parent.importModel.getCharacterData();
1655                 String readme;
1656
1657                 // 開いているか選択しているプロファイルが有効であれば更新可能
1658                 final boolean updatable = (current != null && current.isValid());
1659
1660                 // 新規の場合でインポートもとが有効なキャラクターセットであれば作成可能
1661                 final boolean creatable = (current == null && cd != null && cd.isValid());
1662
1663                 // 新規作成の場合はキャラクター定義名と作者名を更新可能とする
1664                 txtCharacterName.setEnabled(current == null);
1665                 txtCharacterName.setEditable(current == null);
1666                 txtAuthor.setEditable(current == null);
1667                 txtAuthor.setEnabled(current == null);
1668
1669                 // ID、REVが一致するか?
1670                 boolean matchID = false;
1671                 boolean matchREV = false;
1672
1673                 if (cd != null && cd.isValid()) {
1674                         txtCharacterId.setText(cd.getId());
1675                         txtCharacterRev.setText(cd.getRev());
1676                         txtCharacterName.setText(cd.getName());
1677
1678                         if (current != null) {
1679                                 // 既存のプロファイルを選択していてインポート結果のキャラクター定義がある場合はID, REVを比較する.
1680                                 matchID = current.getId() == null ? cd.getId() == null : current.getId().equals(cd.getId());
1681                                 matchREV = current.getRev() == null ? cd.getRev() == null : current.getRev().equals(cd.getRev());
1682                         } else {
1683                                 // 既存のプロファイルが存在しない場合は、ID,REVの比較は成功とみなす
1684                                 matchID = true;
1685                                 matchREV = true;
1686                         }
1687
1688                         AppConfig appConfig = AppConfig.getInstance();
1689                         Color invalidBgColor = appConfig.getInvalidBgColor();
1690
1691                         txtCharacterId.setBackground(matchID ? getBackground() : invalidBgColor);
1692                         txtCharacterRev.setBackground(matchREV ? getBackground() : invalidBgColor);
1693
1694                         txtAuthor.setText(cd.getAuthor());
1695                         readme = cd.getDescription();
1696
1697                 } else {
1698                         // ID, REV等は存在しないので空にする
1699                         txtCharacterId.setText("");
1700                         txtCharacterRev.setText("");
1701                         txtCharacterName.setText("");
1702                         txtAuthor.setText("");
1703
1704                         // readmeで代用
1705                         readme = parent.importModel.getReadme();
1706                 }
1707
1708                 // 説明を追記する.
1709                 boolean existsReadme = (readme != null && readme.trim().length() > 0);
1710                 additionalDescription = existsReadme ? readme : "";
1711                 txtDescription.setText(additionalDescription);
1712                 chkAddDescription.setEnabled((updatable || creatable) && existsReadme);
1713                 chkAddDescription.setSelected((updatable || creatable) && existsReadme);
1714
1715                 // プリセットまたはお気に入りが存在するか?
1716                 boolean hasPresetOrFavorites = (cd == null) ? false : !cd.getPartsSets().isEmpty();
1717                 chkPresets.setEnabled(hasPresetOrFavorites);
1718                 chkPresets.setSelected(hasPresetOrFavorites);
1719
1720                 // パーツイメージ
1721                 Collection<PartsImageContent> partsImageContentsMap = parent.importModel.getPartsImageContents();
1722
1723                 // パーツが存在するか?
1724                 boolean hasParts = !partsImageContentsMap.isEmpty();
1725                 chkPartsImages.setEnabled(hasParts);
1726                 chkPartsImages.setSelected(hasParts);
1727
1728                 // サンプルピクチャ
1729                 BufferedImage samplePicture = parent.importModel.getSamplePicture();
1730                 if (samplePicture != null && (updatable || creatable)) {
1731                         // サンプルピクチャが存在し、インポートか新規作成が可能であれば有効にする.
1732                         samplePicturePanel.setSamplePicture(samplePicture);
1733                         chkSampleImage.setEnabled(true);
1734                         chkSampleImage.setSelected(current == null); // 新規作成の場合のみデフォルトでON
1735
1736                 } else {
1737                         samplePicturePanel.setSamplePicture(samplePicture);
1738                         chkSampleImage.setEnabled(false);
1739                         chkSampleImage.setSelected(false);
1740                 }
1741
1742                 // パーツまたはお気に入り・プリセットが存在する場合、
1743                 // および、新規の場合はキャラクター定義が存在する場合はコンテンツ有り
1744                 boolean hasContents = hasParts || hasPresetOrFavorites
1745                         || (current == null && cd != null && cd.isValid());
1746
1747                 if (!hasContents) {
1748                         JOptionPane.showMessageDialog(this, strings.getProperty("noContents"));
1749
1750                 } else if (cd == null) {
1751                         JOptionPane.showMessageDialog(this, strings.getProperty("notFormalArchive"));
1752
1753                 } else if (!matchID) {
1754                         String fmt = strings.getProperty("unmatchedProfileId");
1755                         String msg = MessageFormat.format(fmt,
1756                                         cd.getId() == null ? "" : cd.getId());
1757                         JOptionPane.showMessageDialog(this, msg);
1758
1759                 } else if (!matchREV) {
1760                         String fmt = strings.getProperty("unmatchedProfileRev");
1761                         String msg = MessageFormat.format(fmt, cd.getRev() == null
1762                                         ? ""
1763                                         : cd.getRev());
1764                         JOptionPane.showMessageDialog(this, msg);
1765                 }
1766         }
1767
1768         public boolean isImportPreset() {
1769                 return chkPresets.isSelected();
1770         }
1771
1772         public boolean isImportPartsImages() {
1773                 return chkPartsImages.isSelected();
1774         }
1775
1776         public boolean isImportSampleImage() {
1777                 return chkSampleImage.isSelected();
1778         }
1779
1780         public boolean isAddDescription() {
1781                 return chkAddDescription.isSelected();
1782         }
1783
1784         /**
1785          * 説明として追加するドキュメント.<Br>
1786          * これはユーザーが編集可能であり、ユーザー編集後の値が取得される.<br>
1787          *
1788          * @return 説明として追加するドキュメント
1789          */
1790         public String getAdditionalDescription() {
1791                 return txtDescription.getText();
1792         }
1793
1794         /**
1795          * キャラクター定義名を取得する.
1796          *
1797          * @return キャラクター定義名
1798          */
1799         public String getCharacterName() {
1800                 return txtCharacterName.getText();
1801         }
1802
1803         /**
1804          * 作者名を取得する.
1805          *
1806          * @return 作者名
1807          */
1808         public String getAuthor() {
1809                 return txtAuthor.getText();
1810         }
1811
1812         @Override
1813         public boolean isReadyPrevious() {
1814                 return true;
1815         }
1816
1817         @Override
1818         public String doPrevious() {
1819                 return ImportFileSelectPanel.PANEL_NAME;
1820         }
1821
1822         @Override
1823         public boolean isReadyNext() {
1824                 if (isImportPartsImages() || isImportPreset()) {
1825                         // パーツイメージの選択もしくはパーツセットの選択を指定している場合は次へ進む
1826                         return true;
1827                 }
1828                 return false;
1829         }
1830
1831         @Override
1832         public boolean isReadyFinish() {
1833                 if (!isImportPartsImages() && !isImportPreset()) {
1834                         if ((parent != null && parent.getTargetCharacterData() == null)
1835                                         || isImportSampleImage()) {
1836                                 // 新規プロファイル作成か、サンプルイメージの更新のみで
1837                                 // イメージもパーツセットもいらなければ、ただちに作成可能.
1838                                 return true;
1839                         }
1840                 }
1841                 return false;
1842         }
1843
1844         @Override
1845         public String doNext() {
1846                 return ImportPartsSelectPanel.PANEL_NAME;
1847         }
1848 }
1849
1850
1851 /**
1852  * パーツ選択パネル
1853  *
1854  * @author seraphy
1855  */
1856 class ImportPartsSelectPanel extends ImportWizardCardPanel {
1857
1858         private static final long serialVersionUID = 1L;
1859
1860         public static final String PANEL_NAME = "importPartsSelectPanel";
1861
1862         private ImportWizardDialog parent;
1863
1864
1865         private ImportPartsTableModel partsTableModel;
1866
1867         private JPanel profileSizePanel;
1868
1869         private JTextField txtProfileHeight;
1870
1871         private int profileWidth;
1872
1873         private int profileHeight;
1874
1875         private JTextField txtProfileWidth;
1876
1877         private JTable partsTable;
1878
1879         private Action actSelectAll;
1880
1881         private Action actDeselectAll;
1882
1883         private Action actSort;
1884
1885         private Action actSortByTimestamp;
1886
1887
1888         public ImportPartsSelectPanel(ScaleSupport scaleSupport) {
1889                 Properties strings = LocalizedResourcePropertyLoader.getCachedInstance()
1890                                 .getLocalizedProperties(ImportWizardDialog.STRINGS_RESOURCE);
1891
1892                 setLayout(new BorderLayout());
1893
1894                 profileSizePanel = new JPanel();
1895                 GridBagLayout profileSizePanelLayout = new GridBagLayout();
1896                 profileSizePanel.setLayout(profileSizePanelLayout);
1897                 profileSizePanel.setBorder(BorderFactory.createTitledBorder(strings.getProperty("sizeOfProfile")));
1898
1899                 GridBagConstraints gbc = new GridBagConstraints();
1900
1901                 gbc.gridx = 0;
1902                 gbc.gridy = 0;
1903                 gbc.gridheight = 1;
1904                 gbc.gridwidth = 1;
1905                 gbc.anchor = GridBagConstraints.EAST;
1906                 gbc.fill = GridBagConstraints.BOTH;
1907                 int gap = (int)(3 * scaleSupport.getManualScaleX());
1908                 gbc.insets = new Insets(gap, gap, gap, gap);
1909                 gbc.weightx = 0.;
1910                 gbc.weighty = 0.;
1911                 gbc.ipadx = 0;
1912                 gbc.ipady = 0;
1913                 profileSizePanel.add(new JLabel(strings.getProperty("widthOfProfile"), JLabel.RIGHT), gbc);
1914
1915                 txtProfileWidth = new JTextField();
1916                 txtProfileWidth.setEditable(false);
1917                 gbc.gridx = 1;
1918                 gbc.gridy = 0;
1919                 profileSizePanel.add(txtProfileWidth, gbc);
1920
1921                 gbc.gridx = 2;
1922                 gbc.gridy = 0;
1923                 profileSizePanel.add(new JLabel(strings.getProperty("heightOfProfile"), JLabel.RIGHT), gbc);
1924
1925                 txtProfileHeight = new JTextField();
1926                 txtProfileHeight.setEditable(false);
1927                 gbc.gridx = 3;
1928                 gbc.gridy = 0;
1929                 profileSizePanel.add(txtProfileHeight, gbc);
1930
1931                 gbc.gridx = 4;
1932                 gbc.gridy = 0;
1933                 gbc.weightx = 1.;
1934                 profileSizePanel.add(Box.createHorizontalGlue(), gbc);
1935
1936                 add(profileSizePanel, BorderLayout.NORTH);
1937
1938                 partsTableModel = new ImportPartsTableModel();
1939
1940                 partsTableModel.addTableModelListener(new TableModelListener() {
1941                         public void tableChanged(TableModelEvent e) {
1942                                 fireChangeEvent();
1943                         }
1944                 });
1945
1946                 AppConfig appConfig = AppConfig.getInstance();
1947                 final Color disabledForeground = appConfig.getDisabledCellForgroundColor();
1948
1949                 partsTable = new JTable(partsTableModel) {
1950                         private static final long serialVersionUID = 1L;
1951
1952                         @Override
1953                         public Component prepareRenderer(TableCellRenderer renderer,
1954                                         int row, int column) {
1955                                 Component comp = super.prepareRenderer(renderer, row, column);
1956                                 if (comp instanceof JCheckBox) {
1957                                         // BooleanのデフォルトのレンダラーはJCheckBoxを継承したJTable$BooleanRenderer
1958                                         comp.setEnabled(isCellEditable(row, column) && isEnabled());
1959                                 }
1960
1961                                 // 行モデル取得
1962                                 ImportPartsTableModel model = (ImportPartsTableModel) getModel();
1963                                 ImportPartsModel rowModel = model.getRow(row);
1964
1965                                 Long lastModifiedAtCur = rowModel.getLastModifiedAtCurrentProfile();
1966                                 if (lastModifiedAtCur != null) {
1967                                         // 既存のパーツが存在すれば太字
1968                                         comp.setFont(getFont().deriveFont(Font.BOLD));
1969                                 } else {
1970                                         // 新規パーツであれば通常フォント
1971                                         comp.setFont(getFont());
1972                                 }
1973
1974                                 // 列ごとの警告の判定
1975                                 boolean warnings = false;
1976                                 if (column == ImportPartsTableModel.COLUMN_LASTMODIFIED) {
1977                                         // 既存のほうが日付が新しければワーニング
1978                                         if (lastModifiedAtCur != null &&
1979                                                         rowModel.getLastModified() < lastModifiedAtCur.longValue()) {
1980                                                 warnings = true;
1981                                         }
1982                                 } else if (column == ImportPartsTableModel.COLUMN_ALPHA) {
1983                                         // アルファ情報がない画像は警告
1984                                         if (!rowModel.isAlphaColor()) {
1985                                                 warnings = true;
1986                                         }
1987                                 } else if (column == ImportPartsTableModel.COLUMN_SIZE) {
1988                                         // プロファイルの画像サイズと一致しないか、不揃いな画像であれば警告
1989                                         if (rowModel.isUnmatchedSize()
1990                                                         || profileWidth != rowModel.getWidth()
1991                                                         || profileHeight != rowModel.getHeight()) {
1992                                                 warnings = true;
1993                                         }
1994                                 }
1995
1996                                 // 前景色、ディセーブル時は灰色
1997                                 Color foregroundColor = isCellSelected(row, column) ? getSelectionForeground() : getForeground();
1998                                 comp.setForeground(isEnabled() ? foregroundColor : disabledForeground);
1999
2000                                 // 背景色、警告行は赤色に
2001                                 if (warnings) {
2002                                         AppConfig appConfig = AppConfig.getInstance();
2003                                         Color invalidBgColor = appConfig.getInvalidBgColor();
2004                                         comp.setBackground(invalidBgColor);
2005                                 } else {
2006                                         if (isCellSelected(row, column)) {
2007                                                 comp.setBackground(getSelectionBackground());
2008                                         } else {
2009                                                 comp.setBackground(getBackground());
2010                                         }
2011                                 }
2012
2013                                 return comp;
2014                         }
2015                 };
2016                 partsTable.setShowGrid(true);
2017                 partsTable.setGridColor(appConfig.getGridColor());
2018                 partsTable.putClientProperty("terminateEditOnFocusLost", Boolean.TRUE);
2019                 partsTable.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
2020                 partsTable.setRowSelectionAllowed(true);
2021
2022                 // 行の高さをフォントの高さにする
2023                 partsTable.setRowHeight((int)(partsTable.getFont().getSize() * 1.2));
2024
2025                 // 列幅の調整
2026                 partsTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
2027                 partsTableModel.adjustColumnModel(partsTable.getColumnModel(), scaleSupport.getManualScaleX());
2028
2029                 Action actPartsSetCheck = new AbstractAction(strings.getProperty("parts.popup.check")) {
2030                         private static final long serialVersionUID = 1L;
2031                         public void actionPerformed(ActionEvent e) {
2032                                 int[] selRows = partsTable.getSelectedRows();
2033                                 partsTableModel.setCheck(selRows, true);
2034                         }
2035                 };
2036                 Action actPartsUnsetCheck = new AbstractAction(strings.getProperty("parts.popup.uncheck")) {
2037                         private static final long serialVersionUID = 1L;
2038                         public void actionPerformed(ActionEvent e) {
2039                                 int[] selRows = partsTable.getSelectedRows();
2040                                 partsTableModel.setCheck(selRows, false);
2041                         }
2042                 };
2043
2044                 final JPopupMenu partsTablePopupMenu = new JPopupMenu();
2045                 partsTablePopupMenu.add(actPartsSetCheck);
2046                 partsTablePopupMenu.add(actPartsUnsetCheck);
2047
2048                 partsTable.setComponentPopupMenu(partsTablePopupMenu);
2049
2050                 JScrollPane partsTableSP = new JScrollPane(partsTable);
2051                 partsTableSP.setBorder(null);
2052                 JPanel partsTableTitledPanel = new JPanel(new BorderLayout());
2053                 partsTableTitledPanel.add(partsTableSP, BorderLayout.CENTER);
2054                 partsTableTitledPanel.setBorder(BorderFactory.createTitledBorder(strings
2055                                 .getProperty("parts.title")));
2056
2057                 add(partsTableTitledPanel, BorderLayout.CENTER);
2058
2059                 actSelectAll = new AbstractAction(strings
2060                                 .getProperty("parts.btn.selectAll")) {
2061                         private static final long serialVersionUID = 1L;
2062
2063                         public void actionPerformed(ActionEvent e) {
2064                                 onSelectAll();
2065                         }
2066                 };
2067                 actDeselectAll = new AbstractAction(strings
2068                                 .getProperty("parts.btn.deselectAll")) {
2069                         private static final long serialVersionUID = 1L;
2070
2071                         public void actionPerformed(ActionEvent e) {
2072                                 onDeselectAll();
2073                         }
2074                 };
2075                 actSort = new AbstractAction(strings.getProperty("parts.btn.sort")) {
2076                         private static final long serialVersionUID = 1L;
2077
2078                         public void actionPerformed(ActionEvent e) {
2079                                 onSort();
2080                         }
2081                 };
2082                 actSortByTimestamp = new AbstractAction(strings
2083                                 .getProperty("parts.btn.sortByTimestamp")) {
2084                         private static final long serialVersionUID = 1L;
2085
2086                         public void actionPerformed(ActionEvent e) {
2087                                 onSortByTimestamp();
2088                         }
2089                 };
2090
2091                 JPanel btnPanel = new JPanel();
2092                 GridBagLayout btnPanelLayout = new GridBagLayout();
2093                 btnPanel.setLayout(btnPanelLayout);
2094
2095                 gbc.gridx = 0;
2096                 gbc.gridy = 0;
2097                 gbc.gridheight = 1;
2098                 gbc.gridwidth = 1;
2099                 gbc.anchor = GridBagConstraints.EAST;
2100                 gbc.fill = GridBagConstraints.BOTH;
2101                 gbc.insets = new Insets(gap, gap, gap, gap);
2102                 gbc.ipadx = 0;
2103                 gbc.ipady = 0;
2104                 JButton btnSelectAll = new JButton(actSelectAll);
2105                 btnPanel.add(btnSelectAll, gbc);
2106
2107                 gbc.gridx = 1;
2108                 gbc.gridy = 0;
2109                 JButton btnDeselectAll = new JButton(actDeselectAll);
2110                 btnPanel.add(btnDeselectAll, gbc);
2111
2112                 gbc.gridx = 2;
2113                 gbc.gridy = 0;
2114                 JButton btnSort = new JButton(actSort);
2115                 btnPanel.add(btnSort, gbc);
2116
2117                 gbc.gridx = 3;
2118                 gbc.gridy = 0;
2119                 JButton btnSortByTimestamp = new JButton(actSortByTimestamp);
2120                 btnPanel.add(btnSortByTimestamp, gbc);
2121
2122                 gbc.gridx = 4;
2123                 gbc.gridy = 0;
2124                 gbc.weightx = 1.;
2125                 btnPanel.add(Box.createHorizontalGlue(), gbc);
2126
2127                 add(btnPanel, BorderLayout.SOUTH);
2128         }
2129
2130         @Override
2131         public void onActive(ImportWizardDialog parent, ImportWizardCardPanel previousPanel) {
2132                 this.parent = parent;
2133                 if (previousPanel == parent.importPresetSelectPanel) {
2134                         return;
2135                 }
2136
2137                 // インポート対象のプロファイルサイズ
2138                 CharacterData characterData;
2139                 if (parent.getTargetCharacterData() == null) {
2140                         // 新規インポート
2141                         characterData = parent.importModel.getCharacterData();
2142                 } else {
2143                         // 更新インポート
2144                         characterData = parent.getTargetCharacterData();
2145                 }
2146
2147                 int profileWidth = 0;
2148                 int profileHeight = 0;
2149                 if (characterData != null) {
2150                         Dimension imageSize = characterData.getImageSize();
2151                         if (imageSize != null) {
2152                                 profileWidth = imageSize.width;
2153                                 profileHeight = imageSize.height;
2154                         }
2155                 }
2156                 txtProfileWidth.setText(Integer.toString(profileWidth));
2157                 txtProfileHeight.setText(Integer.toString(profileHeight));
2158                 profileSizePanel.revalidate();
2159                 this.profileHeight = profileHeight;
2160                 this.profileWidth = profileWidth;
2161
2162                 // パーツのインポート指定があれば編集可能に、そうでなければ表示のみ
2163                 // (パーツセットのインポートの確認のため、パーツ一覧は表示できるようにしておく)
2164                 boolean enabled = parent.importTypeSelectPanel.isImportPartsImages();
2165
2166                 partsTable.setEnabled(enabled);
2167                 actDeselectAll.setEnabled(enabled);
2168                 actSelectAll.setEnabled(enabled);
2169                 actSort.setEnabled(enabled);
2170                 actSortByTimestamp.setEnabled(enabled);
2171
2172                 CharacterData currentProfile = parent.getTargetCharacterData();
2173                 Collection<PartsImageContent> partsImageContents = parent.importModel.getPartsImageContents();
2174                 PartsManageData partsManageData = parent.importModel.getPartsManageData();
2175                 partsTableModel.initModel(partsImageContents, partsManageData, currentProfile);
2176
2177                 // プリセットのモデルも更新する.
2178                 Collection<PartsSet> partsSets = null;
2179                 if (parent.importTypeSelectPanel.isImportPreset()) {
2180                         CharacterData cd = parent.importModel.getCharacterData();
2181                         if (cd != null && cd.isValid()) {
2182                                 partsSets = cd.getPartsSets().values();
2183                         }
2184                 }
2185
2186                 final String defaultPartsSetId;
2187                 final CharacterData presetImportTarget = parent.getTargetCharacterData();
2188                 final boolean selectAllPreset;
2189                 if (presetImportTarget == null) { // 新規の場合
2190                         CharacterData cd = parent.importModel.getCharacterData();
2191                         if (cd != null) {
2192                                 defaultPartsSetId = cd.getDefaultPartsSetId();
2193                         } else {
2194                                 defaultPartsSetId = null;
2195                         }
2196                         selectAllPreset = true; // 新規プロファイルの場合はプリセットをインポートする
2197                 } else {
2198                         defaultPartsSetId = null; // 既存の場合はデフォルトのパーツセットであるかは表示する必要ないのでnullにする.
2199                         selectAllPreset = (presetImportTarget.getPartsCount() == 0); // パーツが空の場合はプリセットをインポートする
2200                 }
2201
2202                 parent.importPresetSelectPanel.initModel(partsSets, defaultPartsSetId, presetImportTarget, selectAllPreset);
2203         }
2204
2205         @Override
2206         public boolean isReadyPrevious() {
2207                 return true;
2208         }
2209
2210         @Override
2211         public String doPrevious() {
2212                 this.partsTableModel.clear();
2213                 return ImportTypeSelectPanel.PANEL_NAME;
2214         }
2215
2216         @Override
2217         public boolean isReadyNext() {
2218                 if (this.parent != null) {
2219                         if (this.parent.importTypeSelectPanel.isImportPreset()) {
2220                                 // パーツセットのインポート指定があれば次へ
2221                                 return true;
2222                         }
2223                 }
2224                 return false;
2225         }
2226
2227         @Override
2228         public boolean isReadyFinish() {
2229                 if (this.parent != null) {
2230                         if (this.parent.importTypeSelectPanel.isImportPartsImages()
2231                                         && !this.parent.importTypeSelectPanel.isImportPreset()) {
2232                                 // パーツセットのインポート指定がなければ可
2233                                 return true;
2234                         }
2235                 }
2236                 return false;
2237         }
2238
2239         public String doNext() {
2240                 return ImportPresetSelectPanel.PANEL_NAME;
2241         };
2242
2243
2244         protected void onSelectAll() {
2245                 partsTableModel.selectAll();
2246         }
2247
2248         protected void onDeselectAll() {
2249                 partsTableModel.deselectAll();
2250         }
2251
2252         protected void onSort() {
2253                 partsTableModel.sort();
2254                 if (partsTableModel.getRowCount() > 0) {
2255                         Rectangle rct = partsTable.getCellRect(0, 0, true);
2256                         partsTable.scrollRectToVisible(rct);
2257                 }
2258         }
2259
2260         protected void onSortByTimestamp() {
2261                 partsTableModel.sortByTimestamp();
2262                 if (partsTableModel.getRowCount() > 0) {
2263                         Rectangle rct = partsTable.getCellRect(0, 0, true);
2264                         partsTable.scrollRectToVisible(rct);
2265                 }
2266         }
2267
2268         /**
2269          * 選択されたイメージコンテンツのコレクション.<br>
2270          *
2271          * @return 選択されたイメージコンテンツのコレクション、なければ空
2272          */
2273         public Collection<PartsImageContent> getSelectedPartsImageContents() {
2274                 return partsTableModel.getSelectedPartsImageContents();
2275         }
2276
2277         /**
2278          * すでにプロファイルに登録済みのパーツ識別子、および、これからインポートする予定の選択されたパーツ識別子のコレクション.<br>
2279          *
2280          * @return インポートされた、またはインポートするパーツ識別子のコレクション.なければ空.
2281          */
2282         public Collection<PartsIdentifier> getImportedPartsIdentifiers() {
2283                 HashSet<PartsIdentifier> partsIdentifiers = new HashSet<PartsIdentifier>();
2284                 partsIdentifiers.addAll(partsTableModel.getCurrentProfilePartsIdentifers());
2285                 partsIdentifiers.addAll(partsTableModel.getSelectedPartsIdentifiers());
2286                 return partsIdentifiers;
2287         }
2288
2289         public void selectByPartsIdentifiers(Collection<PartsIdentifier> partsIdentifiers) {
2290                 partsTableModel.selectByPartsIdentifiers(partsIdentifiers);
2291         }
2292
2293 }
2294
2295
2296 /**
2297  * 同じパーツ名をもつイメージのコレクション.<br>
2298  * パーツの各レイヤーの集合を想定する.<br>
2299  *
2300  * @author seraphy
2301  */
2302 class ImportPartsImageSet extends AbstractCollection<PartsImageContent> {
2303
2304         /**
2305          * パーツ名
2306          */
2307         private String partsName;
2308
2309         /**
2310          * 各レイヤー
2311          */
2312         private ArrayList<PartsImageContent> contentSet = new ArrayList<PartsImageContent>();
2313
2314         private Long lastModified;
2315
2316         private int width;
2317
2318         private int height;
2319
2320         private boolean unmatchedSize;
2321
2322         private boolean alphaColor;
2323
2324         private Collection<PartsCategory> partsCategories;
2325
2326         private boolean checked;
2327
2328
2329         public ImportPartsImageSet(String partsName) {
2330                 if (partsName == null || partsName.length() == 0) {
2331                         throw new IllegalArgumentException();
2332                 }
2333                 this.partsName = partsName;
2334         }
2335
2336         public String getPartsName() {
2337                 return partsName;
2338         }
2339
2340         @Override
2341         public int size() {
2342                 return contentSet.size();
2343         }
2344
2345         public Iterator<PartsImageContent> iterator() {
2346                 return contentSet.iterator();
2347         }
2348
2349         @Override
2350         public boolean add(PartsImageContent o) {
2351                 if (o == null) {
2352                         throw new IllegalArgumentException();
2353                 }
2354                 if (!partsName.equals(o.getPartsName())) {
2355                         throw new IllegalArgumentException();
2356                 }
2357
2358                 lastModified = null; // リセットする.
2359
2360                 return contentSet.add(o);
2361         }
2362
2363         public int getWidth() {
2364                 recheck();
2365                 return width;
2366         }
2367
2368         public int getHeight() {
2369                 recheck();
2370                 return height;
2371         }
2372
2373         public boolean isUnmatchedSize() {
2374                 recheck();
2375                 return unmatchedSize;
2376         }
2377
2378         public boolean isAlphaColor() {
2379                 recheck();
2380                 return alphaColor;
2381         }
2382
2383         public long lastModified() {
2384                 recheck();
2385                 return lastModified.longValue();
2386         }
2387
2388         public Collection<PartsCategory> getPartsCategories() {
2389                 recheck();
2390                 return this.partsCategories;
2391         }
2392
2393         protected void recheck() {
2394                 if (lastModified != null) {
2395                         return;
2396                 }
2397
2398                 long lastModified = 0;
2399                 int maxWidth = 0;
2400                 int maxHeight = 0;
2401                 int minWidth = 0;
2402                 int minHeight = 0;
2403                 boolean alphaColor = !this.contentSet.isEmpty();
2404                 HashSet<PartsCategory> partsCategories = new HashSet<PartsCategory>();
2405
2406                 for (PartsImageContent partsImageContent : this.contentSet) {
2407                         PNGFileImageHeader header = partsImageContent.getPngFileImageHeader();
2408
2409                         maxWidth = Math.max(maxWidth, header.getWidth());
2410                         maxHeight = Math.max(maxHeight, header.getHeight());
2411                         minWidth = Math.max(minWidth, header.getWidth());
2412                         minHeight = Math.max(minHeight, header.getHeight());
2413
2414                         if (header.getColorType() != 6 && !header.hasTransparencyInformation()) {
2415                                 // TrueColor + Alpha (6)か、アルファ情報があるもの以外はアルファなしとする.
2416                                 alphaColor = false;
2417                         }
2418
2419                         for (CategoryLayerPair clPair : partsImageContent.getCategoryLayerPairs()) {
2420                                 partsCategories.add(clPair.getPartsCategory());
2421                         }
2422
2423                         long tm = partsImageContent.lastModified();
2424                         lastModified = Math.max(lastModified, tm);
2425                 }
2426
2427                 this.lastModified = Long.valueOf(lastModified);
2428                 this.width = maxWidth;
2429                 this.height = maxHeight;
2430                 this.unmatchedSize = (minWidth != maxWidth) || (minHeight != maxHeight);
2431                 this.alphaColor = alphaColor;
2432                 this.partsCategories = Collections.unmodifiableCollection(partsCategories);
2433         }
2434
2435         public void setChecked(boolean checked) {
2436                 this.checked = checked;
2437         }
2438
2439         public boolean isChecked() {
2440                 return checked;
2441         }
2442 }
2443
2444
2445
2446 class ImportPartsModel {
2447
2448         private PartsIdentifier partsIdentifier;
2449
2450         private PartsAuthorInfo authorInfo;
2451
2452         private PartsManageData.PartsVersionInfo versionInfo;
2453
2454         private PartsSpec partsSpecAtCurrent;
2455
2456         private ImportPartsImageSet imageSet;
2457
2458         private int numOfLink;
2459
2460         private Long lastModifiedAtCurrentProfile;
2461
2462
2463         /**
2464          * 行モデルを構築する
2465          *
2466          * @param partsIdentifier
2467          *            パーツ識別子
2468          * @param authorInfo
2469          *            作者情報(なければnull)
2470          * @param versionInfo
2471          *            バージョン情報(なければnull)
2472          * @param imageSet
2473          *            イメージファイルのセット
2474          * @param numOfLink
2475          *            カテゴリの参照カウント数(複数カテゴリに参照される場合は2以上となる)
2476          */
2477         public ImportPartsModel(PartsIdentifier partsIdentifier,
2478                         PartsAuthorInfo authorInfo,
2479                         PartsManageData.PartsVersionInfo versionInfo,
2480                         PartsSpec partsSpecAtCurrent,
2481                         ImportPartsImageSet imageSet, int numOfLink) {
2482                 if (partsIdentifier == null || imageSet == null) {
2483                         throw new IllegalArgumentException();
2484                 }
2485
2486                 this.partsIdentifier = partsIdentifier;
2487                 this.authorInfo = authorInfo;
2488                 this.versionInfo = versionInfo;
2489                 this.partsSpecAtCurrent = partsSpecAtCurrent;
2490                 this.imageSet = imageSet;
2491                 this.numOfLink = numOfLink;
2492
2493                 if (partsSpecAtCurrent != null) {
2494                         lastModifiedAtCurrentProfile = Long.valueOf(partsSpecAtCurrent
2495                                         .getPartsFiles().lastModified());
2496                 } else {
2497                         lastModifiedAtCurrentProfile = null;
2498                 }
2499         }
2500
2501         public int getNumOfLink() {
2502                 return numOfLink;
2503         }
2504
2505         public PartsIdentifier getPartsIdentifier() {
2506                 return partsIdentifier;
2507         }
2508
2509         public ImportPartsImageSet getImageSet() {
2510                 return imageSet;
2511         }
2512
2513         public String getPartsName() {
2514                 return partsIdentifier.getLocalizedPartsName();
2515         }
2516
2517         public String getAuthor() {
2518                 if (authorInfo != null) {
2519                         return authorInfo.getAuthor();
2520                 }
2521                 return null;
2522         }
2523
2524         public String getAuthorAtCurrent() {
2525                 if (partsSpecAtCurrent != null) {
2526                         PartsAuthorInfo partsAuthorInfo = partsSpecAtCurrent.getAuthorInfo();
2527                         if (partsAuthorInfo != null) {
2528                                 return partsAuthorInfo.getAuthor();
2529                         }
2530                 }
2531                 return null;
2532         }
2533
2534         public double getVersion() {
2535                 if (versionInfo != null) {
2536                         return versionInfo.getVersion();
2537                 }
2538                 return 0;
2539         }
2540
2541         public double getVersionAtCurrent() {
2542                 if (partsSpecAtCurrent != null) {
2543                         return partsSpecAtCurrent.getVersion();
2544                 }
2545                 return 0;
2546         }
2547
2548         public PartsCategory getPartsCategory() {
2549                 return partsIdentifier.getPartsCategory();
2550         }
2551
2552         public void setChecked(boolean checked) {
2553                 imageSet.setChecked(checked);
2554         }
2555
2556         public boolean isChecked() {
2557                 return imageSet.isChecked();
2558         }
2559
2560         public int getWidth() {
2561                 return imageSet.getWidth();
2562         }
2563
2564         public int getHeight() {
2565                 return imageSet.getHeight();
2566         }
2567
2568         public boolean isUnmatchedSize() {
2569                 return imageSet.isUnmatchedSize();
2570         }
2571
2572         public boolean isAlphaColor() {
2573                 return imageSet.isAlphaColor();
2574         }
2575
2576         public long getLastModified() {
2577                 return imageSet.lastModified();
2578         }
2579
2580         public Long getLastModifiedAtCurrentProfile() {
2581                 return lastModifiedAtCurrentProfile;
2582         }
2583 }
2584
2585
2586 class ImportPartsTableModel extends AbstractTableModelWithComboBoxModel<ImportPartsModel> {
2587
2588         private static final long serialVersionUID = 1L;
2589
2590         private static final String[] COLUMN_NAMES;
2591
2592         private static final int[] COLUMN_WIDTHS;
2593
2594         public static final int COLUMN_LASTMODIFIED = 5;
2595
2596         public static final int COLUMN_ALPHA = 4;
2597
2598         public static final int COLUMN_SIZE = 3;
2599
2600         private Set<PartsIdentifier> currentProfilePartsIdentifiers;
2601
2602         static {
2603                 Properties strings = LocalizedResourcePropertyLoader.getCachedInstance()
2604                                 .getLocalizedProperties(ImportWizardDialog.STRINGS_RESOURCE);
2605
2606                 COLUMN_NAMES = new String[] {
2607                                 strings.getProperty("parts.column.check"),
2608                                 strings.getProperty("parts.column.partsname"),
2609                                 strings.getProperty("parts.column.category"),
2610                                 strings.getProperty("parts.column.imagesize"),
2611                                 strings.getProperty("parts.column.alpha"),
2612                                 strings.getProperty("parts.column.lastmodified"),
2613                                 strings.getProperty("parts.column.org-lastmodified"),
2614                                 strings.getProperty("parts.column.author"),
2615                                 strings.getProperty("parts.column.org-author"),
2616                                 strings.getProperty("parts.column.version"),
2617                                 strings.getProperty("parts.column.org-version"),
2618                 };
2619                 COLUMN_WIDTHS = new int[] {
2620                                 Integer.parseInt(strings.getProperty("parts.column.check.size")),
2621                                 Integer.parseInt(strings.getProperty("parts.column.partsname.size")),
2622                                 Integer.parseInt(strings.getProperty("parts.column.category.size")),
2623                                 Integer.parseInt(strings.getProperty("parts.column.imagesize.size")),
2624                                 Integer.parseInt(strings.getProperty("parts.column.alpha.size")),
2625                                 Integer.parseInt(strings.getProperty("parts.column.lastmodified.size")),
2626                                 Integer.parseInt(strings.getProperty("parts.column.org-lastmodified.size")),
2627                                 Integer.parseInt(strings.getProperty("parts.column.author.size")),
2628                                 Integer.parseInt(strings.getProperty("parts.column.org-author.size")),
2629                                 Integer.parseInt(strings.getProperty("parts.column.version.size")),
2630                                 Integer.parseInt(strings.getProperty("parts.column.org-version.size")),
2631                 };
2632         }
2633
2634
2635         /**
2636          * モデルを初期化する.<br>
2637          *
2638          * @param partsImageContents
2639          *            インポートもとアーカイブに含まれる、全パーツイメージコンテンツ
2640          * @param currentProfile
2641          *            インポート先のプロファイル、現在プロファイルが既に持っているパーツを取得するためのもの。
2642          */
2643         public void initModel(Collection<PartsImageContent> partsImageContents, PartsManageData partsManageData, CharacterData currentProfile) {
2644                 clear();
2645                 if (partsImageContents == null || partsManageData == null) {
2646                         return;
2647                 }
2648
2649                 // 現在のプロファイルが所有する全パーツ一覧を構築する.
2650                 // 現在のプロファイルがなければ空.
2651                 HashSet<PartsIdentifier> currentProfilePartsIdentifiers = new HashSet<PartsIdentifier>();
2652                 if (currentProfile != null) {
2653                         for (PartsCategory partsCategory : currentProfile.getPartsCategories()) {
2654                                 currentProfilePartsIdentifiers.addAll(currentProfile.getPartsSpecMap(partsCategory).keySet());
2655                         }
2656                 }
2657                 this.currentProfilePartsIdentifiers = Collections.unmodifiableSet(currentProfilePartsIdentifiers);
2658
2659                 // 同じパーツ名をもつ各レイヤーを集める
2660                 HashMap<String, ImportPartsImageSet> partsImageSets = new HashMap<String, ImportPartsImageSet>();
2661                 for (PartsImageContent content : partsImageContents) {
2662                         String partsName = content.getPartsName();
2663                         ImportPartsImageSet partsImageSet = partsImageSets.get(partsName);
2664                         if (partsImageSet == null) {
2665                                 partsImageSet = new ImportPartsImageSet(partsName);
2666                                 partsImageSets.put(partsName, partsImageSet);
2667                         }
2668                         partsImageSet.add(content);
2669                 }
2670
2671                 // 名前順に並び替える
2672                 ArrayList<String> partsNames = new ArrayList<String>(partsImageSets.keySet());
2673                 Collections.sort(partsNames);
2674
2675                 // 登録する
2676                 for (String partsName : partsNames) {
2677                         ImportPartsImageSet partsImageSet = partsImageSets.get(partsName);
2678                         int numOfLink = partsImageSet.getPartsCategories().size();
2679                         for (PartsCategory partsCategory : partsImageSet.getPartsCategories()) {
2680
2681                                 // パーツ管理情報の索引キー
2682                                 PartsManageData.PartsKey partsKey = new PartsManageData.PartsKey(partsName, partsCategory.getCategoryId());
2683                                 // ローカライズされたパーツ名があれば取得する。なければオリジナルのまま
2684                                 String localizedPartsName = partsManageData.getLocalizedName(partsKey);
2685                                 if (localizedPartsName == null || localizedPartsName.length() == 0) {
2686                                         localizedPartsName = partsName;
2687                                 }
2688                                 // 作者情報・バージョン情報があれば取得する.
2689                                 PartsAuthorInfo partsAuthorInfo = partsManageData.getPartsAuthorInfo(partsKey);
2690                                 PartsManageData.PartsVersionInfo versionInfo = partsManageData.getVersion(partsKey);
2691
2692                                 // パーツ識別子を構築する
2693                                 PartsIdentifier partsIdentifier = new PartsIdentifier(partsCategory, partsName, localizedPartsName);
2694
2695                                 // 現在のプロファイル上のパーツ情報を取得する.(なければnull)
2696                                 PartsSpec partsSpec;
2697                                 if (currentProfile != null) {
2698                                         partsSpec = currentProfile.getPartsSpec(partsIdentifier);
2699                                 } else {
2700                                         partsSpec = null;
2701                                 }
2702
2703                                 // 行モデルを構築する.
2704                                 ImportPartsModel rowModel = new ImportPartsModel(
2705                                                 partsIdentifier, partsAuthorInfo, versionInfo,
2706                                                 partsSpec, partsImageSet, numOfLink);
2707
2708                                 addRow(rowModel);
2709                         }
2710                 }
2711
2712                 // 既存がないか、既存よりも新しい日付であれば自動的にチェックを設定する.
2713                 // もしくはバージョンが上であれば自動的にチェックをつける.
2714                 for (ImportPartsModel rowModel : elements) {
2715
2716                         // 現在のプロファイル上のファイル群の最終更新日
2717                         Long lastModifiedAtCurrent = rowModel.getLastModifiedAtCurrentProfile();
2718                         if (lastModifiedAtCurrent == null) {
2719                                 lastModifiedAtCurrent = Long.valueOf(0);
2720                         }
2721
2722                         // インポートするファイル群の最終更新日
2723                         ImportPartsImageSet partsImageSet = rowModel.getImageSet();
2724
2725                         // 新しければ自動的にチェックをつける.
2726                         if (lastModifiedAtCurrent.longValue() < partsImageSet.lastModified()) {
2727                                 partsImageSet.setChecked(true);
2728                         }
2729
2730                         // バージョンが新しければチェックをつける. (改変版や作者名改名もあるので、作者名が同一であるかは問わない.)
2731                         double versionAtCurrent = rowModel.getVersionAtCurrent();
2732                         double version = rowModel.getVersion();
2733                         if (versionAtCurrent < version) {
2734                                 partsImageSet.setChecked(true);
2735                         }
2736                 }
2737
2738                 // 並び替え
2739                 sort();
2740         }
2741
2742         /**
2743          * 選択されているパーツを構成するファイルのコレクションを返します.<br>
2744          *
2745          * @return パーツイメージコンテンツのコレクション、選択がなければ空
2746          */
2747         public Collection<PartsImageContent> getSelectedPartsImageContents() {
2748                 IdentityHashMap<ImportPartsImageSet, ImportPartsImageSet> partsImageSets
2749                                 = new IdentityHashMap<ImportPartsImageSet, ImportPartsImageSet>();
2750
2751                 for (ImportPartsModel rowModel : elements) {
2752                         ImportPartsImageSet partsImageSet = rowModel.getImageSet();
2753                         if (partsImageSet.isChecked()) {
2754                                 partsImageSets.put(partsImageSet, partsImageSet);
2755                         }
2756                 }
2757
2758                 ArrayList<PartsImageContent> partsImageContents = new ArrayList<PartsImageContent>();
2759                 for (ImportPartsImageSet partsImageSet : partsImageSets.values()) {
2760                         partsImageContents.addAll(partsImageSet);
2761                 }
2762                 return partsImageContents;
2763         }
2764
2765         /**
2766          * 選択されているパーツ識別子のコレクションを返します.<br>
2767          * 返されるコレクションには同一のパーツ識別子が複数存在しないことが保証されます.<br>
2768          * 一つも選択がない場合は空が返されます.<br>
2769          *
2770          * @return パーツ識別子のコレクション.<br>
2771          */
2772         public Collection<PartsIdentifier> getSelectedPartsIdentifiers() {
2773                 HashSet<PartsIdentifier> partsIdentifiers = new HashSet<PartsIdentifier>();
2774                 for (ImportPartsModel rowModel : elements) {
2775                         if (rowModel.isChecked()) {
2776                                 partsIdentifiers.add(rowModel.getPartsIdentifier());
2777                         }
2778                 }
2779                 return partsIdentifiers;
2780         }
2781
2782         /**
2783          * 現在のプロファイルが所有している全パーツの識別子.<br>
2784          * 現在のプロファイルがないか、まったく所有していなければ空.<br>
2785          *
2786          * @return 現在のプロファイルが所有するパーツの識別子のコレクション.(重複しない一意であることが保証される.)
2787          */
2788         public Collection<PartsIdentifier> getCurrentProfilePartsIdentifers() {
2789                 return currentProfilePartsIdentifiers;
2790         }
2791
2792         public int getColumnCount() {
2793                 return COLUMN_NAMES.length;
2794         }
2795
2796         @Override
2797         public String getColumnName(int column) {
2798                 return COLUMN_NAMES[column];
2799         }
2800
2801         public Object getValueAt(int rowIndex, int columnIndex) {
2802                 ImportPartsModel rowModel = getRow(rowIndex);
2803                 switch (columnIndex) {
2804                 case 0:
2805                         return rowModel.isChecked();
2806                 case 1:
2807                         return rowModel.getPartsName();
2808                 case 2:
2809                         return rowModel.getPartsCategory().getLocalizedCategoryName();
2810                 case 3:
2811                         return rowModel.getWidth() + "x" + rowModel.getHeight()
2812                                         + (rowModel.isUnmatchedSize() ? "*" : "");
2813                 case 4:
2814                         return rowModel.isAlphaColor();
2815                 case 5:
2816                         long lastModified = rowModel.getLastModified();
2817                         if (lastModified > 0) {
2818                                 return new Timestamp(lastModified).toString();
2819                         }
2820                         return "";
2821                 case 6:
2822                         Long lastModifiedAtCur = rowModel.getLastModifiedAtCurrentProfile();
2823                         if (lastModifiedAtCur != null && lastModifiedAtCur.longValue() > 0) {
2824                                 return new Timestamp(lastModifiedAtCur.longValue()).toString();
2825                         }
2826                         return "";
2827                 case 7:
2828                         return rowModel.getAuthor();
2829                 case 8:
2830                         return rowModel.getAuthorAtCurrent();
2831                 case 9:
2832                         double version = rowModel.getVersion();
2833                         if (version > 0) {
2834                                 return Double.toString(version);
2835                         }
2836                         return "";
2837                 case 10:
2838                         double versionAtCurrent = rowModel.getVersionAtCurrent();
2839                         if (versionAtCurrent > 0) {
2840                                 return Double.toString(versionAtCurrent);
2841                         }
2842                         return "";
2843                 }
2844                 return "";
2845         }
2846
2847         @Override
2848         public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
2849                 ImportPartsModel rowModel = getRow(rowIndex);
2850
2851                 switch (columnIndex) {
2852                 case 0:
2853                         rowModel.setChecked(((Boolean) aValue).booleanValue());
2854                         break;
2855                 default:
2856                         return;
2857                 }
2858
2859                 if (rowModel.getNumOfLink() > 1) {
2860                         fireTableDataChanged();
2861                 } else {
2862                         fireListUpdated(rowIndex, rowIndex);
2863                 }
2864         }
2865
2866         @Override
2867         public Class<?> getColumnClass(int columnIndex) {
2868                 switch (columnIndex) {
2869                 case 0:
2870                         return Boolean.class;
2871                 case 1:
2872                         return String.class;
2873                 case 2:
2874                         return String.class;
2875                 case 3:
2876                         return String.class;
2877                 case 4:
2878                         return Boolean.class;
2879                 case 5:
2880                         return String.class;
2881                 case 6:
2882                         return String.class;
2883                 case 7:
2884                         return String.class;
2885                 case 8:
2886                         return String.class;
2887                 case 9:
2888                         return String.class;
2889                 case 10:
2890                         return String.class;
2891                 }
2892                 return String.class;
2893         }
2894
2895         @Override
2896         public boolean isCellEditable(int rowIndex, int columnIndex) {
2897                 if (columnIndex == 0) {
2898                         return true;
2899                 }
2900                 return false;
2901         }
2902
2903         public void adjustColumnModel(TableColumnModel columnModel, double scale) {
2904                 int mx = columnModel.getColumnCount();
2905                 for (int idx = 0; idx < mx; idx++) {
2906                         columnModel.getColumn(idx).setPreferredWidth((int)(COLUMN_WIDTHS[idx] * scale));
2907                 }
2908         }
2909
2910         public void selectAll() {
2911                 boolean modified = false;
2912                 for (ImportPartsModel rowModel : elements) {
2913                         if (!rowModel.isChecked()) {
2914                                 rowModel.setChecked(true);
2915                                 modified = true;
2916                         }
2917                 }
2918                 if (modified) {
2919                         fireTableDataChanged();
2920                 }
2921         }
2922
2923         public void deselectAll() {
2924                 boolean modified = false;
2925                 for (ImportPartsModel rowModel : elements) {
2926                         if (rowModel.isChecked()) {
2927                                 rowModel.setChecked(false);
2928                                 modified = true;
2929                         }
2930                 }
2931                 if (modified) {
2932                         fireTableDataChanged();
2933                 }
2934         }
2935
2936         public void sort() {
2937                 Collections.sort(elements, new Comparator<ImportPartsModel> () {
2938                         public int compare(ImportPartsModel o1, ImportPartsModel o2) {
2939                                 int ret = (o1.isChecked() ? 0 : 1) - (o2.isChecked() ? 0 : 1);
2940                                 if (ret == 0) {
2941                                         ret = o1.getPartsIdentifier().compareTo(o2.getPartsIdentifier());
2942                                 }
2943                                 return ret;
2944                         }
2945                 });
2946
2947                 fireTableDataChanged();
2948         }
2949
2950         public void sortByTimestamp() {
2951                 Collections.sort(elements, new Comparator<ImportPartsModel> () {
2952                         public int compare(ImportPartsModel o1, ImportPartsModel o2) {
2953                                 long ret = (o1.isChecked() ? 0 : 1) - (o2.isChecked() ? 0 : 1);
2954                                 if (ret == 0) {
2955                                         Long tm1 = o1.getLastModifiedAtCurrentProfile();
2956                                         Long tm2 = o2.getLastModifiedAtCurrentProfile();
2957
2958                                         long lastModified1 = Math.max(o1.getLastModified(), tm1 == null ? 0 : tm1.longValue());
2959                                         long lastModified2 = Math.max(o2.getLastModified(), tm2 == null ? 0 : tm2.longValue());
2960
2961                                         ret = lastModified1 - lastModified2;
2962                                 }
2963                                 if (ret == 0) {
2964                                         ret = o1.getPartsIdentifier().compareTo(o2.getPartsIdentifier());
2965                                 }
2966
2967                                 return ret == 0 ? 0 : ret > 0 ? 1 : -1;
2968                         }
2969                 });
2970
2971                 fireTableDataChanged();
2972         }
2973
2974         /**
2975          * 指定したパーツ識別子をチェック状態にする.
2976          *
2977          * @param partsIdentifiers
2978          *            パーツ識別子のコレクション、nullの場合は何もしない.
2979          */
2980         public void selectByPartsIdentifiers(Collection<PartsIdentifier> partsIdentifiers) {
2981                 boolean modified = false;
2982                 if (partsIdentifiers != null) {
2983                         for (PartsIdentifier partsIdentifier : partsIdentifiers) {
2984                                 for (ImportPartsModel rowModel : elements) {
2985                                         if (rowModel.getPartsIdentifier().equals(partsIdentifier)) {
2986                                                 if (!rowModel.isChecked()) {
2987                                                         rowModel.setChecked(true);
2988                                                         modified = true;
2989                                                 }
2990                                         }
2991                                 }
2992                         }
2993                 }
2994                 if (modified) {
2995                         fireTableDataChanged();
2996                 }
2997         }
2998
2999         public void setCheck(int[] selRows, boolean checked) {
3000                 if (selRows == null || selRows.length == 0) {
3001                         return;
3002                 }
3003                 Arrays.sort(selRows);
3004                 for (int selRow : selRows) {
3005                         ImportPartsModel rowModel = getRow(selRow);
3006                         rowModel.setChecked(checked);
3007                 }
3008                 fireTableRowsUpdated(selRows[0], selRows[selRows.length - 1]);
3009         }
3010 }
3011
3012
3013
3014 /**
3015  * プリセット選択パネル
3016  *
3017  * @author seraphy
3018  */
3019 class ImportPresetSelectPanel extends ImportWizardCardPanel {
3020
3021         private static final long serialVersionUID = 1L;
3022
3023         public static final String PANEL_NAME = "importPresetSelectPanel";
3024
3025         private ImportPresetTableModel presetTableModel;
3026
3027         private ImportWizardDialog parent;
3028
3029         private JTable presetTable;
3030
3031         private Action actSelectAll;
3032
3033         private Action actDeselectAll;
3034
3035         private Action actSelectUsedParts;
3036
3037         public ImportPresetSelectPanel(ScaleSupport scaleSupport) {
3038                 Properties strings = LocalizedResourcePropertyLoader.getCachedInstance()
3039                                 .getLocalizedProperties(ImportWizardDialog.STRINGS_RESOURCE);
3040
3041                 setBorder(BorderFactory.createTitledBorder(strings.getProperty("preset.title")));
3042
3043                 setLayout(new BorderLayout());
3044
3045                 presetTableModel = new ImportPresetTableModel();
3046
3047                 presetTableModel.addTableModelListener(new TableModelListener() {
3048                         public void tableChanged(TableModelEvent e) {
3049                                 if (e.getType() == TableModelEvent.UPDATE) {
3050                                         fireChangeEvent();
3051                                 }
3052                         }
3053                 });
3054
3055                 AppConfig appConfig = AppConfig.getInstance();
3056                 final Color warningForegroundColor = appConfig.getExportPresetWarningsForegroundColor();
3057                 final Color disabledForeground = appConfig.getDisabledCellForgroundColor();
3058
3059                 presetTable = new JTable(presetTableModel) {
3060                         private static final long serialVersionUID = 1L;
3061
3062                         @Override
3063                         public Component prepareRenderer(TableCellRenderer renderer,
3064                                         int row, int column) {
3065                                 Component comp = super.prepareRenderer(renderer, row, column);
3066
3067                                 if (comp instanceof JCheckBox) {
3068                                         // BooleanのデフォルトのレンダラーはJCheckBoxを継承したJTable$BooleanRenderer
3069                                         comp.setEnabled(isCellEditable(row, column) && isEnabled());
3070                                 }
3071
3072                                 ImportPresetModel presetModel = presetTableModel.getRow(row);
3073
3074                                 // インポート先のプリセットを上書きする場合、もしくはデフォルトのパーツセットの場合は太字にする.
3075                                 if (presetModel.isOverwrite() || presetTableModel.isDefaultPartsSet(row)) {
3076                                         comp.setFont(getFont().deriveFont(Font.BOLD));
3077                                 } else {
3078                                         comp.setFont(getFont());
3079                                 }
3080
3081                                 // インポートするプリセットのパーツが不足している場合、警告色にする.
3082                                 if (!isEnabled()) {
3083                                         comp.setForeground(disabledForeground);
3084
3085                                 } else {
3086                                         if (presetModel.isCheched()
3087                                                         && presetModel.getMissingPartsIdentifiers().size() > 0) {
3088                                                 comp.setForeground(warningForegroundColor);
3089                                         } else {
3090                                                 comp.setForeground(getForeground());
3091                                         }
3092                                 }
3093                                 return comp;
3094                         }
3095                 };
3096                 presetTable.setShowGrid(true);
3097                 presetTable.setGridColor(appConfig.getGridColor());
3098                 presetTable.putClientProperty("terminateEditOnFocusLost", Boolean.TRUE);
3099
3100                 // 行の高さをフォントの高さにする
3101                 presetTable.setRowHeight((int)(presetTable.getFont().getSize() * 1.2));
3102
3103                 actSelectUsedParts = new AbstractAction(strings.getProperty("preset.popup.selectUsedParts")) {
3104                         private static final long serialVersionUID = 1L;
3105
3106                         public void actionPerformed(ActionEvent e) {
3107                                 exportUsedParts();
3108                         }
3109                 };
3110
3111                 final JPopupMenu popupMenu = new JPopupMenu();
3112                 popupMenu.add(actSelectUsedParts);
3113
3114                 presetTable.setComponentPopupMenu(popupMenu);
3115
3116                 // 列幅の調整
3117                 presetTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
3118                 presetTableModel.adjustColumnModel(presetTable.getColumnModel(), scaleSupport.getManualScaleX());
3119
3120                 add(new JScrollPane(presetTable), BorderLayout.CENTER);
3121
3122                 actSelectAll = new AbstractAction(strings.getProperty("parts.btn.selectAll")) {
3123                         private static final long serialVersionUID = 1L;
3124
3125                         public void actionPerformed(ActionEvent e) {
3126                                 onSelectAll();
3127                         }
3128                 };
3129                 actDeselectAll = new AbstractAction(strings.getProperty("parts.btn.deselectAll")) {
3130                         private static final long serialVersionUID = 1L;
3131
3132                         public void actionPerformed(ActionEvent e) {
3133                                 onDeselectAll();
3134                         }
3135                 };
3136                 Action actSort = new AbstractAction(strings
3137                                 .getProperty("parts.btn.sort")) {
3138                         private static final long serialVersionUID = 1L;
3139
3140                         public void actionPerformed(ActionEvent e) {
3141                                 onSort();
3142                         }
3143                 };
3144
3145                 JPanel btnPanel = new JPanel();
3146                 GridBagLayout btnPanelLayout = new GridBagLayout();
3147                 btnPanel.setLayout(btnPanelLayout);
3148
3149                 GridBagConstraints gbc = new GridBagConstraints();
3150
3151                 gbc.gridx = 0;
3152                 gbc.gridy = 0;
3153                 gbc.gridheight = 1;
3154                 gbc.gridwidth = 1;
3155                 gbc.anchor = GridBagConstraints.EAST;
3156                 gbc.fill = GridBagConstraints.BOTH;
3157                 int gap = (int)(3 * scaleSupport.getManualScaleX());
3158                 gbc.insets = new Insets(gap, gap, gap, gap);
3159                 gbc.ipadx = 0;
3160                 gbc.ipady = 0;
3161                 JButton btnSelectAll = new JButton(actSelectAll);
3162                 btnPanel.add(btnSelectAll, gbc);
3163
3164                 gbc.gridx = 1;
3165                 gbc.gridy = 0;
3166                 JButton btnDeselectAll = new JButton(actDeselectAll);
3167                 btnPanel.add(btnDeselectAll, gbc);
3168
3169                 gbc.gridx = 2;
3170                 gbc.gridy = 0;
3171                 JButton btnSort = new JButton(actSort);
3172                 btnPanel.add(btnSort, gbc);
3173
3174                 gbc.gridx = 3;
3175                 gbc.gridy = 0;
3176                 gbc.weightx = 1.;
3177                 btnPanel.add(Box.createHorizontalGlue(), gbc);
3178
3179                 add(btnPanel, BorderLayout.SOUTH);
3180         }
3181
3182         @Override
3183         public void onActive(ImportWizardDialog parent,
3184                         ImportWizardCardPanel previousPanel) {
3185                 this.parent= parent;
3186
3187                 actSelectUsedParts.setEnabled(parent.importTypeSelectPanel.isImportPartsImages());
3188                 checkMissingParts();
3189         }
3190
3191         public void checkMissingParts() {
3192                 Collection<PartsIdentifier> importedPartsIdentifiers = this.parent.importPartsSelectPanel.getImportedPartsIdentifiers();
3193                 presetTableModel.checkMissingParts(importedPartsIdentifiers);
3194         }
3195
3196         protected void onSelectAll() {
3197                 presetTableModel.selectAll();
3198         }
3199
3200         protected void onDeselectAll() {
3201                 presetTableModel.deselectAll();
3202         }
3203
3204         protected void onSort() {
3205                 presetTableModel.sort();
3206                 if (presetTableModel.getRowCount() > 0) {
3207                         Rectangle rct = presetTable.getCellRect(0, 0, true);
3208                         presetTable.scrollRectToVisible(rct);
3209                 }
3210         }
3211
3212         protected void exportUsedParts() {
3213                 ArrayList<PartsIdentifier> requirePartsIdentifiers = new ArrayList<PartsIdentifier>();
3214                 int[] selRows = presetTable.getSelectedRows();
3215                 for (int selRow : selRows) {
3216                         ImportPresetModel presetModel = presetTableModel.getRow(selRow);
3217                         PartsSet partsSet = presetModel.getPartsSet();
3218                         for (List<PartsIdentifier> partsIdentifiers : partsSet.values()) {
3219                                 for (PartsIdentifier partsIdentifier : partsIdentifiers) {
3220                                         requirePartsIdentifiers.add(partsIdentifier);
3221                                 }
3222                         }
3223                 }
3224                 this.parent.importPartsSelectPanel.selectByPartsIdentifiers(requirePartsIdentifiers);
3225                 checkMissingParts();
3226         }
3227
3228         @Override
3229         public boolean isReadyPrevious() {
3230                 return true;
3231         }
3232
3233         @Override
3234         public boolean isReadyNext() {
3235                 return false;
3236         }
3237
3238         @Override
3239         public boolean isReadyFinish() {
3240                 if (this.parent != null) {
3241                         return true;
3242                 }
3243                 return false;
3244         }
3245
3246         @Override
3247         public String doPrevious() {
3248
3249                 return ImportPartsSelectPanel.PANEL_NAME;
3250         }
3251
3252         @Override
3253         public String doNext() {
3254                 return null;
3255         }
3256
3257         public Collection<PartsSet> getSelectedPartsSets() {
3258                 return presetTableModel.getSelectedPartsSets();
3259         }
3260
3261         /**
3262          * デフォルトのパーツセットIDとして使用されることが推奨されるパーツセットIDを取得する.<br>
3263          * 明示的なデフォルトのパーツセットIDがなければ、もしくは、 明示的に指定されているパーツセットIDが選択されているパーツセットの中になければ、
3264          * 選択されているパーツセットの最初のアイテムを返す.<br>
3265          * 選択しているパーツセットが一つもなければnullを返す.<br>
3266          *
3267          * @return デフォルトのパーツセット
3268          */
3269         public String getPrefferedDefaultPartsSetId() {
3270                 String defaultPartsSetId = presetTableModel.getDefaultPartsSetId();
3271                 String firstPartsSetId = null;
3272                 boolean existsDefaultPartsSetId = false;
3273                 for (PartsSet partsSet : getSelectedPartsSets()) {
3274                         if (firstPartsSetId == null) {
3275                                 firstPartsSetId = partsSet.getPartsSetId();
3276                         }
3277                         if (partsSet.getPartsSetId().equals(defaultPartsSetId)) {
3278                                 existsDefaultPartsSetId = true;
3279                         }
3280                 }
3281                 if (!existsDefaultPartsSetId || defaultPartsSetId == null || defaultPartsSetId.length() == 0) {
3282                         defaultPartsSetId = firstPartsSetId;
3283                 }
3284                 return defaultPartsSetId;
3285         }
3286
3287         public void initModel(Collection<PartsSet> partsSets, String defaultPartsSetId, CharacterData presetImportTarget,
3288                         boolean selectAll) {
3289                 presetTableModel.initModel(partsSets, defaultPartsSetId, presetImportTarget);
3290                 if (selectAll) {
3291                         presetTableModel.selectAll();
3292                 }
3293         }
3294 }
3295
3296 class ImportPresetModel {
3297
3298         private boolean cheched;
3299
3300         private PartsSet partsSet;
3301
3302         private boolean overwrite;
3303
3304         private Collection<PartsIdentifier> missingPartsIdentifiers = Collections.emptySet();
3305
3306         public ImportPresetModel(PartsSet partsSet, boolean overwrite, boolean checked) {
3307                 if (partsSet == null) {
3308                         throw new IllegalArgumentException();
3309                 }
3310                 this.partsSet = partsSet;
3311                 this.cheched = checked;
3312                 this.overwrite = overwrite;
3313         }
3314
3315         public boolean isCheched() {
3316                 return cheched;
3317         }
3318
3319         public void setCheched(boolean cheched) {
3320                 this.cheched = cheched;
3321         }
3322
3323         public PartsSet getPartsSet() {
3324                 return partsSet;
3325         }
3326
3327         public String getPartsSetName() {
3328                 return partsSet.getLocalizedName();
3329         }
3330
3331         public void setPartsSetName(String name) {
3332                 if (name == null || name.trim().length() == 0) {
3333                         throw new IllegalArgumentException();
3334                 }
3335                 partsSet.setLocalizedName(name);
3336         }
3337
3338         public Collection<PartsIdentifier> getMissingPartsIdentifiers() {
3339                 return missingPartsIdentifiers;
3340         }
3341
3342         public boolean hasMissingParts() {
3343                 return true;
3344         }
3345
3346         public boolean isOverwrite() {
3347                 return overwrite;
3348         }
3349
3350         public boolean checkMissingParts(Collection<PartsIdentifier> importedPartsIdentifiers) {
3351                 HashSet<PartsIdentifier> missingPartsIdentifiers = new HashSet<PartsIdentifier>();
3352                 for (List<PartsIdentifier> partsIdentifiers : partsSet.values()) {
3353                         for (PartsIdentifier partsIdentifier : partsIdentifiers) {
3354                                 boolean exists = false;
3355                                 if (importedPartsIdentifiers != null && importedPartsIdentifiers.contains(partsIdentifier)) {
3356                                         exists = true;
3357                                 }
3358                                 if (!exists) {
3359                                         missingPartsIdentifiers.add(partsIdentifier);
3360                                 }
3361                         }
3362                 }
3363
3364                 boolean modified = (!missingPartsIdentifiers.equals(this.missingPartsIdentifiers));
3365                 if (modified) {
3366                         this.missingPartsIdentifiers = missingPartsIdentifiers;
3367                 }
3368                 return modified;
3369         }
3370 }
3371
3372 class ImportPresetTableModel extends AbstractTableModelWithComboBoxModel<ImportPresetModel> {
3373
3374         private static final long serialVersionUID = 1L;
3375
3376         private static final String[] COLUMN_NAMES; // = {"選択", "プリセット名",
3377                                                                                                 // "不足するパーツ"};
3378
3379         private static final int[] COLUMN_WIDTHS; // = {50, 100, 200};
3380
3381         static {
3382                 Properties strings = LocalizedResourcePropertyLoader.getCachedInstance()
3383                                 .getLocalizedProperties(ImportWizardDialog.STRINGS_RESOURCE);
3384
3385                 COLUMN_NAMES = new String[] {
3386                                 strings.getProperty("preset.column.check"),
3387                                 strings.getProperty("preset.column.name"),
3388                                 strings.getProperty("preset.column.missings"),
3389                 };
3390
3391                 COLUMN_WIDTHS = new int[] {
3392                                 Integer.parseInt(strings.getProperty("preset.column.check.size")),
3393                                 Integer.parseInt(strings.getProperty("preset.column.name.size")),
3394                                 Integer.parseInt(strings.getProperty("preset.column.missings.size")),
3395                 };
3396         }
3397
3398         private String defaultPartsSetId;
3399
3400         public String getDefaultPartsSetId() {
3401                 return defaultPartsSetId;
3402         }
3403
3404         public void setDefaultPartsSetId(String defaultPartsSetId) {
3405                 this.defaultPartsSetId = defaultPartsSetId;
3406         }
3407
3408         public int getColumnCount() {
3409                 return COLUMN_NAMES.length;
3410         }
3411
3412         @Override
3413         public String getColumnName(int column) {
3414                 return COLUMN_NAMES[column];
3415         }
3416
3417         @Override
3418         public Class<?> getColumnClass(int columnIndex) {
3419                 switch (columnIndex) {
3420                 case 0:
3421                         return Boolean.class;
3422                 case 1:
3423                         return String.class;
3424                 case 2:
3425                         return String.class;
3426                 }
3427                 return String.class;
3428         }
3429
3430         @Override
3431         public boolean isCellEditable(int rowIndex, int columnIndex) {
3432                 if (columnIndex == 0 || columnIndex == 1) {
3433                         return true;
3434                 }
3435                 return false;
3436         }
3437
3438         public Object getValueAt(int rowIndex, int columnIndex) {
3439                 ImportPresetModel rowModel = getRow(rowIndex);
3440                 switch (columnIndex) {
3441                 case 0:
3442                         return rowModel.isCheched();
3443                 case 1:
3444                         return rowModel.getPartsSetName();
3445                 case 2:
3446                         return getMissingPartsIdentifiersString(rowModel);
3447                 }
3448                 return "";
3449         }
3450
3451         private String getMissingPartsIdentifiersString(ImportPresetModel rowModel) {
3452                 StringBuilder buf = new StringBuilder();
3453                 for (PartsIdentifier partsIdentifier : rowModel.getMissingPartsIdentifiers()) {
3454                         if (buf.length() > 0) {
3455                                 buf.append(", ");
3456                         }
3457                         buf.append(partsIdentifier.getLocalizedPartsName());
3458                 }
3459                 return buf.toString();
3460         }
3461
3462         @Override
3463         public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
3464                 ImportPresetModel rowModel = getRow(rowIndex);
3465                 switch (columnIndex) {
3466                 case 0:
3467                         rowModel.setCheched(((Boolean) aValue).booleanValue());
3468                         break;
3469                 case 1:
3470                         String name = (String) aValue;
3471                         name = (name != null) ? name.trim() : "";
3472                         if (name.length() > 0) {
3473                                 rowModel.setPartsSetName(name);
3474                         }
3475                 default:
3476                         return;
3477                 }
3478                 fireTableRowsUpdated(rowIndex, rowIndex);
3479         }
3480
3481         /**
3482          * 指定した行のパーツセットがデフォルトパーツセットであるか?
3483          *
3484          * @param rowIndex
3485          *            行インデックス
3486          * @return デフォルトパーツセットであればtrue、そうでなければfalse
3487          */
3488         public boolean isDefaultPartsSet(int rowIndex) {
3489                 ImportPresetModel rowModel = getRow(rowIndex);
3490                 return rowModel.getPartsSet().getPartsSetId().equals(defaultPartsSetId);
3491         }
3492
3493         public void adjustColumnModel(TableColumnModel columnModel, double scale) {
3494                 int mx = columnModel.getColumnCount();
3495                 for (int idx = 0; idx < mx; idx++) {
3496                         columnModel.getColumn(idx).setPreferredWidth((int)(COLUMN_WIDTHS[idx] * scale));
3497                 }
3498         }
3499
3500         /**
3501          * パーツセットリストを構築する.<br>
3502          *
3503          * @param partsSets
3504          *            登録するパーツセット
3505          * @param defaultPartsSetId
3506          *            デフォルトのパーツセットID、なければnull
3507          * @param presetImportTarget
3508          *            インポート先、新規の場合はnull (上書き判定のため)
3509          */
3510         public void initModel(Collection<PartsSet> partsSets, String defaultPartsSetId, CharacterData presetImportTarget) {
3511                 clear();
3512                 if (partsSets == null) {
3513                         return;
3514                 }
3515
3516                 // インポート先の既存のパーツセット
3517                 Map<String, PartsSet> currentProfilesPartsSet;
3518                 if (presetImportTarget != null) {
3519                         currentProfilesPartsSet = presetImportTarget.getPartsSets();
3520                 } else {
3521                         // 新規の場合は既存パーツセットは空.
3522                         currentProfilesPartsSet = Collections.emptyMap();
3523                 }
3524
3525                 // インポートもとのパーツセットをテープルモデルに登録する.
3526                 for (PartsSet partsSet : partsSets) {
3527                         String partsSetId = partsSet.getPartsSetId();
3528                         if (partsSetId == null || partsSetId.length() == 0) {
3529                                 continue;
3530                         }
3531                         PartsSet compatiblePartsSet;
3532                         if (presetImportTarget != null) {
3533                                 // 既存のキャラクター定義へのインポート時は、パーツセットのカテゴリを合わせる.
3534                                 // 一つもカテゴリが合わない場合は空のパーツセットになる.
3535                                 compatiblePartsSet = partsSet.createCompatible(presetImportTarget);
3536                         } else {
3537                                 compatiblePartsSet = partsSet; // 新規の場合はフィッティングの必要なし.
3538                         }
3539                         if (!compatiblePartsSet.isEmpty()) {
3540                                 // 空のパーツセットは登録対象にしない.
3541                                 boolean overwrite = currentProfilesPartsSet.containsKey(partsSetId);
3542                                 boolean checked = (presetImportTarget == null); // 新規の場合は既定で選択状態とする.
3543                                 ImportPresetModel rowModel = new ImportPresetModel(partsSet, overwrite, checked);
3544                                 addRow(rowModel);
3545                         }
3546                 }
3547
3548                 // デフォルトのパーツセットIDを設定、存在しない場合はnull
3549                 this.defaultPartsSetId = defaultPartsSetId;
3550
3551                 sort();
3552         }
3553
3554         public Collection<PartsSet> getSelectedPartsSets() {
3555                 ArrayList<PartsSet> partsSets = new ArrayList<PartsSet>();
3556                 for (ImportPresetModel rowModel : elements) {
3557                         if (rowModel.isCheched()) {
3558                                 partsSets.add(rowModel.getPartsSet());
3559                         }
3560                 }
3561                 return partsSets;
3562         }
3563
3564         public void selectAll() {
3565                 boolean modified = false;
3566                 for (ImportPresetModel rowModel : elements) {
3567                         if (!rowModel.isCheched()) {
3568                                 rowModel.setCheched(true);
3569                                 modified = true;
3570                         }
3571                 }
3572                 if (modified) {
3573                         fireTableDataChanged();
3574                 }
3575         }
3576
3577         public void deselectAll() {
3578                 boolean modified = false;
3579                 for (ImportPresetModel rowModel : elements) {
3580                         if (rowModel.isCheched()) {
3581                                 rowModel.setCheched(false);
3582                                 modified = true;
3583                         }
3584                 }
3585                 if (modified) {
3586                         fireTableDataChanged();
3587                 }
3588         }
3589
3590         public void sort() {
3591                 Collections.sort(elements, new Comparator<ImportPresetModel>() {
3592                         public int compare(ImportPresetModel o1, ImportPresetModel o2) {
3593                                 int ret = (o1.isCheched() ? 0 : 1) - (o2.isCheched() ? 0 : 1);
3594                                 if (ret == 0) {
3595                                         ret = o1.getPartsSetName().compareTo(o2.getPartsSetName());
3596                                 }
3597                                 return ret;
3598                         }
3599                 });
3600                 fireTableDataChanged();
3601         }
3602
3603         public void checkMissingParts(Collection<PartsIdentifier> importedPartsIdentifiers) {
3604                 boolean changed = false;
3605
3606                 for (ImportPresetModel rowModel : elements) {
3607                         if (rowModel.checkMissingParts(importedPartsIdentifiers)) {
3608                                 changed = true;
3609                         }
3610                 }
3611
3612                 if (changed) {
3613                         fireTableDataChanged();
3614                 }
3615         }
3616 }
3617