• R/O
  • HTTP
  • SSH
  • HTTPS

CharacterManaJ: Commit

キャラクターなんとかJの本体


Commit MetaInfo

Revisionc2f484e8955f938c47c7c23e72f16ccbe494563f (tree)
Time2019-02-06 02:02:02
Authorseraphy <seraphy@user...>
Commiterseraphy

Log Message

プロファイル作成時にデフォルトのキャラクターデータのダウンロードを問い合わせる

および、HiDPI対応、リソース文字列漏れ、リファクタなど。

Change Summary

Incremental Difference

--- a/src/main/java/charactermanaj/model/AppConfig.java
+++ b/src/main/java/charactermanaj/model/AppConfig.java
@@ -1559,4 +1559,37 @@ public final class AppConfig {
15591559 propChangeSupport.firePropertyChange(USE_RLE_COMPRESSION_FOR_PSD, old, useRLECompressionForPSD);
15601560 }
15611561 }
1562+
1563+ private String impersonateUserAgent = "curl/7.58.0";
1564+
1565+ private static final String IMPERSONATE_USER_AGENT = "impersonateUserAgent";
1566+
1567+ public String getImpersonateUserAgent() {
1568+ return impersonateUserAgent;
1569+ }
1570+
1571+ public void setImpersonateUserAgent(String impersonateUserAgent) {
1572+ String old = this.impersonateUserAgent;
1573+ this.impersonateUserAgent = impersonateUserAgent;
1574+ if (old == null ? impersonateUserAgent != null : !old.equals(impersonateUserAgent)) {
1575+ this.impersonateUserAgent = impersonateUserAgent;
1576+ propChangeSupport.firePropertyChange(IMPERSONATE_USER_AGENT, old, impersonateUserAgent);
1577+ }
1578+ }
1579+
1580+ private boolean deleteDownloadFileOnExit = true;
1581+
1582+ private static final String DELETE_DOWNLOAD_FILE_ON_EXIT = "deleteDownloadFileOnExit";
1583+
1584+ public boolean isDeleteDownloadFileOnExit() {
1585+ return deleteDownloadFileOnExit;
1586+ }
1587+
1588+ public void setDeleteDownloadFileOnExit(boolean deleteDownloadFileOnExit) {
1589+ boolean old = this.deleteDownloadFileOnExit;
1590+ if (old != deleteDownloadFileOnExit) {
1591+ this.deleteDownloadFileOnExit = deleteDownloadFileOnExit;
1592+ propChangeSupport.firePropertyChange(DELETE_DOWNLOAD_FILE_ON_EXIT, old, deleteDownloadFileOnExit);
1593+ }
1594+ }
15621595 }
--- a/src/main/java/charactermanaj/model/CharacterData.java
+++ b/src/main/java/charactermanaj/model/CharacterData.java
@@ -770,6 +770,21 @@ public class CharacterData implements PartsSpecResolver {
770770 }
771771
772772 /**
773+ * 全カテゴリー中のパーツデータの個数を取得する。
774+ * @return パーツの個数
775+ */
776+ public int getPartsCount() {
777+ int cnt = 0;
778+ for (PartsCategory category : partsCategories.asList()) {
779+ Map<PartsIdentifier, PartsSpec> partsMap = images.get(category);
780+ if (partsMap != null) {
781+ cnt += partsMap.size();
782+ }
783+ }
784+ return cnt;
785+ }
786+
787+ /**
773788 * パーツデータをリロードする.<br>
774789 * ロード時に使用したローダーを使ってパーツを再ロードします.<br>
775790 * まだ一度もロードしていない場合はIllegalStateException例外が発生します.<br>
--- a/src/main/java/charactermanaj/model/IndependentWorkingSet.java
+++ b/src/main/java/charactermanaj/model/IndependentWorkingSet.java
@@ -80,6 +80,11 @@ public class IndependentWorkingSet {
8080 */
8181 private Rectangle windowRect;
8282
83+ /**
84+ * ダウンロード不要フラグ
85+ */
86+ private boolean noNeedDataDownload;
87+
8388
8489 public void setCharacterDocBase(URI characterDocBase) {
8590 this.characterDocBase = characterDocBase;
@@ -178,6 +183,14 @@ public class IndependentWorkingSet {
178183 this.lastUsePresetParts = lastUsePresetParts;
179184 }
180185
186+ public boolean isNoNeedDataDownload() {
187+ return noNeedDataDownload;
188+ }
189+
190+ public void setNoNeedDataDownload(boolean noNeedDataDownload) {
191+ this.noNeedDataDownload = noNeedDataDownload;
192+ }
193+
181194 /**
182195 * キャラクターデータを指定して、指定されたキャラクターデータ上のインスタンスと関連づけられた
183196 * カテゴリおよびパーツ名などのインスタンスで構成されるパーツ識別名とカラー情報を、 引数で指定したマップに出力する.
@@ -229,6 +242,7 @@ public class IndependentWorkingSet {
229242 buf.append(", currentPartsSet=").append(currentPartsSet);
230243 buf.append(", lastUsedSaveDir=").append(lastUsedSaveDir);
231244 buf.append(", lastUsedExportDir=").append(lastUsedExportDir);
245+ buf.append(", noNeedDataDownload=").append(noNeedDataDownload);
232246 buf.append(")");
233247 return buf.toString();
234248 }
--- a/src/main/java/charactermanaj/model/WorkingSet.java
+++ b/src/main/java/charactermanaj/model/WorkingSet.java
@@ -58,6 +58,11 @@ public class WorkingSet {
5858 */
5959 private Rectangle windowRect;
6060
61+ /**
62+ * データのダウンロードが不要である
63+ */
64+ private boolean noNeedDataDownload;
65+
6166 public void setCharacterDataRev(String characterDataRev) {
6267 this.characterDataRev = characterDataRev;
6368 }
@@ -200,6 +205,14 @@ public class WorkingSet {
200205 this.windowRect = windowRect;
201206 }
202207
208+ public boolean isNoNeedDataDownload() {
209+ return noNeedDataDownload;
210+ }
211+
212+ public void setNoNeedDataDownload(boolean noNeedDataDownload) {
213+ this.noNeedDataDownload = noNeedDataDownload;
214+ }
215+
203216 @Override
204217 public String toString() {
205218 return "docBase:" + characterDocBase + "/rev:" + characterDataRev;
--- a/src/main/java/charactermanaj/model/io/CharacterDataFileReaderWriterFactory.java
+++ b/src/main/java/charactermanaj/model/io/CharacterDataFileReaderWriterFactory.java
@@ -12,11 +12,11 @@ public class CharacterDataFileReaderWriterFactory {
1212 private CharacterDataFileReaderWriterFactory() {
1313 super();
1414 }
15-
15+
1616 public static CharacterDataFileReaderWriterFactory getInstance() {
1717 return singleton;
1818 }
19-
19+
2020 /**
2121 * ファイルの拡張子に応じてzip/cmj形式でのライターを構築して帰します.<br>
2222 * 拡張子がjarとcmjは同じ意味で、ともにjarファイル形式となります.<br>
@@ -29,7 +29,7 @@ public class CharacterDataFileReaderWriterFactory {
2929 if (outfile == null) {
3030 throw new IllegalArgumentException();
3131 }
32-
32+
3333 String name = outfile.getName().toLowerCase();
3434 if (name.endsWith(".jar") || name.endsWith(".cmj")) {
3535 return new CharacterDataJarFileWriter(outfile);
@@ -37,10 +37,10 @@ public class CharacterDataFileReaderWriterFactory {
3737 } else if (name.endsWith(".zip")) {
3838 return new CharacterDataZipFileWriter(outfile);
3939 }
40-
40+
4141 throw new IOException("unsupported file type: " + name);
4242 }
43-
43+
4444 public CharacterDataArchiveFile openArchive(URI archiveFile) throws IOException {
4545 if (archiveFile == null) {
4646 throw new IllegalArgumentException();
@@ -55,8 +55,8 @@ public class CharacterDataFileReaderWriterFactory {
5555 // file以外は現在のところサポートしない。
5656 throw new UnsupportedOperationException();
5757 }
58-
59-
58+
59+
6060 public CharacterDataArchiveFile openArchive(File archiveFile) throws IOException {
6161 if (archiveFile == null) {
6262 throw new IllegalArgumentException();
@@ -66,7 +66,7 @@ public class CharacterDataFileReaderWriterFactory {
6666 // ディレクトリの場合
6767 return new CharacterDataDirectoryFile(archiveFile);
6868 }
69-
69+
7070 // zipまたはcmjファイルの場合
7171 String name = archiveFile.getName().toLowerCase();
7272 if (name.endsWith(".jar") || name.endsWith(".cmj")) {
@@ -75,8 +75,20 @@ public class CharacterDataFileReaderWriterFactory {
7575 } else if (name.endsWith(".zip")) {
7676 return new CharacterDataZipArchiveFile(archiveFile);
7777 }
78-
78+
7979 throw new IOException("unsupported file type: " + name);
8080 }
81-
81+
82+ /**
83+ * ファイルの拡張子からアーカイブとしてサポートされているタイプであるか判断する
84+ * @param fileName ファイル名
85+ * @return サポートされている場合はtrue、そうでなければfalse
86+ */
87+ public boolean isSupportedFile(String fileName) {
88+ if (fileName != null) {
89+ fileName = fileName.toLowerCase();
90+ return fileName.endsWith(".zip") || fileName.endsWith(".jar") || fileName.endsWith(".cmj");
91+ }
92+ return false;
93+ }
8294 }
--- a/src/main/java/charactermanaj/model/io/WorkingSetPersist.java
+++ b/src/main/java/charactermanaj/model/io/WorkingSetPersist.java
@@ -210,6 +210,8 @@ public class WorkingSetPersist {
210210 ws.setZoomFactor(workingSet2.getZoomFactor());
211211 ws.setViewPosition(workingSet2.getViewPosition());
212212 ws.setWindowRect(workingSet2.getWindowRect());
213+
214+ ws.setNoNeedDataDownload(workingSet2.isNoNeedDataDownload());
213215
214216 return ws;
215217 }
--- a/src/main/java/charactermanaj/model/io/WorkingSetXMLReader.java
+++ b/src/main/java/charactermanaj/model/io/WorkingSetXMLReader.java
@@ -229,6 +229,15 @@ public class WorkingSetXMLReader {
229229 break; // 最初の一要素のみ
230230 }
231231
232+ // ダウンロード不要フラグ
233+ boolean noNeedDataDownload = false;
234+ for (Element noNeedDataDownloadElm : getChildElements(docElm,
235+ "noNeedDataDownload")) {
236+ noNeedDataDownload = Boolean.parseBoolean(noNeedDataDownloadElm.getTextContent());
237+ break; // 最初の一要素のみ
238+ }
239+ workingSet.setNoNeedDataDownload(noNeedDataDownload);
240+
232241 return workingSet;
233242
234243 } catch (RuntimeException ex) {
--- a/src/main/java/charactermanaj/model/io/WorkingSetXMLWriter.java
+++ b/src/main/java/charactermanaj/model/io/WorkingSetXMLWriter.java
@@ -206,6 +206,13 @@ public class WorkingSetXMLWriter {
206206 // ズーム情報等
207207 root.appendChild(writeViewSettings(doc, ws.getZoomFactor(), ws.getViewPosition(), ws.getWindowRect()));
208208
209+ // データダウンロード不要フラグ
210+ if (ws.isNoNeedDataDownload()) {
211+ Element noNeedDataDownloadElm = doc.createElement("noNeedDataDownload");
212+ noNeedDataDownloadElm.setTextContent(Boolean.toString(ws.isNoNeedDataDownload()));
213+ root.appendChild(noNeedDataDownloadElm);
214+ }
215+
209216 doc.appendChild(root);
210217 return doc;
211218 }
--- a/src/main/java/charactermanaj/ui/ExportWizardDialog.java
+++ b/src/main/java/charactermanaj/ui/ExportWizardDialog.java
@@ -156,12 +156,15 @@ public class ExportWizardDialog extends JDialog {
156156
157157 // メインパネル
158158
159+ ScaleSupport scaleSupport = ScaleSupport.getInstance(this);
160+
159161 Container contentPane = getContentPane();
160162 contentPane.setLayout(new BorderLayout());
161163
162164 final JPanel mainPanel = new JPanel();
163165 mainPanel.setBorder(BorderFactory.createEtchedBorder());
164- final CardLayout mainPanelLayout = new CardLayout(5, 5);
166+ int mergin = (int)(5 * scaleSupport.getManualScaleX());
167+ final CardLayout mainPanelLayout = new CardLayout(mergin, mergin);
165168 mainPanel.setLayout(mainPanelLayout);
166169 contentPane.add(mainPanel, BorderLayout.CENTER);
167170
@@ -212,14 +215,14 @@ public class ExportWizardDialog extends JDialog {
212215 };
213216
214217 // panel1 : basic
215- this.basicPanel = new ExportInformationPanel(characterData, samplePicture);
218+ this.basicPanel = new ExportInformationPanel(characterData, samplePicture, scaleSupport);
216219 this.basicPanel.addComponentListener(componentListener);
217220 this.basicPanel.addChangeListener(actChangeValue);
218221 this.basicPanel.addChangeListener(actPanelEnabler);
219222 mainPanel.add(this.basicPanel, "basicPanel");
220223
221224 // panel2 : panelSelectPanel
222- this.partsSelectPanel = new ExportPartsSelectPanel(characterData);
225+ this.partsSelectPanel = new ExportPartsSelectPanel(characterData, scaleSupport);
223226 this.partsSelectPanel.addComponentListener(componentListener);
224227 this.partsSelectPanel.addChangeListener(actChangeValue);
225228 mainPanel.add(this.partsSelectPanel, "partsSelectPanel");
@@ -229,7 +232,8 @@ public class ExportWizardDialog extends JDialog {
229232 this.partsSelectPanel,
230233 this.basicPanel,
231234 characterData.getPartsSets().values(),
232- characterData.getDefaultPartsSetId());
235+ characterData.getDefaultPartsSetId(),
236+ scaleSupport);
233237 this.presetSelectPanel.addComponentListener(componentListener);
234238 this.presetSelectPanel.addChangeListener(actChangeValue);
235239 mainPanel.add(this.presetSelectPanel, "presetSelectPanel");
@@ -238,7 +242,8 @@ public class ExportWizardDialog extends JDialog {
238242 // button panel
239243
240244 JPanel btnPanel = new JPanel();
241- btnPanel.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 45));
245+ int gap = (int)(3 * scaleSupport.getManualScaleX());
246+ btnPanel.setBorder(BorderFactory.createEmptyBorder(gap, gap, gap, gap * 15)); // 3, 3, 3, 45
242247 GridBagLayout btnPanelLayout = new GridBagLayout();
243248 btnPanel.setLayout(btnPanelLayout);
244249
@@ -256,7 +261,7 @@ public class ExportWizardDialog extends JDialog {
256261 gbc.fill = GridBagConstraints.BOTH;
257262 gbc.ipadx = 0;
258263 gbc.ipady = 0;
259- gbc.insets = new Insets(3, 3, 3, 3);
264+ gbc.insets = new Insets(gap, gap, gap, gap);
260265 gbc.weightx = 1.;
261266 gbc.weighty = 0.;
262267 btnPanel.add(Box.createHorizontalGlue(), gbc);
@@ -297,13 +302,8 @@ public class ExportWizardDialog extends JDialog {
297302
298303 // 表示
299304
300- Dimension dim = new Dimension(500, 500);
301- ScaleSupport scaleSupport = ScaleSupport.getInstance(this);
302- if (scaleSupport != null) {
303- // HiDpi環境でのスケールを考慮したウィンドウサイズに補正する
304- dim = scaleSupport.manualScaled(dim);
305- }
306- setSize(dim);
305+ // HiDpi環境でのスケールを考慮したウィンドウサイズに補正する
306+ setSize(scaleSupport.manualScaled(new Dimension(500, 500)));
307307 setLocationRelativeTo(parent);
308308
309309 mainPanelLayout.first(mainPanel);
@@ -587,7 +587,8 @@ class ExportInformationPanel extends AbstractImportPanel implements ExportInform
587587
588588
589589
590- protected ExportInformationPanel(final CharacterData characterData, final BufferedImage samplePicture) {
590+ protected ExportInformationPanel(final CharacterData characterData, final BufferedImage samplePicture,
591+ ScaleSupport scaleSupport) {
591592 if (characterData == null) {
592593 throw new IllegalArgumentException();
593594 }
@@ -603,8 +604,9 @@ class ExportInformationPanel extends AbstractImportPanel implements ExportInform
603604 setLayout(basicPanelLayout);
604605
605606 JPanel contentsSpecPanel = new JPanel();
607+ int mergin = (int)(5 * scaleSupport.getManualScaleX());
606608 contentsSpecPanel.setBorder(BorderFactory.createCompoundBorder(
607- BorderFactory.createEmptyBorder(5, 5, 5, 5),
609+ BorderFactory.createEmptyBorder(mergin, mergin, mergin, mergin),
608610 BorderFactory.createTitledBorder(strings.getProperty("basic.contentsSpec"))));
609611 BoxLayout contentsSpecPanelLayout = new BoxLayout(contentsSpecPanel, BoxLayout.PAGE_AXIS);
610612 contentsSpecPanel.setLayout(contentsSpecPanelLayout);
@@ -625,11 +627,11 @@ class ExportInformationPanel extends AbstractImportPanel implements ExportInform
625627 ///
626628
627629 JPanel commentPanel = new JPanel();
628- Dimension archiveInfoPanelMinSize = new Dimension(300, 200);
630+ Dimension archiveInfoPanelMinSize = scaleSupport.manualScaled(new Dimension(300, 200));
629631 commentPanel.setMinimumSize(archiveInfoPanelMinSize);
630632 commentPanel.setPreferredSize(archiveInfoPanelMinSize);
631633 commentPanel.setBorder(BorderFactory.createCompoundBorder(
632- BorderFactory.createEmptyBorder(5, 5, 5, 5),
634+ BorderFactory.createEmptyBorder(mergin, mergin, mergin, mergin),
633635 BorderFactory.createTitledBorder(strings.getProperty("basic.comment"))));
634636 GridBagLayout commentPanelLayout = new GridBagLayout();
635637 commentPanel.setLayout(commentPanelLayout);
@@ -640,7 +642,8 @@ class ExportInformationPanel extends AbstractImportPanel implements ExportInform
640642 gbc.gridwidth = 1;
641643 gbc.weightx = 0.;
642644 gbc.weighty = 0.;
643- gbc.insets = new Insets(3, 3, 3, 3);
645+ int gap = (int)(3 * scaleSupport.getManualScaleX());
646+ gbc.insets = new Insets(gap, gap, gap, gap);
644647 gbc.anchor = GridBagConstraints.WEST;
645648 gbc.fill = GridBagConstraints.BOTH;
646649 commentPanel.add(new JLabel(strings.getProperty("author"), JLabel.RIGHT), gbc);
@@ -674,7 +677,7 @@ class ExportInformationPanel extends AbstractImportPanel implements ExportInform
674677
675678 sampleImagePanel = new SamplePicturePanel(samplePicture);
676679 sampleImagePanel.setBorder(BorderFactory.createCompoundBorder(
677- BorderFactory.createEmptyBorder(5, 5, 5, 5),
680+ BorderFactory.createEmptyBorder(mergin, mergin, mergin, mergin),
678681 BorderFactory.createTitledBorder(strings.getProperty("basic.sampleImage"))));
679682
680683
@@ -809,7 +812,7 @@ class ExportPartsSelectPanel extends AbstractImportPanel implements ExportPartsR
809812
810813 private Action actSortByTimestamp;
811814
812- protected ExportPartsSelectPanel(PartsSpecResolver partsSpecResolver) {
815+ protected ExportPartsSelectPanel(PartsSpecResolver partsSpecResolver, ScaleSupport scaleSupport) {
813816 if (partsSpecResolver == null) {
814817 throw new IllegalArgumentException();
815818 }
@@ -854,7 +857,6 @@ class ExportPartsSelectPanel extends AbstractImportPanel implements ExportPartsR
854857 };
855858 partsTable.setShowGrid(true);
856859 partsTable.setGridColor(appConfig.getGridColor());
857- partsTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
858860 partsTable.putClientProperty("terminateEditOnFocusLost", Boolean.TRUE);
859861 partsTable.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
860862 partsTable.setRowSelectionAllowed(true);
@@ -862,6 +864,10 @@ class ExportPartsSelectPanel extends AbstractImportPanel implements ExportPartsR
862864 // 行の高さをフォントの高さにする
863865 partsTable.setRowHeight((int)(partsTable.getFont().getSize() * 1.2));
864866
867+ // 列幅を調整する
868+ partsTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
869+ partsTableModel.adjustColumnModel(partsTable.getColumnModel(), scaleSupport.getManualScaleX());
870+
865871 Action actPartsSetCheck = new AbstractAction(strings.getProperty("parts.popup.check")) {
866872 private static final long serialVersionUID = 1L;
867873 public void actionPerformed(ActionEvent e) {
@@ -923,7 +929,8 @@ class ExportPartsSelectPanel extends AbstractImportPanel implements ExportPartsR
923929 gbc.gridwidth = 1;
924930 gbc.anchor = GridBagConstraints.EAST;
925931 gbc.fill = GridBagConstraints.BOTH;
926- gbc.insets = new Insets(3, 3, 3, 3);
932+ int gap = (int)(3 * scaleSupport.getManualScaleX());
933+ gbc.insets = new Insets(gap, gap, gap, gap);
927934 gbc.ipadx = 0;
928935 gbc.ipady = 0;
929936 JButton btnSelectAll = new JButton(actSelectAll);
@@ -952,13 +959,6 @@ class ExportPartsSelectPanel extends AbstractImportPanel implements ExportPartsR
952959 add(btnPanel, BorderLayout.SOUTH);
953960 }
954961
955- @Override
956- public void addNotify() {
957- super.addNotify();
958- ScaleSupport scaleSupport = ScaleSupport.getInstance(this);
959- partsTableModel.adjustColumnModel(partsTable.getColumnModel(), scaleSupport.getManualScaleX());
960- }
961-
962962 protected void loadPartsInfo(PartsSpecResolver partsSpecResolver) {
963963 partsTableModel.clear();
964964 for (PartsCategory partsCategory : partsSpecResolver.getPartsCategories()) {
@@ -1049,7 +1049,8 @@ class ExportPresetSelectPanel extends AbstractImportPanel implements ExportPrese
10491049 protected ExportPresetSelectPanel(
10501050 final ExportPartsResolver exportPartsResolver,
10511051 final ExportInformationResolver exportInfoResolver,
1052- Collection<PartsSet> partsSets, String defaultPresetId) {
1052+ Collection<PartsSet> partsSets, String defaultPresetId,
1053+ ScaleSupport scaleSupport) {
10531054
10541055 this.exportPartsResolver = exportPartsResolver;
10551056
@@ -1123,6 +1124,10 @@ class ExportPresetSelectPanel extends AbstractImportPanel implements ExportPrese
11231124 // 行の高さをフォントの高さにする
11241125 presetTable.setRowHeight((int)(presetTable.getFont().getSize() * 1.2));
11251126
1127+ // 列幅を調整する
1128+ presetTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
1129+ presetTableModel.adjustColumnModel(presetTable.getColumnModel(), scaleSupport.getManualScaleX());
1130+
11261131 final Action actSelectUsedParts = new AbstractAction(
11271132 strings.getProperty("preset.popup.selectUsedParts")) {
11281133 private static final long serialVersionUID = 1L;
@@ -1137,8 +1142,6 @@ class ExportPresetSelectPanel extends AbstractImportPanel implements ExportPrese
11371142 presetTable.setComponentPopupMenu(popupMenu);
11381143
11391144
1140- presetTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
1141-
11421145 add(new JScrollPane(presetTable), BorderLayout.CENTER);
11431146
11441147 actSelectAll = new AbstractAction(strings.getProperty("parts.btn.selectAll")) {
@@ -1173,7 +1176,8 @@ class ExportPresetSelectPanel extends AbstractImportPanel implements ExportPrese
11731176 gbc.gridwidth = 1;
11741177 gbc.anchor = GridBagConstraints.EAST;
11751178 gbc.fill = GridBagConstraints.BOTH;
1176- gbc.insets = new Insets(3, 3, 3, 3);
1179+ int gap = (int)(3 * scaleSupport.getManualScaleX());
1180+ gbc.insets = new Insets(gap, gap, gap, gap);
11771181 gbc.ipadx = 0;
11781182 gbc.ipady = 0;
11791183 JButton btnSelectAll = new JButton(actSelectAll);
@@ -1197,13 +1201,6 @@ class ExportPresetSelectPanel extends AbstractImportPanel implements ExportPrese
11971201 add(btnPanel, BorderLayout.SOUTH);
11981202 }
11991203
1200- @Override
1201- public void addNotify() {
1202- super.addNotify();
1203- ScaleSupport scaleSupport = ScaleSupport.getInstance(this);
1204- presetTableModel.adjustColumnModel(presetTable.getColumnModel(), scaleSupport.getManualScaleX());
1205- }
1206-
12071204 protected void loadPresetInfo(Collection<PartsSet> partsSets, String defaultPresetId) {
12081205 presetTableModel.clear();
12091206 for (PartsSet orgPartsSet : partsSets) {
--- a/src/main/java/charactermanaj/ui/ImportWizardDialog.java
+++ b/src/main/java/charactermanaj/ui/ImportWizardDialog.java
@@ -24,6 +24,7 @@ import java.awt.event.WindowAdapter;
2424 import java.awt.event.WindowEvent;
2525 import java.awt.image.BufferedImage;
2626 import java.io.File;
27+import java.io.FileNotFoundException;
2728 import java.io.IOException;
2829 import java.net.URI;
2930 import java.sql.Timestamp;
@@ -93,6 +94,7 @@ import charactermanaj.model.PartsSet;
9394 import charactermanaj.model.PartsSpec;
9495 import charactermanaj.model.io.AbstractCharacterDataArchiveFile.CategoryLayerPair;
9596 import charactermanaj.model.io.AbstractCharacterDataArchiveFile.PartsImageContent;
97+import charactermanaj.model.io.CharacterDataFileReaderWriterFactory;
9698 import charactermanaj.model.io.CharacterDataPersistent;
9799 import charactermanaj.model.io.ImportModel;
98100 import charactermanaj.ui.model.AbstractTableModelWithComboBoxModel;
@@ -102,6 +104,8 @@ import charactermanaj.ui.progress.WorkerException;
102104 import charactermanaj.ui.progress.WorkerWithProgessDialog;
103105 import charactermanaj.ui.util.FileDropTarget;
104106 import charactermanaj.ui.util.ScaleSupport;
107+import charactermanaj.util.DownloadUtils;
108+import charactermanaj.util.DownloadUtils.HeadResponse;
105109 import charactermanaj.util.ErrorMessageHelper;
106110 import charactermanaj.util.LocalizedResourcePropertyLoader;
107111
@@ -172,14 +176,10 @@ public class ImportWizardDialog extends JDialog {
172176 * 親フレーム
173177 * @param current
174178 * 更新対象となる現在のプロファイル(新規インポートの場合はnull)
175- * @param initFiles
176- * アーカイブファィルまたはディレクトリの初期選択、なければnullまたは空
177179 */
178- public ImportWizardDialog(JFrame parent, CharacterData current, List<File> initFiles) {
180+ public ImportWizardDialog(JFrame parent, CharacterData current) {
179181 super(parent, true);
180182 initComponent(parent, current);
181-
182- importFileSelectPanel.setSelectFile(initFiles);
183183 }
184184
185185 /**
@@ -196,6 +196,18 @@ public class ImportWizardDialog extends JDialog {
196196 }
197197
198198 /**
199+ * @param initFiles
200+ * アーカイブファィルまたはディレクトリの初期選択、なければnullまたは空
201+ */
202+ public void initSelectFile(File initFile) {
203+ importFileSelectPanel.setSelectFile(initFile);
204+ }
205+
206+ public void initSelectURL(String url) {
207+ importFileSelectPanel.initSelectURL(url);
208+ }
209+
210+ /**
199211 * ウィザードダイアログのコンポーネントを初期化します.<br>
200212 * currentがnullの場合は新規インポート、そうでない場合は更新インポートとります。
201213 *
@@ -227,12 +239,15 @@ public class ImportWizardDialog extends JDialog {
227239
228240 // メインパネル
229241
242+ ScaleSupport scaleSupport = ScaleSupport.getInstance(this);
243+
230244 Container contentPane = getContentPane();
231245 contentPane.setLayout(new BorderLayout());
232246
233247 final JPanel mainPanel = new JPanel();
234248 mainPanel.setBorder(BorderFactory.createEtchedBorder());
235- this.mainPanelLayout = new CardLayout(5, 5);
249+ int gap = (int)(5 * scaleSupport.getManualScaleX());
250+ this.mainPanelLayout = new CardLayout(gap, gap);
236251 mainPanel.setLayout(mainPanelLayout);
237252 contentPane.add(mainPanel, BorderLayout.CENTER);
238253
@@ -291,25 +306,25 @@ public class ImportWizardDialog extends JDialog {
291306 };
292307
293308 // ImportFileSelectPanel
294- this.importFileSelectPanel = new ImportFileSelectPanel();
309+ this.importFileSelectPanel = new ImportFileSelectPanel(scaleSupport);
295310 this.importFileSelectPanel.addComponentListener(componentListener);
296311 this.importFileSelectPanel.addChangeListener(changeListener);
297312 mainPanel.add(this.importFileSelectPanel, ImportFileSelectPanel.PANEL_NAME);
298313
299314 // ImportTypeSelectPanel
300- this.importTypeSelectPanel = new ImportTypeSelectPanel();
315+ this.importTypeSelectPanel = new ImportTypeSelectPanel(scaleSupport);
301316 this.importTypeSelectPanel.addComponentListener(componentListener);
302317 this.importTypeSelectPanel.addChangeListener(changeListener);
303318 mainPanel.add(this.importTypeSelectPanel, ImportTypeSelectPanel.PANEL_NAME);
304319
305320 // ImportPartsSelectPanel
306- this.importPartsSelectPanel = new ImportPartsSelectPanel();
321+ this.importPartsSelectPanel = new ImportPartsSelectPanel(scaleSupport);
307322 this.importPartsSelectPanel.addComponentListener(componentListener);
308323 this.importPartsSelectPanel.addChangeListener(changeListener);
309324 mainPanel.add(this.importPartsSelectPanel, ImportPartsSelectPanel.PANEL_NAME);
310325
311326 // ImportPresetSelectPanel
312- this.importPresetSelectPanel = new ImportPresetSelectPanel();
327+ this.importPresetSelectPanel = new ImportPresetSelectPanel(scaleSupport);
313328 this.importPresetSelectPanel.addComponentListener(componentListener);
314329 this.importPresetSelectPanel.addChangeListener(changeListener);
315330 mainPanel.add(this.importPresetSelectPanel, ImportPresetSelectPanel.PANEL_NAME);
@@ -318,7 +333,8 @@ public class ImportWizardDialog extends JDialog {
318333 // button panel
319334
320335 JPanel btnPanel = new JPanel();
321- btnPanel.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 45));
336+ int mergin = (int)(3 * scaleSupport.getManualScaleX());
337+ btnPanel.setBorder(BorderFactory.createEmptyBorder(mergin, mergin, mergin, mergin * 15)); // 3,3,3,45
322338 GridBagLayout btnPanelLayout = new GridBagLayout();
323339 btnPanel.setLayout(btnPanelLayout);
324340
@@ -336,7 +352,7 @@ public class ImportWizardDialog extends JDialog {
336352 gbc.fill = GridBagConstraints.BOTH;
337353 gbc.ipadx = 0;
338354 gbc.ipady = 0;
339- gbc.insets = new Insets(3, 3, 3, 3);
355+ gbc.insets = new Insets(mergin, mergin, mergin, mergin);
340356 gbc.weightx = 1.;
341357 gbc.weighty = 0.;
342358 btnPanel.add(Box.createHorizontalGlue(), gbc);
@@ -378,11 +394,8 @@ public class ImportWizardDialog extends JDialog {
378394 // 表示
379395
380396 Dimension dim = new Dimension(500, 500);
381- ScaleSupport scaleSupport = ScaleSupport.getInstance(this);
382- if (scaleSupport != null) {
383- // HiDpi環境でのスケールを考慮したウィンドウサイズに補正する
384- dim = scaleSupport.manualScaled(dim);
385- }
397+ // HiDpi環境でのスケールを考慮したウィンドウサイズに補正する
398+ dim = scaleSupport.manualScaled(dim);
386399 setSize(dim);
387400 setLocationRelativeTo(parent);
388401
@@ -778,11 +791,21 @@ class ImportFileSelectPanel extends ImportWizardCardPanel {
778791 private Action actChooseDirectory;
779792
780793
794+ /**
795+ * URLを指定してインポート
796+ */
797+ private JRadioButton radioURL;
798+
799+ /**
800+ * URL入力ボックス
801+ */
802+ private JTextField txtURL;
803+
781804
782805 /* 以下、対象ファイルの読み取り結果 */
783806
784807
785- public ImportFileSelectPanel() {
808+ public ImportFileSelectPanel(ScaleSupport scaleSupport) {
786809 setLayout(new BorderLayout());
787810
788811 Properties strings = LocalizedResourcePropertyLoader.getCachedInstance()
@@ -805,9 +828,11 @@ class ImportFileSelectPanel extends ImportWizardCardPanel {
805828
806829 txtArchiveFile = new JTextField();
807830 txtDirectory = new JTextField();
831+ txtURL = new JTextField();
808832
809833 txtArchiveFile.getDocument().addDocumentListener(documentListener);
810834 txtDirectory.getDocument().addDocumentListener(documentListener);
835+ txtURL.getDocument().addDocumentListener(documentListener);
811836
812837 actChooseFile = new AbstractAction(strings.getProperty("browse")) {
813838 private static final long serialVersionUID = 1L;
@@ -830,6 +855,7 @@ class ImportFileSelectPanel extends ImportWizardCardPanel {
830855
831856 radioArchiveFile = new JRadioButton(strings.getProperty("importingArchiveFile"));
832857 radioDirectory = new JRadioButton(strings.getProperty("importingDirectory"));
858+ radioURL = new JRadioButton(strings.getProperty("importingURL"));
833859
834860 ChangeListener radioChangeListener = new ChangeListener() {
835861 public void stateChanged(ChangeEvent e) {
@@ -839,27 +865,32 @@ class ImportFileSelectPanel extends ImportWizardCardPanel {
839865 };
840866 radioArchiveFile.addChangeListener(radioChangeListener);
841867 radioDirectory.addChangeListener(radioChangeListener);
868+ radioURL.addChangeListener(radioChangeListener);
842869
843870 ButtonGroup btnGroup = new ButtonGroup();
844871 btnGroup.add(radioArchiveFile);
845872 btnGroup.add(radioDirectory);
873+ btnGroup.add(radioURL);
846874
847875 // アーカイブからのインポートをデフォルトとする
848876 radioArchiveFile.setSelected(true);
849877
850878 GridBagConstraints gbc = new GridBagConstraints();
851879
880+ gbc.ipadx = 0;
881+ gbc.ipady = 0;
882+ int gap = (int)(3 * scaleSupport.getManualScaleX());
883+ gbc.insets = new Insets(gap, gap, gap, gap);
884+ gbc.anchor = GridBagConstraints.WEST;
885+ gbc.fill = GridBagConstraints.BOTH;
886+
887+ // アーカイブ
852888 gbc.gridx = 0;
853889 gbc.gridy = 0;
854890 gbc.weightx = 1.;
855891 gbc.weighty = 0.;
856892 gbc.gridheight = 1;
857893 gbc.gridwidth = 3;
858- gbc.ipadx = 0;
859- gbc.ipady = 0;
860- gbc.insets = new Insets(3, 3, 3, 3);
861- gbc.anchor = GridBagConstraints.WEST;
862- gbc.fill = GridBagConstraints.BOTH;
863894 fileChoosePanel.add(radioArchiveFile, gbc);
864895
865896 gbc.gridx = 0;
@@ -881,6 +912,7 @@ class ImportFileSelectPanel extends ImportWizardCardPanel {
881912 gbc.weightx = 0.;
882913 fileChoosePanel.add(new JButton(actChooseFile), gbc);
883914
915+ // ディレクトり
884916 gbc.gridx = 0;
885917 gbc.gridy = 2;
886918 gbc.ipadx = 0;
@@ -907,8 +939,31 @@ class ImportFileSelectPanel extends ImportWizardCardPanel {
907939 gbc.weightx = 0.;
908940 fileChoosePanel.add(new JButton(actChooseDirectory), gbc);
909941
942+ // URL
943+ gbc.gridx = 0;
944+ gbc.gridy = 4;
945+ gbc.weightx = 1.;
946+ gbc.weighty = 0.;
947+ gbc.gridheight = 1;
948+ gbc.gridwidth = 3;
949+ fileChoosePanel.add(radioURL, gbc);
950+
910951 gbc.gridx = 0;
911952 gbc.gridy = 4;
953+ gbc.gridwidth = 1;
954+ gbc.ipadx = 45;
955+ gbc.weightx = 0;
956+ fileChoosePanel.add(Box.createHorizontalGlue(), gbc);
957+
958+ gbc.gridx = 1;
959+ gbc.gridy = 5;
960+ gbc.ipadx = 0;
961+ gbc.weightx = 1.;
962+ fileChoosePanel.add(txtURL, gbc);
963+
964+ // パディング
965+ gbc.gridx = 0;
966+ gbc.gridy = 6;
912967 gbc.ipadx = 0;
913968 gbc.gridwidth = 3;
914969 gbc.weightx = 1.;
@@ -924,7 +979,8 @@ class ImportFileSelectPanel extends ImportWizardCardPanel {
924979 if (dropFiles == null || dropFiles.isEmpty()) {
925980 return;
926981 }
927- setSelectFile(dropFiles);
982+ File dropFile = dropFiles.get(0);
983+ setSelectFile(dropFile);
928984 }
929985
930986 @Override
@@ -943,19 +999,15 @@ class ImportFileSelectPanel extends ImportWizardCardPanel {
943999 * @param dropFile
9441000 * アーカイブファイルまたはディレクトリ、もしくはnull
9451001 */
946- public void setSelectFile(List<File> dropFiles) {
947-
948- File dropFile = null;
949- if (dropFiles != null && dropFiles.size() > 0) {
950- dropFile = dropFiles.get(0);
951- }
952-
1002+ public void setSelectFile(File dropFile) {
9531003 if (dropFile == null) {
9541004 // 選択なしの場合
9551005 txtDirectory.setText("");
9561006 txtArchiveFile.setText("");
1007+ txtURL.setText("");
9571008 radioDirectory.setSelected(false);
9581009 radioArchiveFile.setSelected(false);
1010+ radioURL.setSelected(false);
9591011
9601012 } else if (dropFile.isDirectory()) {
9611013 // ディレクトリの場合
@@ -969,15 +1021,39 @@ class ImportFileSelectPanel extends ImportWizardCardPanel {
9691021 }
9701022 }
9711023
1024+ /**
1025+ * URL入力を選択状態とする
1026+ * @param url URL
1027+ */
1028+ public void initSelectURL(String url) {
1029+ if (url != null && (url.startsWith("http://") || url.startsWith("https://"))) {
1030+ // URL指定の場合
1031+ txtURL.setText(url);
1032+ radioURL.setSelected(true);
1033+
1034+ } else {
1035+ // 選択なしの場合
1036+ txtDirectory.setText("");
1037+ txtArchiveFile.setText("");
1038+ txtURL.setText("");
1039+ radioDirectory.setSelected(false);
1040+ radioArchiveFile.setSelected(false);
1041+ radioURL.setSelected(false);
1042+ }
1043+ }
1044+
9721045 protected void updateUIState() {
9731046 boolean enableArchiveFile = radioArchiveFile.isSelected();
9741047 boolean enableDirectory = radioDirectory.isSelected();
1048+ boolean enableURL = radioURL.isSelected();
9751049
9761050 txtArchiveFile.setEnabled(enableArchiveFile);
9771051 actChooseFile.setEnabled(enableArchiveFile);
9781052
9791053 txtDirectory.setEnabled(enableDirectory);
9801054 actChooseDirectory.setEnabled(enableDirectory);
1055+
1056+ txtURL.setEnabled(enableURL);
9811057 }
9821058
9831059 protected void onChooseFile() {
@@ -1019,6 +1095,11 @@ class ImportFileSelectPanel extends ImportWizardCardPanel {
10191095 if (directoryTxt != null && directoryTxt.trim().length() > 0) {
10201096 return true;
10211097 }
1098+ } else if (radioURL.isSelected()) {
1099+ String url = txtURL.getText();
1100+ if (url != null && (url.startsWith("http://") || url.startsWith("https://"))) {
1101+ return true;
1102+ }
10221103 }
10231104 return false;
10241105 }
@@ -1076,6 +1157,15 @@ class ImportFileSelectPanel extends ImportWizardCardPanel {
10761157 }
10771158 importArchive = file.toURI();
10781159
1160+ } else if (radioURL.isSelected()) {
1161+ // URLからダウンロード
1162+ File tmpFile = loadTemporaryFromURL(txtURL.getText());
1163+ if (tmpFile == null) {
1164+ // エラーメッセージは表示済みのため、単にnullでnextを拒否する
1165+ return null;
1166+ }
1167+ importArchive = tmpFile.toURI();
1168+
10791169 } else {
10801170 // それ以外はサポートしていない.
10811171 return null;
@@ -1085,16 +1175,14 @@ class ImportFileSelectPanel extends ImportWizardCardPanel {
10851175 parent.importModel.openImportSource(importArchive, parent.current);
10861176
10871177 // ワーカースレッドでアーカイブの読み込みを行う.
1088- Worker<Object> worker = new Worker<Object>() {
1178+ Worker<Void> worker = new Worker<Void>() {
10891179 public Void doWork(ProgressHandle progressHandle) throws IOException {
10901180 parent.importModel.loadContents(progressHandle);
10911181 return null;
10921182 }
10931183 };
10941184
1095- WorkerWithProgessDialog<Object> dlg
1096- = new WorkerWithProgessDialog<Object>(parent, worker);
1097-
1185+ WorkerWithProgessDialog<Void> dlg = new WorkerWithProgessDialog<Void>(parent, worker);
10981186 dlg.startAndWait();
10991187
11001188 // 読み込めたら次ページへ
@@ -1109,6 +1197,70 @@ class ImportFileSelectPanel extends ImportWizardCardPanel {
11091197
11101198 return null;
11111199 }
1200+
1201+ /**
1202+ * 指定されたURLからテンポラリにダウンロードして、そのファイルを返す。
1203+ * URLが適切でない場合、もしくはエラーが発生した場合はnullを返す。
1204+ * @param url URL
1205+ * @return ダウンロードしたファイル
1206+ */
1207+ private File loadTemporaryFromURL(final String url) {
1208+ final Properties strings = LocalizedResourcePropertyLoader.getCachedInstance()
1209+ .getLocalizedProperties(ImportWizardDialog.STRINGS_RESOURCE);
1210+ final AppConfig appConfig = AppConfig.getInstance();
1211+ final DownloadUtils downloader = new DownloadUtils();
1212+ downloader.setImpersonateUserAgent(appConfig.getImpersonateUserAgent());
1213+ downloader.setDeleteDownloadFileOnExit(appConfig.isDeleteDownloadFileOnExit());
1214+
1215+ try {
1216+ // ワーカースレッドでアーカイブの読み込みを行う.
1217+ Worker<File> worker = new Worker<File>() {
1218+ public File doWork(ProgressHandle progressHandle) throws IOException {
1219+ progressHandle.setIndeterminate(true);
1220+ progressHandle.setCaption(strings.getProperty("downloading.checkhead"));
1221+
1222+ HeadResponse headResponse = downloader.getHead(url);
1223+
1224+ String contentType = headResponse.getContentType();
1225+ String ext = headResponse.getDotExtension();
1226+ CharacterDataFileReaderWriterFactory archiveRdWrFactory =
1227+ CharacterDataFileReaderWriterFactory.getInstance();
1228+ if (contentType == null || !contentType.startsWith("application/") ||
1229+ !archiveRdWrFactory.isSupportedFile(ext)) {
1230+ // コンテンツタイプが不明、もしくはバイナリではない
1231+ // あるいは、ファイル名がzip, jar, cmjのいずれでもない場合
1232+ return null;
1233+ }
1234+
1235+ progressHandle.setCaption(strings.getProperty("downloading.waitForDownload"));
1236+ return downloader.downloadTemporary(headResponse);
1237+ }
1238+ };
1239+
1240+ WorkerWithProgessDialog<File> dlg = new WorkerWithProgessDialog<File>(parent, worker);
1241+ try {
1242+ File tempFile = dlg.startAndWait();
1243+ if (tempFile != null) {
1244+ return tempFile;
1245+ }
1246+ JOptionPane.showMessageDialog(this, strings.getProperty("downloading.invalidFileType"),
1247+ "ERROR", JOptionPane.ERROR_MESSAGE);
1248+ return null;
1249+
1250+ } catch (WorkerException ex) {
1251+ Throwable iex = ex.getCause();
1252+ throw (iex == null) ? ex : (Exception) iex;
1253+ }
1254+
1255+ } catch (FileNotFoundException ex) {
1256+ JOptionPane.showMessageDialog(this, strings.getProperty("downloading.notFound"),
1257+ "ERROR", JOptionPane.ERROR_MESSAGE);
1258+
1259+ } catch (Exception ex) {
1260+ ErrorMessageHelper.showErrorDialog(this, ex);
1261+ }
1262+ return null;
1263+ }
11121264 }
11131265
11141266 class URLTableRow {
@@ -1145,7 +1297,7 @@ class URLTableModel extends AbstractTableModelWithComboBoxModel<URLTableRow> {
11451297
11461298 static {
11471299 COLUMN_NAMES = new String[] {
1148-"作者",
1300+ "作者",
11491301 "URL",
11501302 };
11511303 COLUMN_WIDTHS = new int[] {
@@ -1268,7 +1420,7 @@ class ImportTypeSelectPanel extends ImportWizardCardPanel {
12681420
12691421 /* 以下、選択結果 */
12701422
1271- public ImportTypeSelectPanel() {
1423+ public ImportTypeSelectPanel(ScaleSupport scaleSupport) {
12721424 Properties strings = LocalizedResourcePropertyLoader.getCachedInstance()
12731425 .getLocalizedProperties(ImportWizardDialog.STRINGS_RESOURCE);
12741426
@@ -1277,9 +1429,10 @@ class ImportTypeSelectPanel extends ImportWizardCardPanel {
12771429 setLayout(basicPanelLayout);
12781430
12791431 JPanel contentsSpecPanel = new JPanel();
1432+ int mergin = (int)(5 * scaleSupport.getManualScaleX());
12801433 contentsSpecPanel.setBorder(BorderFactory.createCompoundBorder(
1281- BorderFactory.createEmptyBorder(5, 5, 5, 5), BorderFactory
1282- .createTitledBorder(strings.getProperty("basic.contentsSpec"))));
1434+ BorderFactory.createEmptyBorder(mergin, mergin, mergin, mergin),
1435+ BorderFactory.createTitledBorder(strings.getProperty("basic.contentsSpec"))));
12831436 BoxLayout contentsSpecPanelLayout = new BoxLayout(contentsSpecPanel, BoxLayout.PAGE_AXIS);
12841437 contentsSpecPanel.setLayout(contentsSpecPanelLayout);
12851438
@@ -1295,9 +1448,9 @@ class ImportTypeSelectPanel extends ImportWizardCardPanel {
12951448
12961449 JPanel archiveInfoPanel = new JPanel();
12971450 archiveInfoPanel.setBorder(BorderFactory.createCompoundBorder(BorderFactory
1298- .createEmptyBorder(5, 5, 5, 5), BorderFactory
1299- .createTitledBorder(strings.getProperty("basic.archiveInfo"))));
1300- Dimension archiveInfoPanelMinSize = new Dimension(300, 200);
1451+ .createEmptyBorder(mergin, mergin, mergin, mergin),
1452+ BorderFactory.createTitledBorder(strings.getProperty("basic.archiveInfo"))));
1453+ Dimension archiveInfoPanelMinSize = scaleSupport.manualScaled(new Dimension(300, 200));
13011454 archiveInfoPanel.setMinimumSize(archiveInfoPanelMinSize);
13021455 archiveInfoPanel.setPreferredSize(archiveInfoPanelMinSize);
13031456 GridBagLayout commentPanelLayout = new GridBagLayout();
@@ -1309,7 +1462,8 @@ class ImportTypeSelectPanel extends ImportWizardCardPanel {
13091462 gbc.gridwidth = 2;
13101463 gbc.weightx = 0.;
13111464 gbc.weighty = 0.;
1312- gbc.insets = new Insets(3, 3, 3, 3);
1465+ int gap = (int)(3 * scaleSupport.getManualScaleX());
1466+ gbc.insets = new Insets(gap, gap, gap, gap);
13131467 gbc.anchor = GridBagConstraints.WEST;
13141468 gbc.fill = GridBagConstraints.BOTH;
13151469
@@ -1420,9 +1574,8 @@ class ImportTypeSelectPanel extends ImportWizardCardPanel {
14201574 JPanel samplePictureTitledPanel = new JPanel(new BorderLayout());
14211575 samplePictureTitledPanel.add(samplePicturePanelSP, BorderLayout.CENTER);
14221576 samplePictureTitledPanel.setBorder(BorderFactory.createCompoundBorder(
1423- BorderFactory.createEmptyBorder(5, 5, 5, 5), BorderFactory
1424- .createTitledBorder(strings
1425- .getProperty("basic.sampleImage"))));
1577+ BorderFactory.createEmptyBorder(mergin, mergin, mergin, mergin),
1578+ BorderFactory.createTitledBorder(strings.getProperty("basic.sampleImage"))));
14261579
14271580 // /
14281581
@@ -1719,7 +1872,7 @@ class ImportPartsSelectPanel extends ImportWizardCardPanel {
17191872 private Action actSortByTimestamp;
17201873
17211874
1722- public ImportPartsSelectPanel() {
1875+ public ImportPartsSelectPanel(ScaleSupport scaleSupport) {
17231876 Properties strings = LocalizedResourcePropertyLoader.getCachedInstance()
17241877 .getLocalizedProperties(ImportWizardDialog.STRINGS_RESOURCE);
17251878
@@ -1728,8 +1881,7 @@ class ImportPartsSelectPanel extends ImportWizardCardPanel {
17281881 profileSizePanel = new JPanel();
17291882 GridBagLayout profileSizePanelLayout = new GridBagLayout();
17301883 profileSizePanel.setLayout(profileSizePanelLayout);
1731- profileSizePanel.setBorder(BorderFactory
1732- .createTitledBorder("プロファイルのサイズ"));
1884+ profileSizePanel.setBorder(BorderFactory.createTitledBorder(strings.getProperty("sizeOfProfile")));
17331885
17341886 GridBagConstraints gbc = new GridBagConstraints();
17351887
@@ -1739,12 +1891,13 @@ class ImportPartsSelectPanel extends ImportWizardCardPanel {
17391891 gbc.gridwidth = 1;
17401892 gbc.anchor = GridBagConstraints.EAST;
17411893 gbc.fill = GridBagConstraints.BOTH;
1742- gbc.insets = new Insets(3, 3, 3, 3);
1894+ int gap = (int)(3 * scaleSupport.getManualScaleX());
1895+ gbc.insets = new Insets(gap, gap, gap, gap);
17431896 gbc.weightx = 0.;
17441897 gbc.weighty = 0.;
17451898 gbc.ipadx = 0;
17461899 gbc.ipady = 0;
1747- profileSizePanel.add(new JLabel("幅:", JLabel.RIGHT), gbc);
1900+ profileSizePanel.add(new JLabel(strings.getProperty("widthOfProfile"), JLabel.RIGHT), gbc);
17481901
17491902 txtProfileWidth = new JTextField();
17501903 txtProfileWidth.setEditable(false);
@@ -1754,7 +1907,7 @@ class ImportPartsSelectPanel extends ImportWizardCardPanel {
17541907
17551908 gbc.gridx = 2;
17561909 gbc.gridy = 0;
1757- profileSizePanel.add(new JLabel("高さ:", JLabel.RIGHT), gbc);
1910+ profileSizePanel.add(new JLabel(strings.getProperty("heightOfProfile"), JLabel.RIGHT), gbc);
17581911
17591912 txtProfileHeight = new JTextField();
17601913 txtProfileHeight.setEditable(false);
@@ -1847,7 +2000,6 @@ class ImportPartsSelectPanel extends ImportWizardCardPanel {
18472000 return comp;
18482001 }
18492002 };
1850- partsTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
18512003 partsTable.setShowGrid(true);
18522004 partsTable.setGridColor(appConfig.getGridColor());
18532005 partsTable.putClientProperty("terminateEditOnFocusLost", Boolean.TRUE);
@@ -1857,6 +2009,10 @@ class ImportPartsSelectPanel extends ImportWizardCardPanel {
18572009 // 行の高さをフォントの高さにする
18582010 partsTable.setRowHeight((int)(partsTable.getFont().getSize() * 1.2));
18592011
2012+ // 列幅の調整
2013+ partsTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
2014+ partsTableModel.adjustColumnModel(partsTable.getColumnModel(), scaleSupport.getManualScaleX());
2015+
18602016 Action actPartsSetCheck = new AbstractAction(strings.getProperty("parts.popup.check")) {
18612017 private static final long serialVersionUID = 1L;
18622018 public void actionPerformed(ActionEvent e) {
@@ -1929,7 +2085,7 @@ class ImportPartsSelectPanel extends ImportWizardCardPanel {
19292085 gbc.gridwidth = 1;
19302086 gbc.anchor = GridBagConstraints.EAST;
19312087 gbc.fill = GridBagConstraints.BOTH;
1932- gbc.insets = new Insets(3, 3, 3, 3);
2088+ gbc.insets = new Insets(gap, gap, gap, gap);
19332089 gbc.ipadx = 0;
19342090 gbc.ipady = 0;
19352091 JButton btnSelectAll = new JButton(actSelectAll);
@@ -1959,13 +2115,6 @@ class ImportPartsSelectPanel extends ImportWizardCardPanel {
19592115 }
19602116
19612117 @Override
1962- public void addNotify() {
1963- super.addNotify();
1964- ScaleSupport scaleSupport = ScaleSupport.getInstance(this);
1965- partsTableModel.adjustColumnModel(partsTable.getColumnModel(), scaleSupport.getDefaultScaleX());
1966- }
1967-
1968- @Override
19692118 public void onActive(ImportWizardDialog parent, ImportWizardCardPanel previousPanel) {
19702119 this.parent = parent;
19712120 if (previousPanel == parent.importPresetSelectPanel) {
@@ -2022,6 +2171,7 @@ class ImportPartsSelectPanel extends ImportWizardCardPanel {
20222171
20232172 String defaultPartsSetId;
20242173 CharacterData presetImportTarget;
2174+ boolean selectAllPreset;
20252175 if (parent.current == null) {
20262176 presetImportTarget = null;
20272177 CharacterData cd = parent.importModel.getCharacterData();
@@ -2030,12 +2180,14 @@ class ImportPartsSelectPanel extends ImportWizardCardPanel {
20302180 } else {
20312181 defaultPartsSetId = null;
20322182 }
2183+ selectAllPreset = true; // 新規プロファイルの場合はプリセットをインポートする
20332184 } else {
20342185 presetImportTarget = parent.current;
20352186 defaultPartsSetId = null; // 既存の場合はデフォルトのパーツセットであるかは表示する必要ないのでnullにする.
2187+ selectAllPreset = (parent.current.getPartsCount() == 0); // パーツが空の場合はプリセットをインポートする
20362188 }
20372189
2038- parent.importPresetSelectPanel.initModel(partsSets, defaultPartsSetId, presetImportTarget);
2190+ parent.importPresetSelectPanel.initModel(partsSets, defaultPartsSetId, presetImportTarget, selectAllPreset);
20392191 }
20402192
20412193 @Override
@@ -2739,7 +2891,7 @@ class ImportPartsTableModel extends AbstractTableModelWithComboBoxModel<ImportPa
27392891 public void adjustColumnModel(TableColumnModel columnModel, double scale) {
27402892 int mx = columnModel.getColumnCount();
27412893 for (int idx = 0; idx < mx; idx++) {
2742- columnModel.getColumn(idx).setWidth((int)(COLUMN_WIDTHS[idx] * scale));
2894+ columnModel.getColumn(idx).setPreferredWidth((int)(COLUMN_WIDTHS[idx] * scale));
27432895 }
27442896 }
27452897
@@ -2870,7 +3022,7 @@ class ImportPresetSelectPanel extends ImportWizardCardPanel {
28703022
28713023 private Action actSelectUsedParts;
28723024
2873- public ImportPresetSelectPanel() {
3025+ public ImportPresetSelectPanel(ScaleSupport scaleSupport) {
28743026 Properties strings = LocalizedResourcePropertyLoader.getCachedInstance()
28753027 .getLocalizedProperties(ImportWizardDialog.STRINGS_RESOURCE);
28763028
@@ -2948,7 +3100,10 @@ class ImportPresetSelectPanel extends ImportWizardCardPanel {
29483100 popupMenu.add(actSelectUsedParts);
29493101
29503102 presetTable.setComponentPopupMenu(popupMenu);
3103+
3104+ // 列幅の調整
29513105 presetTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
3106+ presetTableModel.adjustColumnModel(presetTable.getColumnModel(), scaleSupport.getManualScaleX());
29523107
29533108 add(new JScrollPane(presetTable), BorderLayout.CENTER);
29543109
@@ -2987,7 +3142,8 @@ class ImportPresetSelectPanel extends ImportWizardCardPanel {
29873142 gbc.gridwidth = 1;
29883143 gbc.anchor = GridBagConstraints.EAST;
29893144 gbc.fill = GridBagConstraints.BOTH;
2990- gbc.insets = new Insets(3, 3, 3, 3);
3145+ int gap = (int)(3 * scaleSupport.getManualScaleX());
3146+ gbc.insets = new Insets(gap, gap, gap, gap);
29913147 gbc.ipadx = 0;
29923148 gbc.ipady = 0;
29933149 JButton btnSelectAll = new JButton(actSelectAll);
@@ -3012,13 +3168,6 @@ class ImportPresetSelectPanel extends ImportWizardCardPanel {
30123168 }
30133169
30143170 @Override
3015- public void addNotify() {
3016- super.addNotify();
3017- ScaleSupport scaleSupport = ScaleSupport.getInstance(this);
3018- presetTableModel.adjustColumnModel(presetTable.getColumnModel(), scaleSupport.getManualScaleX());
3019- }
3020-
3021- @Override
30223171 public void onActive(ImportWizardDialog parent,
30233172 ImportWizardCardPanel previousPanel) {
30243173 this.parent= parent;
@@ -3123,8 +3272,12 @@ class ImportPresetSelectPanel extends ImportWizardCardPanel {
31233272 return defaultPartsSetId;
31243273 }
31253274
3126- public void initModel(Collection<PartsSet> partsSets, String defaultPartsSetId, CharacterData presetImportTarget) {
3275+ public void initModel(Collection<PartsSet> partsSets, String defaultPartsSetId, CharacterData presetImportTarget,
3276+ boolean selectAll) {
31273277 presetTableModel.initModel(partsSets, defaultPartsSetId, presetImportTarget);
3278+ if (selectAll) {
3279+ presetTableModel.selectAll();
3280+ }
31283281 }
31293282 }
31303283
@@ -3328,7 +3481,7 @@ class ImportPresetTableModel extends AbstractTableModelWithComboBoxModel<ImportP
33283481 public void adjustColumnModel(TableColumnModel columnModel, double scale) {
33293482 int mx = columnModel.getColumnCount();
33303483 for (int idx = 0; idx < mx; idx++) {
3331- columnModel.getColumn(idx).setWidth((int)(COLUMN_WIDTHS[idx] * scale));
3484+ columnModel.getColumn(idx).setPreferredWidth((int)(COLUMN_WIDTHS[idx] * scale));
33323485 }
33333486 }
33343487
--- a/src/main/java/charactermanaj/ui/MainFrame.java
+++ b/src/main/java/charactermanaj/ui/MainFrame.java
@@ -43,16 +43,19 @@ import java.util.logging.Logger;
4343
4444 import javax.swing.Box;
4545 import javax.swing.BoxLayout;
46+import javax.swing.ButtonGroup;
4647 import javax.swing.JCheckBox;
4748 import javax.swing.JCheckBoxMenuItem;
4849 import javax.swing.JColorChooser;
4950 import javax.swing.JFrame;
51+import javax.swing.JLabel;
5052 import javax.swing.JMenu;
5153 import javax.swing.JMenuBar;
5254 import javax.swing.JMenuItem;
5355 import javax.swing.JOptionPane;
5456 import javax.swing.JPanel;
5557 import javax.swing.JPopupMenu;
58+import javax.swing.JRadioButton;
5659 import javax.swing.JScrollBar;
5760 import javax.swing.JScrollPane;
5861 import javax.swing.JSeparator;
@@ -95,6 +98,7 @@ import charactermanaj.model.PartsIdentifier;
9598 import charactermanaj.model.PartsSet;
9699 import charactermanaj.model.RecommendationURL;
97100 import charactermanaj.model.WorkingSet;
101+import charactermanaj.model.io.CharacterDataFileReaderWriterFactory;
98102 import charactermanaj.model.io.CharacterDataPersistent;
99103 import charactermanaj.model.io.CustomLayerOrderPersist;
100104 import charactermanaj.model.io.CustomLayerOrderPersist.CustomLayerOrderPersistListener;
@@ -129,6 +133,8 @@ import charactermanaj.ui.util.FileDropTarget;
129133 import charactermanaj.ui.util.ScaleSupport;
130134 import charactermanaj.ui.util.WindowAdjustLocationSupport;
131135 import charactermanaj.util.DesktopUtilities;
136+import charactermanaj.util.DownloadUtils;
137+import charactermanaj.util.DownloadUtils.HeadResponse;
132138 import charactermanaj.util.ErrorMessageHelper;
133139 import charactermanaj.util.LocalizedResourcePropertyLoader;
134140 import charactermanaj.util.SystemUtil;
@@ -285,6 +291,11 @@ public class MainFrame extends JFrame
285291 private final ActiveCustomLayerPatternMgr customLayerPatternMgr = new ActiveCustomLayerPatternMgr();
286292
287293 /**
294+ * ダウンロード不要フラグ
295+ */
296+ private boolean noNeedDataDownload;
297+
298+ /**
288299 * アクティブなメインフレームを設定する.
289300 *
290301 * @param mainFrame
@@ -457,6 +468,18 @@ public class MainFrame extends JFrame
457468 // アプリケーション設定の変更で画面の再表示を試行する
458469 AppConfig.getInstance().addPropertyChangeListener(appConfigChangeListener);
459470
471+ // パーツがひとつも登録されていない場合
472+ // (ただし、すでにダウンロード不要フラグを設定してあればスキップする)
473+ // (データ構造変更時の再ロードでは、この処理は行わない。一時的にパーツ数が0になるため)
474+ if (characterData.getPartsCount() == 0 && !noNeedDataDownload) {
475+ SwingUtilities.invokeLater(new Runnable() {
476+ @Override
477+ public void run() {
478+ confirmDefaultCharacterDataDownload();
479+ }
480+ });
481+ }
482+
460483 } catch (RuntimeException ex) {
461484 logger.log(Level.SEVERE, "メインフレームの構築中に予期せぬ例外が発生しました。", ex);
462485 dispose(); // コンストラクタが呼ばれた時点でJFrameは構築済みなのでdisposeの必要がある.
@@ -875,12 +898,19 @@ public class MainFrame extends JFrame
875898 if (dropFiles == null || dropFiles.isEmpty()) {
876899 return;
877900 }
901+ final File dropFile = dropFiles.get(0);
902+
878903 // インポートダイアログを開く.
879904 // ドロップソースの処理がブロッキングしないように、
880905 // ドロップハンドラの処理を終了してからインポートダイアログが開くようにする.
881906 SwingUtilities.invokeLater(new Runnable() {
882907 public void run() {
883- onImport(dropFiles);
908+ onImport(new ImportSourceCallback() {
909+ @Override
910+ public void onInit(ImportWizardDialog dlg) {
911+ dlg.initSelectFile(dropFile);
912+ }
913+ });
884914 }
885915 });
886916 }
@@ -924,6 +954,94 @@ public class MainFrame extends JFrame
924954 }
925955
926956 /**
957+ * おすすめURLの表示名がアスタリスクで始まっている、urlがzipの最初の定義を
958+ * デフォルトのキャラクターセットのダウンロードurlとしてインポートするか問い合わせる
959+ */
960+ private void confirmDefaultCharacterDataDownload() {
961+ List<RecommendationURL> recommendations = characterData.getRecommendationURLList();
962+ List<RecommendationURL> downloadUrls = new ArrayList<RecommendationURL>();
963+ if (recommendations != null) {
964+ CharacterDataFileReaderWriterFactory archiveRdWrFactory =
965+ CharacterDataFileReaderWriterFactory.getInstance();
966+ for (RecommendationURL recommendation : recommendations) {
967+ String name = recommendation.getDisplayName();
968+ String url = recommendation.getUrl();
969+ if (name.startsWith("*") && archiveRdWrFactory.isSupportedFile(url)) {
970+ downloadUrls.add(recommendation);
971+ }
972+ }
973+ }
974+ if (downloadUrls.isEmpty()) {
975+ // デフォルトのキャラクターデータが定義されていない場合
976+ return;
977+ }
978+
979+ Properties strings = LocalizedResourcePropertyLoader.getCachedInstance()
980+ .getLocalizedProperties(STRINGS_RESOURCE);
981+
982+ Box box = Box.createVerticalBox();
983+ box.add(new JLabel(strings.getProperty("defaultdatadownload.confirm.message")));
984+
985+ ButtonGroup grp = new ButtonGroup();
986+ for (RecommendationURL recommendation : downloadUrls) {
987+ String name = recommendation.getDisplayName();
988+ String url = recommendation.getUrl();
989+ if (name.startsWith("*")) {
990+ name = name.substring(1);
991+ }
992+ JRadioButton radio = new JRadioButton(name);
993+ radio.setToolTipText(url);
994+ radio.setActionCommand(url);
995+ if (grp.getButtonCount() == 0) {
996+ // 最初のアイテムを選択状態にする
997+ radio.setSelected(true);
998+ }
999+ grp.add(radio);
1000+ box.add(radio);
1001+ }
1002+
1003+ JRadioButton chkNoAskAgain = new JRadioButton(strings.getProperty("noDownloadAndDoNotAskAgain"));
1004+ grp.add(chkNoAskAgain);
1005+ box.add(chkNoAskAgain);
1006+
1007+ int ret = JOptionPane.showConfirmDialog(MainFrame.this, box,
1008+ strings.getProperty("defaultdatadownload.confirm.title"), JOptionPane.YES_NO_OPTION);
1009+ if (ret != JOptionPane.YES_OPTION) {
1010+ return;
1011+ }
1012+
1013+ if (chkNoAskAgain.isSelected()) {
1014+ // ダウンロードしないし、今後、この問い合わせは不要
1015+ noNeedDataDownload = true;
1016+ return;
1017+ }
1018+
1019+ // リダイレクト先の実際のURLを取得する
1020+ String downloadUrl = grp.getSelection().getActionCommand();
1021+ final String actualDownloadUrl;
1022+ try {
1023+ AppConfig appConfig = AppConfig.getInstance();
1024+ DownloadUtils downloader = new DownloadUtils();
1025+ downloader.setImpersonateUserAgent(appConfig.getImpersonateUserAgent());
1026+ HeadResponse response = downloader.getHead(downloadUrl);
1027+ actualDownloadUrl = response.getLocation();
1028+
1029+ } catch (Exception ex) {
1030+ // アクセスできなかった場合はエラーを表示してインポートはしない
1031+ ErrorMessageHelper.showErrorDialog(this, ex);
1032+ return;
1033+ }
1034+
1035+ // リダイレクト先URLをインポートダイアログに表示する
1036+ onImport(new ImportSourceCallback() {
1037+ @Override
1038+ public void onInit(ImportWizardDialog dlg) {
1039+ dlg.initSelectURL(actualDownloadUrl);
1040+ }
1041+ });
1042+ }
1043+
1044+ /**
9271045 * パーツが変更されたことを検知した場合.<br>
9281046 * パーツデータをリロードし、各カテゴリのパーツ一覧を再表示させ、プレビューを更新する.<br>
9291047 */
@@ -1395,6 +1513,10 @@ public class MainFrame extends JFrame
13951513 for (RecommendationURL recommendation : recommendations) {
13961514 String displayName = recommendation.getDisplayName();
13971515 String url = recommendation.getUrl();
1516+ if (displayName.startsWith("*") && url.endsWith(".zip")) {
1517+ // デフォルトのデータダウンロードURLはメニューには表示しない
1518+ continue;
1519+ }
13981520
13991521 JMenuItem mnuItem = menuBuilder.createJMenuItem();
14001522 mnuItem.setText(displayName);
@@ -2027,7 +2149,7 @@ public class MainFrame extends JFrame
20272149
20282150 try {
20292151 // インポートウィザードの実行(新規モード)
2030- ImportWizardDialog importWizDialog = new ImportWizardDialog(this, null, null);
2152+ ImportWizardDialog importWizDialog = new ImportWizardDialog(this, null);
20312153 importWizDialog.setVisible(true);
20322154 int exitCode = importWizDialog.getExitCode();
20332155 if (exitCode == ImportWizardDialog.EXIT_PROFILE_CREATED) {
@@ -2050,15 +2172,20 @@ public class MainFrame extends JFrame
20502172 }
20512173 }
20522174
2175+ private interface ImportSourceCallback {
2176+
2177+ void onInit(ImportWizardDialog dlg);
2178+ }
2179+
20532180 /**
20542181 * 現在のプロファイルに対するインポートウィザードを実行する.<br>
20552182 * インポートが実行された場合は、パーツをリロードする.<br>
20562183 * インポートウィザード表示中は監視スレッドは停止される.<br>
20572184 *
2058- * @param initFile
2059- * アーカイブファィルまたはディレクトリ、指定がなければnull
2185+ * @param initCallback
2186+ * アーカイブファィルまたはディレクトリを初期設定するコールバック
20602187 */
2061- protected void onImport(List<File> initFiles) {
2188+ protected void onImport(ImportSourceCallback initCallback) {
20622189 if (!characterData.isValid()) {
20632190 Toolkit tk = Toolkit.getDefaultToolkit();
20642191 tk.beep();
@@ -2069,7 +2196,10 @@ public class MainFrame extends JFrame
20692196 watchAgent.suspend();
20702197 try {
20712198 // インポートウィザードの実行
2072- ImportWizardDialog importWizDialog = new ImportWizardDialog(this, characterData, initFiles);
2199+ ImportWizardDialog importWizDialog = new ImportWizardDialog(this, characterData);
2200+ if (initCallback != null) {
2201+ initCallback.onInit(importWizDialog);
2202+ }
20732203 importWizDialog.setVisible(true);
20742204
20752205 if (importWizDialog.getExitCode() == ImportWizardDialog.EXIT_PROFILE_UPDATED) {
@@ -2272,6 +2402,8 @@ public class MainFrame extends JFrame
22722402 Rectangle windowRect = new Rectangle(windowPos, windowSize);
22732403 workingSet.setWindowRect(windowRect);
22742404
2405+ workingSet.setNoNeedDataDownload(noNeedDataDownload);
2406+
22752407 // XML形式でのワーキングセットの保存
22762408 WorkingSetPersist workingSetPersist = WorkingSetPersist
22772409 .getInstance();
@@ -2387,6 +2519,9 @@ public class MainFrame extends JFrame
23872519 }
23882520 }
23892521
2522+ // ダウンロード不要フラグ
2523+ noNeedDataDownload = workingSet.isNoNeedDataDownload();
2524+
23902525 return true;
23912526
23922527 } catch (Exception ex) {
--- a/src/main/java/charactermanaj/ui/PartsRandomChooserDialog.java
+++ b/src/main/java/charactermanaj/ui/PartsRandomChooserDialog.java
@@ -47,6 +47,7 @@ import charactermanaj.model.CharacterData;
4747 import charactermanaj.model.PartsCategory;
4848 import charactermanaj.model.PartsIdentifier;
4949 import charactermanaj.model.PartsSet;
50+import charactermanaj.ui.util.ScaleSupport;
5051 import charactermanaj.util.LocalizedResourcePropertyLoader;
5152
5253 /**
@@ -195,6 +196,8 @@ public class PartsRandomChooserDialog extends JDialog {
195196
196197 setTitle(strings.getProperty("partsRandomChooser"));
197198
199+ ScaleSupport scaleSupport = ScaleSupport.getInstance(this);
200+
198201 Container contentPane = getContentPane();
199202 contentPane.setLayout(new BorderLayout());
200203
@@ -232,7 +235,8 @@ public class PartsRandomChooserDialog extends JDialog {
232235 int idx = centerPnl.getComponentCount();
233236 RandomChooserPanel pnl = addPartsChooserPanel(centerPnl,
234237 idx, category, lastInCategory,
235- changePartsIdentifierListener);
238+ changePartsIdentifierListener,
239+ scaleSupport);
236240
237241 // 未選択の場合、もしくは複数選択カテゴリの場合はランダムはディセーブルとする
238242 pnl.setEnableRandom(enable
@@ -290,7 +294,8 @@ public class PartsRandomChooserDialog extends JDialog {
290294 JButton btnBack = new JButton(actBack);
291295
292296 Box btnPanel = Box.createHorizontalBox();
293- btnPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 42));
297+ int mergin = (int)(5 * scaleSupport.getManualScaleX());
298+ btnPanel.setBorder(BorderFactory.createEmptyBorder(mergin, mergin, mergin, mergin * 8)); // 5, 5, 5, 40
294299
295300 btnPanel.add(btnRandomAll);
296301 btnPanel.add(btnBack);
@@ -493,16 +498,18 @@ public class PartsRandomChooserDialog extends JDialog {
493498 private JToggleButton btnReject;
494499
495500 public RandomChooserPanel(final PartsCategory category,
496- final boolean lastInCategory) {
501+ final boolean lastInCategory,
502+ final ScaleSupport scaleSupport) {
497503 Properties strings = LocalizedResourcePropertyLoader
498504 .getCachedInstance().getLocalizedProperties(
499505 STRINGS_RESOURCE);
500506
507+ int gap = (int)(3 * scaleSupport.getManualScaleX());
501508 setBorder(BorderFactory.createCompoundBorder(
502- BorderFactory.createEmptyBorder(3, 3, 3, 3),
509+ BorderFactory.createEmptyBorder(gap, gap, gap, gap),
503510 BorderFactory.createCompoundBorder(
504511 BorderFactory.createEtchedBorder(),
505- BorderFactory.createEmptyBorder(3, 3, 3, 3))));
512+ BorderFactory.createEmptyBorder(gap, gap, gap, gap))));
506513 setLayout(new GridBagLayout());
507514
508515 GridBagConstraints gbc = new GridBagConstraints();
@@ -696,9 +703,10 @@ public class PartsRandomChooserDialog extends JDialog {
696703 final int addPos,
697704 final PartsCategory category,
698705 final boolean lastInCategory,
699- final ActionListener changePartsIdentifierListener) {
706+ final ActionListener changePartsIdentifierListener,
707+ final ScaleSupport scaleSupport) {
700708 RandomChooserPanel pnl = new RandomChooserPanel(category,
701- lastInCategory) {
709+ lastInCategory, scaleSupport) {
702710 private static final long serialVersionUID = 1L;
703711
704712 @Override
@@ -709,7 +717,8 @@ public class PartsRandomChooserDialog extends JDialog {
709717 if (comp.equals(this)) {
710718 // 同じカテゴリのものを追加する
711719 addPartsChooserPanel(centerPnl, idx + 1, category,
712- lastInCategory, changePartsIdentifierListener);
720+ lastInCategory, changePartsIdentifierListener,
721+ scaleSupport);
713722 centerPnl.validate();
714723 // Addボタンを非表示にする.
715724 ((JButton) e.getSource()).setVisible(false);
--- a/src/main/java/charactermanaj/ui/ProfileListManager.java
+++ b/src/main/java/charactermanaj/ui/ProfileListManager.java
@@ -406,29 +406,46 @@ public final class ProfileListManager {
406406 * @param characterData
407407 */
408408 public static void prepare(CharacterData characterData) {
409- if (characterData.isEnableCustonLayerPattern()) {
410- CustomLayerOrderPersist customLayerOrderPersist = CustomLayerOrderPersist.newInstance(characterData);
411- if (!customLayerOrderPersist.exist()) {
412- // まだカスタムレイヤーが登録されていない場合(空ファイルの登録は無視する)
413- // カスタムレイヤーをまだ使ったことがないキャラクターデータを最初に開いた場合
414- String structureSig = characterData.toStructureString();
415-
416- // 既定は空のパターン
417- Map<CustomLayerOrderKey, List<CustomLayerOrder>> map = Collections.emptyMap();
409+ if (characterData.isEnableCustonLayerPattern() ||
410+ characterData.getRecommendationURLList() == null) {
411+ String structureSig = characterData.toStructureString();
412+ CharacterDataDefaultProvider defProv = new CharacterDataDefaultProvider();
413+ CharacterData v3 = defProv.createDefaultCharacterData(DefaultCharacterDataVersion.V3);
414+
415+ if (characterData.isEnableCustonLayerPattern()) {
416+ CustomLayerOrderPersist customLayerOrderPersist = CustomLayerOrderPersist.newInstance(characterData);
417+ if (!customLayerOrderPersist.exist()) {
418+ // まだカスタムレイヤーが登録されていない場合(空ファイルの登録は無視する)
419+ // カスタムレイヤーをまだ使ったことがないキャラクターデータを最初に開いた場合
420+
421+ // 既定は空のパターン
422+ Map<CustomLayerOrderKey, List<CustomLayerOrder>> map = Collections.emptyMap();
423+
424+ if (v3.toStructureString().equals(structureSig)) {
425+ // デフォルトのキャラクターセット(v3)と同一構造であれば、
426+ // V3デフォルト用のカスタムレイヤー定義をセットする。
427+ map = defProv.createDefaultCustomLayerOrderMap(characterData, DefaultCharacterDataVersion.V3);
428+ }
418429
419- CharacterDataDefaultProvider defProv = new CharacterDataDefaultProvider();
420- CharacterData v3 = defProv.createDefaultCharacterData(DefaultCharacterDataVersion.V3);
421- if (v3.toStructureString().equals(structureSig)) {
422- // デフォルトのキャラクターセット(v3)と同一構造であれば、
423- // V3デフォルト用のカスタムレイヤー定義をセットする。
424- map = defProv.createDefaultCustomLayerOrderMap(characterData, DefaultCharacterDataVersion.V3);
430+ // カスタムレイヤーパターンをファイルに保存する
431+ try {
432+ customLayerOrderPersist.save(map);
433+ } catch (Exception ex) {
434+ logger.log(Level.WARNING, "failed to save the custom layer mapping.", ex);
435+ }
425436 }
437+ }
426438
427- // カスタムレイヤーパターンをファイルに保存する
428- try {
429- customLayerOrderPersist.save(map);
430- } catch (Exception ex) {
431- logger.log(Level.WARNING, "failed to save the custom layer mapping.", ex);
439+ // お薦めリンクがnullの場合
440+ // (旧形式、またはお薦めリンクの定義がデフォルト定義と同一である場合はnullになる。)
441+ // rev:c587663f3dda3a4a874ef6810a336126f07d482c まではMainFrameのお薦めリンクメニュー構築時に補完していた。
442+ // キャラクターデータのダウンロード問い合わせ対応のため、ここで先に補完しておく。
443+ if (characterData.getRecommendationURLList() == null) {
444+ CharacterData v2 = defProv.createDefaultCharacterData(DefaultCharacterDataVersion.V3);
445+ if (v2.toStructureString().equals(structureSig) || v3.toStructureString().equals(structureSig)) {
446+ // デフォルトのキャラクターセット(v2, v3)と同一構造であれば、デフォルトで補完する
447+ final CharacterDataPersistent persistent = CharacterDataPersistent.getInstance();
448+ persistent.compensateRecommendationList(characterData);
432449 }
433450 }
434451 }
--- a/src/main/java/charactermanaj/ui/ProfileSelectorDialog.java
+++ b/src/main/java/charactermanaj/ui/ProfileSelectorDialog.java
@@ -517,8 +517,9 @@ public class ProfileSelectorDialog extends JDialog {
517517
518518 JSplitPane centerPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, true);
519519 centerPane.setResizeWeight(1.f); // ウィンドウサイズ変更時に上を可変とする.
520- centerPane.setDividerLocation(Integer.parseInt(strings
521- .getProperty("dividerLocation")));
520+ int divLocation = Integer.parseInt(strings.getProperty("dividerLocation"));
521+ divLocation = (int)(scaleSupport.getManualScaleY() * divLocation);
522+ centerPane.setDividerLocation(divLocation);
522523
523524 centerPane.add(pnlProfilesGroup);
524525 centerPane.add(infoPanel);
--- a/src/main/java/charactermanaj/ui/SelectCharatersDirDialog.java
+++ b/src/main/java/charactermanaj/ui/SelectCharatersDirDialog.java
@@ -1,6 +1,5 @@
11 package charactermanaj.ui;
22
3-import java.awt.Container;
43 import java.awt.Dimension;
54 import java.awt.Font;
65 import java.awt.GraphicsEnvironment;
@@ -211,11 +210,13 @@ public class SelectCharatersDirDialog extends JDialog {
211210 btnCancel.addFocusListener(focusAdapter);
212211 btnBroseForDir.addFocusListener(focusAdapter);
213212
214- Container contentPane = getContentPane();
215- contentPane.setLayout(new BoxLayout(contentPane, BoxLayout.Y_AXIS));
216-
217213 ScaleSupport scaleSupport = ScaleSupport.getInstance(this);
218214
215+ JPanel contentPane = (JPanel) getContentPane();
216+ int borderSize = (int)(5 * scaleSupport.getManualScaleX());
217+ contentPane.setBorder(BorderFactory.createEmptyBorder(0, borderSize, borderSize, borderSize));
218+ contentPane.setLayout(new BoxLayout(contentPane, BoxLayout.Y_AXIS));
219+
219220 {
220221 JLabel lbl = new JLabel(strings.getProperty("caption"));
221222 lbl.setFont(lbl.getFont().deriveFont(Font.BOLD));
@@ -274,6 +275,14 @@ public class SelectCharatersDirDialog extends JDialog {
274275 btnsBox.add(btnRemoveWorkingSets);
275276 btnsBox.add(Box.createHorizontalGlue());
276277
278+ // OK, CANCELボタンのサイズを合わせる
279+ Dimension dim = btnOK.getPreferredSize();
280+ int btnWidth = Math.max(btnOK.getPreferredSize().width,
281+ btnCancel.getPreferredSize().width);
282+ dim.setSize(btnWidth, dim.height);
283+ btnOK.setPreferredSize(dim);
284+ btnCancel.setPreferredSize(dim);
285+
277286 if (!Main.isLinuxOrMacOSX()) {
278287 btnsBox.add(btnOK);
279288 btnsBox.add(btnCancel);
--- a/src/main/java/charactermanaj/ui/progress/WorkerWithProgessDialog.java
+++ b/src/main/java/charactermanaj/ui/progress/WorkerWithProgessDialog.java
@@ -35,32 +35,32 @@ public class WorkerWithProgessDialog<T> extends JDialog {
3535 * ワーカースレッドが停止したことを示すフラグ
3636 */
3737 private volatile boolean exitThread;
38-
38+
3939 /**
4040 * ワーカースレッドの戻り値
4141 */
4242 private volatile T result;
43-
43+
4444 /**
4545 * ワーカースレッドが例外により終了した場合の例外
4646 */
4747 private volatile Throwable occuredException;
48-
48+
4949 /**
5050 * ワーカースレッド
5151 */
5252 private Thread thread;
53-
53+
5454 /**
5555 * ワーカースレッドの状態を監視しプログレスに反映させるタイマー
5656 */
5757 private Timer timer;
58-
58+
5959 /**
6060 * プログレスの更新頻度(タイマーのインターバル)
6161 */
6262 private static int interval = 200;
63-
63+
6464
6565 /**
6666 * 親フレームとワーカーを指定して構築する.<br>
@@ -73,15 +73,15 @@ public class WorkerWithProgessDialog<T> extends JDialog {
7373 if (worker == null) {
7474 throw new IllegalArgumentException();
7575 }
76-
76+
7777 initComponent(parent, worker);
78-
78+
7979 } catch (RuntimeException ex) {
8080 dispose();
8181 throw ex;
8282 }
8383 }
84-
84+
8585 /**
8686 * 親ダイアログとワーカーを指定して構築する.<br>
8787 * @param parent 親フレーム
@@ -93,9 +93,9 @@ public class WorkerWithProgessDialog<T> extends JDialog {
9393 if (worker == null) {
9494 throw new IllegalArgumentException();
9595 }
96-
96+
9797 initComponent(parent, worker);
98-
98+
9999 } catch (RuntimeException ex) {
100100 dispose();
101101 throw ex;
@@ -122,10 +122,10 @@ public class WorkerWithProgessDialog<T> extends JDialog {
122122 final JProgressBar progressBar = new JProgressBar();
123123 progressBar.setIndeterminate(true);
124124 progressBar.setStringPainted(false);
125-
125+
126126 container.add(progressBar, BorderLayout.SOUTH);
127-
128- // デフォルトのラベル表示
127+
128+ // デフォルトのラベル表示
129129 String title = "please wait for a while.";
130130 final JLabel lblCaption = new JLabel(title);
131131 container.add(lblCaption, BorderLayout.NORTH);
@@ -144,7 +144,7 @@ public class WorkerWithProgessDialog<T> extends JDialog {
144144
145145 // パックする.
146146 pack();
147-
147+
148148 // 親の中央に表示
149149 setLocationRelativeTo(parent);
150150
@@ -178,11 +178,11 @@ public class WorkerWithProgessDialog<T> extends JDialog {
178178 }
179179 });
180180 }
181-
181+
182182 super.flush();
183183 }
184184 };
185-
185+
186186 // プログレスダイアログに状態を反映させるためのタイマー
187187 timer = new Timer(interval, new ActionListener() {
188188 public void actionPerformed(ActionEvent e) {
@@ -210,7 +210,7 @@ public class WorkerWithProgessDialog<T> extends JDialog {
210210 }
211211 });
212212 }
213-
213+
214214 /**
215215 * プログレスの表示間隔を取得する
216216 * @return 表示間隔
@@ -239,7 +239,7 @@ public class WorkerWithProgessDialog<T> extends JDialog {
239239 public void run() {
240240 try {
241241 try {
242- worker.doWork(progressHandle);
242+ result = worker.doWork(progressHandle);
243243
244244 } catch (Throwable ex) {
245245 occuredException = ex;
@@ -251,7 +251,7 @@ public class WorkerWithProgessDialog<T> extends JDialog {
251251 }
252252 };
253253 }
254-
254+
255255 /**
256256 * ワーカースレッドより、スレッドが終了したことを通知される.<br>
257257 * ワーカースレッド自身か、ワーカースレッドの例外ハンドラか、
@@ -269,18 +269,18 @@ public class WorkerWithProgessDialog<T> extends JDialog {
269269 }
270270 });
271271 }
272-
272+
273273 /**
274274 * ワーカースレッドを開始し、プログレスダイアログを表示し、
275275 * ワーカースレッドの完了まで待機する.<br>
276276 * @throws WorkerException ワーカースレッドが例外により終了した場合
277277 */
278- public void startAndWait() throws WorkerException {
278+ public T startAndWait() throws WorkerException {
279279 // 初期化
280280 result = null;
281281 occuredException = null;
282282 exitThread = false;
283-
283+
284284 // ワーカースレッドの開始
285285 thread.start();
286286 try {
@@ -316,6 +316,8 @@ public class WorkerWithProgessDialog<T> extends JDialog {
316316 occuredException
317317 );
318318 }
319+
320+ return getResult();
319321 }
320322
321323 /**
--- /dev/null
+++ b/src/main/java/charactermanaj/util/DownloadUtils.java
@@ -0,0 +1,401 @@
1+package charactermanaj.util;
2+
3+import java.io.BufferedOutputStream;
4+import java.io.File;
5+import java.io.FileNotFoundException;
6+import java.io.FileOutputStream;
7+import java.io.IOException;
8+import java.io.InputStream;
9+import java.io.OutputStream;
10+import java.io.UnsupportedEncodingException;
11+import java.net.HttpURLConnection;
12+import java.net.URL;
13+import java.net.URLDecoder;
14+import java.util.ArrayList;
15+import java.util.List;
16+import java.util.Map;
17+import java.util.TreeMap;
18+import java.util.logging.Level;
19+import java.util.logging.Logger;
20+
21+/**
22+ * ダウンロードをサポートする
23+ */
24+public final class DownloadUtils {
25+
26+ /**
27+ * ロガー
28+ */
29+ private static final Logger logger = Logger.getLogger(DownloadUtils.class.getName());
30+
31+ /**
32+ * 偽装するユーザーエージェント名(nullまたは空文字の場合は偽装しない)
33+ */
34+ private String impersonateUserAgent;
35+
36+ /**
37+ * 最大ホップ数
38+ */
39+ private int maxHop = 10;
40+
41+ /**
42+ * ダウンロードファイルを終了時に削除するか?
43+ */
44+ private boolean deleteDownloadFileOnExit = true;
45+
46+ public void setImpersonateUserAgent(String impersonateUserAgent) {
47+ this.impersonateUserAgent = impersonateUserAgent;
48+ }
49+
50+ public String getImpersonateUserAgent() {
51+ return impersonateUserAgent;
52+ }
53+
54+ public void setMaxHop(int maxHop) {
55+ this.maxHop = maxHop;
56+ }
57+
58+ public int getMaxHop() {
59+ return maxHop;
60+ }
61+
62+ public void setDeleteDownloadFileOnExit(boolean deleteOnExit) {
63+ deleteDownloadFileOnExit = deleteOnExit;
64+ }
65+
66+ public boolean isDeleteDownloadFileOnExit() {
67+ return deleteDownloadFileOnExit;
68+ }
69+
70+ /**
71+ * ヘッドレスポンス
72+ */
73+ public static final class HeadResponse {
74+
75+ String location;
76+
77+ String contentType;
78+
79+ String fileName;
80+
81+ public String getLocation() {
82+ return location;
83+ }
84+
85+ public String getContentType() {
86+ return contentType;
87+ }
88+
89+ public String getFileName() {
90+ return fileName;
91+ }
92+
93+ /**
94+ * ファイルの拡張子、なければ空。
95+ * 返される拡張子はドットを含む。
96+ * @return ドットで始まる拡張子、もしくは空
97+ */
98+ public String getDotExtension() {
99+ String name = fileName;
100+ int pos = name.lastIndexOf('/');
101+ if (pos >= 0) {
102+ name = name.substring(pos + 1);
103+ }
104+ pos = name.lastIndexOf('\\');
105+ if (pos >= 0) {
106+ name = name.substring(pos + 1);
107+ }
108+
109+ int extPos = name.lastIndexOf(".");
110+ String ext = "";
111+ if (extPos > 0) {
112+ // ドットから始まる拡張子に切り取る
113+ ext = name.substring(extPos).toLowerCase();
114+ }
115+ return ext;
116+ }
117+
118+ @Override
119+ public String toString() {
120+ return "location=" + location + ", contentType=" + contentType + ", fileName=" + fileName;
121+ }
122+ }
123+
124+ /**
125+ * 指定したURLのコンテンツをダウンロードする
126+ * @param location URL
127+ * @param os 出力先
128+ * @throws IOException 失敗した場合
129+ */
130+ public void loadContents(String location, OutputStream os) throws IOException {
131+ loadContents(getHead(location), os);
132+ }
133+
134+ public void loadContents(HeadResponse headResponse, OutputStream os) throws IOException {
135+ String realLoction = headResponse.getLocation();
136+
137+ URL url = new URL(realLoction);
138+ HttpURLConnection conn = (HttpURLConnection) url.openConnection();
139+ if (impersonateUserAgent != null && impersonateUserAgent.length() > 0) {
140+ conn.setRequestProperty("User-Agent", impersonateUserAgent);
141+ }
142+ conn.connect();
143+ try {
144+ int status = conn.getResponseCode();
145+ if (status == HttpURLConnection.HTTP_NOT_FOUND) { // 404
146+ throw new FileNotFoundException("Failed to load contents. status=" + status + ", url=" + url);
147+ }
148+ if (status < HttpURLConnection.HTTP_OK || status >= HttpURLConnection.HTTP_MULT_CHOICE) { // 200未満、300以上
149+ throw new IOException("Failed to load contents. status=" + status + ", url=" + url);
150+ }
151+
152+ byte[] buf = new byte[4096];
153+ InputStream is = conn.getInputStream();
154+ try {
155+ for (;;) {
156+ int rd = is.read(buf);
157+ if (rd < 0) {
158+ break;
159+ }
160+ os.write(buf, 0, rd);
161+ }
162+ os.flush();
163+ } finally {
164+ is.close();
165+ }
166+ } finally {
167+ conn.disconnect();
168+ }
169+ }
170+
171+ /**
172+ * テンポラリディレクトりにコンテンツをダウンロードする
173+ * @param headResponse
174+ * @return テンポラリファイル
175+ * @throws IOException
176+ */
177+ public File downloadTemporary(HeadResponse headResponse) throws IOException {
178+ String ext = headResponse.getDotExtension();
179+ if (ext == null || ext.length() == 0) {
180+ ext = ".tmp";
181+ }
182+ File tmpFile = File.createTempFile("cmj-", ext);
183+
184+ if (isDeleteDownloadFileOnExit()) {
185+ tmpFile.deleteOnExit(); // 終了時にファイルを消す。(気休め程度)
186+ }
187+
188+ logger.log(Level.INFO, "Create temporary file: " + tmpFile);
189+ try {
190+ OutputStream bos = new BufferedOutputStream(new FileOutputStream(tmpFile));
191+ try {
192+ loadContents(headResponse, bos);
193+
194+ } finally {
195+ bos.close();
196+ }
197+
198+ } catch (RuntimeException ex) {
199+ tmpFile.delete();
200+ logger.log(Level.INFO, "Delete temporary file: " + tmpFile);
201+ throw ex;
202+
203+ } catch (IOException ex) {
204+ logger.log(Level.INFO, "Delete temporary file: " + tmpFile);
205+ tmpFile.delete();
206+ throw ex;
207+ }
208+ return tmpFile;
209+ }
210+
211+ /**
212+ * URLを指定してリダイレクトがある場合はリダイレクトでなくなるまで探索した最後のURLを返す。
213+ * @param location 開始するURL
214+ * @return 探索されたURL
215+ * @throws IOException 読み込みに失敗した場合、もしくは最大ホップ数を超えた場合
216+ */
217+ public HeadResponse getHead(String location) throws IOException {
218+ String initLocation = location;
219+ int hopCount = 0;
220+ for (;;) {
221+ logger.log(Level.INFO, "Connect to " + location);
222+ URL url = new URL(location);
223+ HttpURLConnection conn = (HttpURLConnection) url.openConnection();
224+
225+ int status;
226+ conn.setRequestMethod("HEAD");
227+ conn.setInstanceFollowRedirects(false); // 自動リダイレクトはしない
228+ if (impersonateUserAgent != null && impersonateUserAgent.length() > 0) {
229+ conn.setRequestProperty("User-Agent", impersonateUserAgent);
230+ }
231+ conn.connect();
232+ try {
233+ status = conn.getResponseCode();
234+
235+ } finally {
236+ conn.disconnect();
237+ }
238+
239+ if (status == HttpURLConnection.HTTP_MOVED_TEMP || // 302
240+ status == HttpURLConnection.HTTP_MOVED_PERM || // 301
241+ status == HttpURLConnection.HTTP_SEE_OTHER) { // 303
242+ if (hopCount > maxHop) {
243+ // 転送回数が多すぎる!
244+ throw new IOException("too many hops! " + hopCount);
245+ }
246+ location = conn.getHeaderField("Location");
247+ if (location == null || location.isEmpty()) {
248+ // locationヘッダがない
249+ throw new IOException("bad response. location not found.");
250+ }
251+ hopCount++;
252+ logger.log(Level.INFO, "Location to " + location);
253+ continue;
254+ }
255+
256+ if (status >= HttpURLConnection.HTTP_OK && status < HttpURLConnection.HTTP_MULT_CHOICE) { // 200以上 300未満
257+ HeadResponse response = new HeadResponse();
258+ response.location = location;
259+ response.contentType = conn.getContentType();
260+
261+ String contentDisposition = conn.getHeaderField("Content-Disposition");
262+ String fileName = null;
263+ if (contentDisposition != null && contentDisposition.length() > 0) {
264+ fileName = parseAttachmentFileName(contentDisposition);
265+ }
266+ if (fileName == null || fileName.length() == 0) {
267+ fileName = initLocation; // ファイル名の指定がない場合は最初のロケーション名を使用する
268+ }
269+ response.fileName = fileName;
270+ logger.log(Level.INFO, "response success. " + response);
271+ return response;
272+ }
273+
274+ if (status == HttpURLConnection.HTTP_NOT_FOUND) { // 404
275+ // ファイルが見つからない場合
276+ throw new FileNotFoundException("Failed to load contents. status=" + status + ", url=" + url);
277+ }
278+
279+ // 何らかのエラー
280+ logger.log(Level.WARNING, "response failed. status=" + status);
281+ throw new IOException("response failed. status=" + status);
282+ }
283+ }
284+
285+ /**
286+ * セミコロンで行を区切る。
287+ * (ダブルクォートがある場合は、閉じられるまではセミコロンは無視する。)
288+ * @param line
289+ * @return
290+ */
291+ private static List<String> splitSemicolon(String line) {
292+ List<String> lines = new ArrayList<String>();
293+ StringBuilder buf = new StringBuilder();
294+ int mode = 0;
295+ for (char ch : line.toCharArray()) {
296+ if (mode == 0) {
297+ if (ch == '"') {
298+ // ダブルクォートがある場合は閉じるまでセミコロンを無視する
299+ buf.append((char) ch);
300+ mode = 1;
301+
302+ } else if (ch == ';') {
303+ lines.add(buf.toString());
304+ buf.setLength(0);
305+
306+ } else {
307+ buf.append((char) ch);
308+ }
309+ } else if (mode == 1) {
310+ if (ch == '"') {
311+ buf.append((char) ch);
312+ mode = 0;
313+
314+ } else {
315+ buf.append((char) ch);
316+ }
317+ }
318+ }
319+ if (buf.length() > 0) {
320+ lines.add(buf.toString());
321+ }
322+ return lines;
323+ }
324+
325+ /**
326+ * key=value形式の文字列のリストからマップを生成する。
327+ * valueがダブルクォートで囲まれている場合はダブルクォートを除去する。
328+ * @param lines
329+ * @return
330+ */
331+ private static Map<String, String> parseKeyValuePair(List<String> lines) {
332+ Map<String, String> keyValueMap = new TreeMap<String, String>(String.CASE_INSENSITIVE_ORDER);
333+ for (String line : lines) {
334+ line = line.trim();
335+ if (!line.isEmpty()) {
336+ int pos = line.indexOf("=");
337+ String key, value;
338+ if (pos >= 0) {
339+ key = line.substring(0, pos);
340+ value = line.substring(pos + 1);
341+ value = value.trim();
342+ if (value.startsWith("\"") && value.endsWith("\"")) {
343+ // ダブルクォートで囲まれている場合は外す
344+ value = value.substring(1, value.length() - 1);
345+ }
346+ } else {
347+ key = line;
348+ value = "";
349+ }
350+ keyValueMap.put(key, value);
351+ }
352+ }
353+ return keyValueMap;
354+ }
355+
356+ /**
357+ * Content-Dispositionのヘッダーパラメータからファイル名を取得する。
358+ * @param contentDisposition
359+ * @return
360+ */
361+ private static String parseAttachmentFileName(String contentDisposition) {
362+ List<String> lines = splitSemicolon(contentDisposition);
363+ logger.log(Level.FINE, "content-dispotion: " + lines);
364+ Map<String, String> kv = parseKeyValuePair(lines);
365+
366+ String fileName = null;
367+
368+ // 文字コードつきファイル名パラメータがあれば、それを解析・取得する
369+ String encodedFileName = kv.get("filename*");
370+ if (encodedFileName != null && encodedFileName.length() > 0) {
371+ // 文字コードの取得(空の場合もありえる)
372+ int pos = encodedFileName.indexOf('\'');
373+ String encoding = encodedFileName.substring(0, pos);
374+ if (encoding.isEmpty()) {
375+ encoding = "utf-8"; // UTF-8をデフォルトとみなす
376+ }
377+
378+ // 言語の取得(空の場合もありえる)
379+ int pos2 = encodedFileName.indexOf('\'', pos + 1);
380+ String language = encodedFileName.substring(pos + 1, pos2);
381+
382+ // ファイル名
383+ try {
384+ fileName = URLDecoder.decode(encodedFileName.substring(pos2 + 1), encoding);
385+
386+ } catch (UnsupportedEncodingException ex) {
387+ logger.log(Level.WARNING, "url encoding error: " + encodedFileName, ex);
388+ fileName = null;
389+ }
390+ logger.log(Level.INFO, "attachment filename*=" + encoding + "," + language + "," + fileName);
391+ }
392+
393+ // 文字コードつきファイル名がなければ、文字コードなしファイル名を取得する
394+ if (fileName == null || fileName.length() == 0) {
395+ fileName = kv.get("filename");
396+ logger.log(Level.INFO, "attachment filename=" + fileName);
397+ }
398+
399+ return fileName;
400+ }
401+}
--- a/src/main/resources/languages/appconfigdialog.xml
+++ b/src/main/resources/languages/appconfigdialog.xml
@@ -73,6 +73,8 @@ If the file already exists, the file is overwritten.]]></entry>
7373
7474 <entry key="jarTransferBufferSize">90;Jar File Buffer</entry>
7575 <entry key="fileTransferBufferSize">91;File Buffer</entry>
76+<entry key="impersonateUserAgent">92;Impersonation of user agent name for download</entry>
77+<entry key="deleteDownloadFileOnExit">93;Delete the downloaded file in temporary directory upon termination</entry>
7678
7779 <entry key="drawGridMask">A0;Draw grid on preview</entry>
7880 <entry key="previewGridColor">A1;Grid color (ARGB)</entry>
--- a/src/main/resources/languages/appconfigdialog_ja.xml
+++ b/src/main/resources/languages/appconfigdialog_ja.xml
@@ -73,6 +73,8 @@
7373
7474 <entry key="jarTransferBufferSize">90;JARファイル用バッファサイズ</entry>
7575 <entry key="fileTransferBufferSize">91;ファイル転送用バッファサイズ</entry>
76+<entry key="impersonateUserAgent">92;ダウンロード時に偽装するユーザーエージェント名</entry>
77+<entry key="deleteDownloadFileOnExit">93;終了時にテンポラリディレクトり上にダウンロードしたファイルを消す</entry>
7678
7779 <entry key="drawGridMask">A0;グリッドを描画する確認モードのビットマスク(0-3, 0は無効にする場合)</entry>
7880 <entry key="previewGridColor">A1;プレビュー画面のグリッドカラー(ARGB)</entry>
--- a/src/main/resources/languages/appconfigdialog_zh.xml
+++ b/src/main/resources/languages/appconfigdialog_zh.xml
@@ -72,6 +72,8 @@
7272
7373 <entry key="jarTransferBufferSize">90;Jar文件缓存</entry>
7474 <entry key="fileTransferBufferSize">91;文件缓存</entry>
75+<entry key="impersonateUserAgent">92;模拟用户代理名称以供下载</entry>
76+<entry key="deleteDownloadFileOnExit">93;终止后,在临时目录中删除下载的文件</entry>
7577
7678 <entry key="drawGridMask">A0;预览时显示网格</entry>
7779 <entry key="previewGridColor">A1;网格颜色(ARGB)</entry>
--- a/src/main/resources/languages/importwizdialog.xml
+++ b/src/main/resources/languages/importwizdialog.xml
@@ -10,9 +10,15 @@
1010 <entry key="browse">Browse</entry>
1111 <entry key="importingArchiveFile">Import archived File (zip, cmj)</entry>
1212 <entry key="importingDirectory">Import from Directory</entry>
13+ <entry key="importingURL">Import from URL</entry>
1314 <entry key="fileNotFound">File not found.</entry>
1415 <entry key="directoryNotFound">Directory not found.</entry>
1516
17+ <entry key="downloading.checkhead">checking...</entry>
18+ <entry key="downloading.waitForDownload">Downloading...</entry>
19+ <entry key="downloading.invalidFileType">Unsupported file type.</entry>
20+ <entry key="downloading.notFound">File not found.</entry>
21+
1622 <entry key="confirm.close">Are you sure you want to cancel?</entry>
1723 <entry key="confirm">Confirm</entry>
1824
@@ -28,12 +34,16 @@
2834 <entry key="description">Note</entry>
2935 <entry key="basic.sampleImage">Sample picture</entry>
3036 <entry key="appendDescription">Append this description to the profile.</entry>
31-
37+
3238 <entry key="noContents">No contents.</entry>
3339 <entry key="notFormalArchive">This is not a formal archive, but may be containing some picture.</entry>
3440 <entry key="unmatchedProfileId">Profile ID mismatch. id="{0}"</entry>
3541 <entry key="unmatchedProfileRev">Profile REV mismatch. rev="{0}"</entry>
3642
43+ <entry key="sizeOfProfile">Profile Size</entry>
44+ <entry key="widthOfProfile">Width: </entry>
45+ <entry key="heightOfProfile">Height: </entry>
46+
3747 <entry key="parts.title">Import parts</entry>
3848 <entry key="parts.btn.selectAll">select All</entry>
3949 <entry key="parts.btn.deselectAll">deselect All</entry>
@@ -75,8 +85,8 @@
7585 <entry key="preset.column.check.size">50</entry>
7686 <entry key="preset.column.name.size">100</entry>
7787 <entry key="preset.column.missings.size">200</entry>
78-
88+
7989 <entry key="complete">Complete</entry>
80-
90+
8191 </properties>
8292
--- a/src/main/resources/languages/importwizdialog_ja.xml
+++ b/src/main/resources/languages/importwizdialog_ja.xml
@@ -10,10 +10,16 @@
1010 <entry key="browse">参照...</entry>
1111 <entry key="importingArchiveFile">アーカイブファイル(zip,cmj)からインポート</entry>
1212 <entry key="importingDirectory">フォルダからインポート</entry>
13+ <entry key="importingURL">URLからダウンロードしてインポート</entry>
1314 <entry key="file">ファイル:</entry>
1415 <entry key="fileNotFound">ファイルがありません。</entry>
1516 <entry key="directoryNotFound">フォルダがありません。</entry>
1617
18+ <entry key="downloading.checkhead">確認中...</entry>
19+ <entry key="downloading.waitForDownload">ダウンロード中...</entry>
20+ <entry key="downloading.invalidFileType">サポートされていない形式です</entry>
21+ <entry key="downloading.notFound">ファイルがみつかりません</entry>
22+
1723 <entry key="confirm.close">キャンセルしますか?</entry>
1824 <entry key="confirm">確認</entry>
1925
@@ -35,12 +41,16 @@
3541 <entry key="unmatchedProfileId">プロファイルIDが一致しません。 id="{0}"</entry>
3642 <entry key="unmatchedProfileRev">プロファイルIDは一致しますが、リビジョンが一致しません。 rev="{0}"</entry>
3743
44+ <entry key="sizeOfProfile">プロファイルのサイズ</entry>
45+ <entry key="widthOfProfile">幅: </entry>
46+ <entry key="heightOfProfile">高さ: </entry>
47+
3848 <entry key="parts.title">インポートするパーツ</entry>
3949 <entry key="parts.btn.selectAll">全て選択</entry>
4050 <entry key="parts.btn.deselectAll">全て解除</entry>
4151 <entry key="parts.btn.sort">名前順で整列</entry>
4252 <entry key="parts.btn.sortByTimestamp">日付順で整列</entry>
43-
53+
4454 <entry key="parts.popup.check">チェックする</entry>
4555 <entry key="parts.popup.uncheck">チェックを外す</entry>
4656
@@ -67,7 +77,7 @@
6777 <entry key="parts.column.org-author.size">80</entry>
6878 <entry key="parts.column.version.size">50</entry>
6979 <entry key="parts.column.org-version.size">50</entry>
70-
80+
7181 <entry key="preset.title">インポートするお気に入り</entry>
7282 <entry key="preset.popup.selectUsedParts">使用しているパーツのインポート</entry>
7383 <entry key="preset.column.check">選択</entry>
@@ -78,6 +88,6 @@
7888 <entry key="preset.column.missings.size">200</entry>
7989
8090 <entry key="complete">インポートが完了しました。</entry>
81-
91+
8292 </properties>
8393
--- a/src/main/resources/languages/importwizdialog_zh.xml
+++ b/src/main/resources/languages/importwizdialog_zh.xml
@@ -10,9 +10,15 @@
1010 <entry key="browse">浏览</entry>
1111 <entry key="importingArchiveFile">导入压缩文件(zip,cmj)</entry>
1212 <entry key="importingDirectory">从文件夹导入</entry>
13+ <entry key="importingURL">通过指定URL导入</entry>
1314 <entry key="fileNotFound">找不到文件</entry>
1415 <entry key="directoryNotFound">找不到文件夹</entry>
1516
17+ <entry key="downloading.checkhead">检查...</entry>
18+ <entry key="downloading.waitForDownload">在下载...</entry>
19+ <entry key="downloading.invalidFileType">不支持的格式</entry>
20+ <entry key="downloading.notFound">找不到档案</entry>
21+
1622 <entry key="confirm.close">你确认要取消么?</entry>
1723 <entry key="confirm">确认</entry>
1824
@@ -75,8 +81,8 @@
7581 <entry key="preset.column.check.size">50</entry>
7682 <entry key="preset.column.name.size">100</entry>
7783 <entry key="preset.column.missings.size">200</entry>
78-
84+
7985 <entry key="complete">完成</entry>
80-
86+
8187 </properties>
8288
--- a/src/main/resources/languages/mainframe.xml
+++ b/src/main/resources/languages/mainframe.xml
@@ -15,4 +15,9 @@
1515 <entry key="help.reportbugs.url">http://osdn.net/projects/charactermanaj/ticket/</entry>
1616 <entry key="help.forum.description">Forum</entry>
1717 <entry key="help.forum.url">http://osdn.net/projects/charactermanaj/forums/</entry>
18+
19+ <entry key="defaultdatadownload.confirm.message">There is default character data. Do you want to download it?</entry>
20+ <entry key="defaultdatadownload.confirm.title">Default character data download</entry>
21+ <entry key="noDownloadAndDoNotAskAgain">I do not want to download, and do not ask me again.</entry>
1822 </properties>
23+
--- a/src/main/resources/languages/mainframe_ja.xml
+++ b/src/main/resources/languages/mainframe_ja.xml
@@ -15,4 +15,8 @@
1515 <entry key="help.reportbugs.url">http://osdn.net/projects/charactermanaj/ticket/</entry>
1616 <entry key="help.forum.description">フォーラムは以下のURLにあります。</entry>
1717 <entry key="help.forum.url">http://osdn.net/projects/charactermanaj/forums/</entry>
18+
19+ <entry key="defaultdatadownload.confirm.message">デフォルトのキャラクターデータがあります。ダウンロードしますか?</entry>
20+ <entry key="defaultdatadownload.confirm.title">キャラクターデータのダウンロード</entry>
21+ <entry key="noDownloadAndDoNotAskAgain">ダウンロードしない。今後の確認も不要である。</entry>
1822 </properties>
--- a/src/main/resources/languages/mainframe_zh.xml
+++ b/src/main/resources/languages/mainframe_zh.xml
@@ -15,4 +15,8 @@
1515 <entry key="help.reportbugs.url">http://osdn.net/projects/charactermanaj/ticket/</entry>
1616 <entry key="help.forum.description">论坛(日)</entry>
1717 <entry key="help.forum.url">http://osdn.net/projects/charactermanaj/forums/</entry>
18+
19+ <entry key="defaultdatadownload.confirm.message">有默认数据。你想下载吗?</entry>
20+ <entry key="defaultdatadownload.confirm.title">下载默认数据</entry>
21+ <entry key="noDownloadAndDoNotAskAgain">我不想下载,也不要再问我了</entry>
1822 </properties>
--- a/src/main/resources/template/character3.xml
+++ b/src/main/resources/template/character3.xml
@@ -313,6 +313,11 @@
313313 <description xml:lang="zh_TW">パーツ保管庫 (零件的保管庫)</description>
314314 <URL xml:lang="en">https://charactermanaj.osdn.jp/upload.html</URL>
315315 </recommendation>
316+ <recommendation>
317+ <description xml:lang="en">*Default PartsSet (K.Hmix 1st Edition)</description>
318+ <description xml:lang="ja">*キャラクターなんとか機のパーツデータをダウンロードする (K.Hmix 1st Edition)</description>
319+ <URL xml:lang="en">http://charactermanaj.osdn.jp/ext/default_characterset_v3.zip</URL>
320+ </recommendation>
316321 </recommendations>
317322
318323 </character>
Show on old repository browser