// MainDialog.cpp #include "pch.h" #include "AveryDennisonUtil.h" #include "MainDialog.h" #include "RenderScene.h" #include "PaperSizeDlg.h" // ===== バージョン情報ダイアログ ===== class CAboutDlg : public CDialog { public: CAboutDlg() : CDialog(IDD_ABOUTBOX) {} enum { IDD = IDD_ABOUTBOX }; protected: void DoDataExchange(CDataExchange* pDX) override { CDialog::DoDataExchange(pDX); } DECLARE_MESSAGE_MAP() }; BEGIN_MESSAGE_MAP(CAboutDlg, CDialog) END_MESSAGE_MAP() // 子キャンバスのウィンドウ ID(リソース上の IDC_CANVAS とは別) static const UINT kCanvasId = 1101; static LPCTSTR kFilter = _T("Otozai ラベル (*.otz)|*.otz|すべてのファイル (*.*)|*.*||"); BEGIN_MESSAGE_MAP(CMainDialog, CDialog) ON_COMMAND(ID_FILE_NEW, &CMainDialog::OnFileNew) ON_COMMAND(ID_FILE_OPEN, &CMainDialog::OnFileOpen) ON_COMMAND(ID_FILE_SAVE, &CMainDialog::OnFileSave) ON_COMMAND(ID_FILE_SAVE_AS, &CMainDialog::OnFileSaveAs) ON_COMMAND(ID_INSERT_TEXT, &CMainDialog::OnInsertText) ON_COMMAND(ID_INSERT_IMAGE, &CMainDialog::OnInsertImage) ON_COMMAND(ID_INSERT_RECT, &CMainDialog::OnInsertRect) ON_COMMAND(ID_INSERT_LINE, &CMainDialog::OnInsertLine) ON_COMMAND(ID_EDIT_PROPERTIES, &CMainDialog::OnEditProperties) ON_COMMAND(ID_EDIT_DELETE_ITEM, &CMainDialog::OnEditDeleteItem) ON_COMMAND(ID_PAPER_SIZE, &CMainDialog::OnPaperSize) ON_COMMAND(ID_VIEW_ZOOMIN, &CMainDialog::OnViewZoomIn) ON_COMMAND(ID_VIEW_ZOOMOUT, &CMainDialog::OnViewZoomOut) ON_COMMAND(ID_FILE_PRINT, &CMainDialog::OnFilePrint) ON_COMMAND(ID_APP_ABOUT, &CMainDialog::OnAppAbout) ON_COMMAND(ID_ITEM_COPY, &CMainDialog::OnItemCopy) ON_COMMAND(ID_ITEM_PASTE, &CMainDialog::OnItemPaste) ON_COMMAND(ID_ITEM_DUPLICATE, &CMainDialog::OnItemDuplicate) ON_COMMAND(ID_ITEM_FRONT, &CMainDialog::OnItemFront) ON_COMMAND(ID_ITEM_BACK, &CMainDialog::OnItemBack) ON_COMMAND(ID_ALIGN_MENU, &CMainDialog::OnAlignMenu) ON_COMMAND_RANGE(ID_ALIGN_LEFT, ID_ALIGN_BOTTOM, &CMainDialog::OnAlignCmd) ON_BN_CLICKED(IDC_CHECK_GRID, &CMainDialog::OnToggleGrid) ON_COMMAND(ID_TEMPLATE_LOAD, &CMainDialog::OnTemplateLoad) ON_COMMAND(ID_TEMPLATE_CLEAR, &CMainDialog::OnTemplateClear) ON_BN_CLICKED(IDC_CHECK_PRINT_TEMPLATE, &CMainDialog::OnTogglePrintTemplate) END_MESSAGE_MAP() // コンストラクタ。メインダイアログのリソース ID を指定して生成。 CMainDialog::CMainDialog(CWnd* pParent) : CDialog(IDD_MAIN_DIALOG, pParent) { } // コントロールと変数の関連付け(本ダイアログは直接の DDX 変数は持たない)。 void CMainDialog::DoDataExchange(CDataExchange* pDX) { CDialog::DoDataExchange(pDX); } // ダイアログ初期化。プレースホルダ位置に編集キャンバスを生成し、 // チェックボックスの初期状態をモデルへ合わせる。 BOOL CMainDialog::OnInitDialog() { CDialog::OnInitDialog(); SetIcon(AfxGetApp()->LoadStandardIcon(IDI_APPLICATION), TRUE); SetIcon(AfxGetApp()->LoadStandardIcon(IDI_APPLICATION), FALSE); // プレースホルダ(IDC_CANVAS)の矩形にキャンバスを生成 CWnd* pHolder = GetDlgItem(IDC_CANVAS); CRect rc; if (pHolder) { pHolder->GetWindowRect(rc); ScreenToClient(rc); pHolder->ShowWindow(SW_HIDE); } else { rc = CRect(80, 10, 460, 300); } m_canvas.SetLayout(&m_layout); m_canvas.Create(this, rc, kCanvasId); m_canvas.FitZoom(); // グリッド/スナップのチェック状態をキャンバスの既定に合わせる CButton* pGrid = (CButton*)GetDlgItem(IDC_CHECK_GRID); if (pGrid) pGrid->SetCheck(m_canvas.GetSnap() ? BST_CHECKED : BST_UNCHECKED); UpdateTemplateCheck(); UpdateCaption(); return TRUE; } // タイトルバーを「ファイル名 - AveryDennisonUtil」に更新する(未保存は「無題」)。 void CMainDialog::UpdateCaption() { CString name = m_path.IsEmpty() ? CString(_T("無題")) : m_path; if (!m_path.IsEmpty()) { int slash = m_path.ReverseFind(_T('\\')); if (slash >= 0) name = m_path.Mid(slash + 1); } CString title; title.Format(_T("%s - AveryDennisonUtil"), (LPCTSTR)name); SetWindowText(title); } // 未保存の変更がある場合に保存確認する。続行可なら TRUE、キャンセルなら FALSE。 // 新規/開く/終了の前に呼んで作業内容の消失を防ぐ。 BOOL CMainDialog::ConfirmDiscardIfDirty() { if (!m_canvas.IsDirty()) return TRUE; int r = AfxMessageBox(_T("変更を保存しますか?"), MB_YESNOCANCEL | MB_ICONQUESTION); if (r == IDCANCEL) return FALSE; if (r == IDYES) return DoSave(FALSE); return TRUE; } // Enter キーでダイアログが閉じないように無効化(編集中の誤終了防止)。 void CMainDialog::OnOK() { // Enter キーでダイアログを閉じない(編集を継続) } // 終了(×ボタン/Esc/「終了」)。未保存なら確認してから閉じる。 void CMainDialog::OnCancel() { if (!ConfirmDiscardIfDirty()) return; CDialog::OnCancel(); } // ===== ファイル ===== // 「新規」。未保存確認の後、レイアウトを初期化して表示を更新する。 void CMainDialog::OnFileNew() { if (!ConfirmDiscardIfDirty()) return; m_layout.Reset(); m_path.Empty(); m_canvas.OnLayoutReplaced(); m_canvas.FitZoom(); UpdateTemplateCheck(); UpdateCaption(); } // 「開く」。.otz を選んで読み込み、表示・チェック状態・タイトルを更新する。 void CMainDialog::OnFileOpen() { if (!ConfirmDiscardIfDirty()) return; CFileDialog dlg(TRUE, _T("otz"), nullptr, OFN_FILEMUSTEXIST | OFN_HIDEREADONLY, kFilter, this); if (dlg.DoModal() != IDOK) return; if (!m_layout.LoadFromFile(dlg.GetPathName())) { AfxMessageBox(_T("ファイルを開けませんでした。")); m_layout.Reset(); } m_path = dlg.GetPathName(); m_canvas.OnLayoutReplaced(); m_canvas.FitZoom(); UpdateTemplateCheck(); UpdateCaption(); } void CMainDialog::OnFileSave() { DoSave(FALSE); } // 「保存」(既存パスへ上書き) void CMainDialog::OnFileSaveAs(){ DoSave(TRUE); } // 「名前を付けて保存」 // 保存の実処理。bForceDialog=TRUE またはパス未設定のときは保存ダイアログを出す。 // 成功で TRUE。保存後はダーティ状態を解除しタイトルを更新する。 BOOL CMainDialog::DoSave(BOOL bForceDialog) { CString path = m_path; if (bForceDialog || path.IsEmpty()) { CFileDialog dlg(FALSE, _T("otz"), m_path.IsEmpty() ? _T("label.otz") : (LPCTSTR)m_path, OFN_OVERWRITEPROMPT | OFN_HIDEREADONLY, kFilter, this); if (dlg.DoModal() != IDOK) return FALSE; path = dlg.GetPathName(); } if (!m_layout.SaveToFile(path)) { AfxMessageBox(_T("保存に失敗しました。")); return FALSE; } m_path = path; m_canvas.SetDirty(FALSE); UpdateCaption(); return TRUE; } // ===== 挿入・編集(キャンバスへ委譲) ===== void CMainDialog::OnInsertText() { m_canvas.InsertText(); } void CMainDialog::OnInsertImage() { m_canvas.InsertImage(); } void CMainDialog::OnInsertRect() { m_canvas.InsertRect(); } void CMainDialog::OnInsertLine() { m_canvas.InsertLine(); } void CMainDialog::OnEditProperties() { m_canvas.EditSelectedProperties(); } void CMainDialog::OnEditDeleteItem() { m_canvas.DeleteSelected(); } void CMainDialog::OnViewZoomIn() { m_canvas.ZoomIn(); } void CMainDialog::OnViewZoomOut() { m_canvas.ZoomOut(); } // 「用紙サイズ」。現在値をダイアログに渡し、OK なら用紙寸法・DPI・余白を反映する。 void CMainDialog::OnPaperSize() { CPaperSizeDlg dlg(this); dlg.m_widthMm = m_layout.m_paperWidthHu / 100.0; dlg.m_heightMm = m_layout.m_paperHeightHu / 100.0; dlg.m_dpi = m_layout.m_dpi; dlg.m_marginMm = m_layout.m_marginHu / 100.0; if (dlg.DoModal() == IDOK) { m_layout.m_paperWidthHu = (int)(dlg.m_widthMm * 100 + 0.5); m_layout.m_paperHeightHu = (int)(dlg.m_heightMm * 100 + 0.5); m_layout.m_dpi = dlg.m_dpi; m_layout.m_marginHu = (int)(dlg.m_marginMm * 100 + 0.5); m_layout.SyncTemplateRect(); m_canvas.SetDirty(TRUE); m_canvas.FitZoom(); m_canvas.Invalidate(); } } // ===== 印刷(GDI 直接印刷) ===== // 「印刷」。プリンタ選択ダイアログ→プリンタ DC を取得し、画面と同じ // Render::DrawLayout で 1 ページ印刷する(StartDoc/StartPage/EndPage/EndDoc)。 void CMainDialog::OnFilePrint() { CPrintDialog pd(FALSE, PD_RETURNDC | PD_USEDEVMODECOPIES | PD_NOPAGENUMS | PD_NOSELECTION | PD_ALLPAGES, this); if (pd.DoModal() != IDOK) return; HDC hdc = pd.GetPrinterDC(); if (!hdc) { AfxMessageBox(_T("プリンタ DC を取得できませんでした。")); return; } CDC dc; dc.Attach(hdc); DOCINFO di; ZeroMemory(&di, sizeof(di)); di.cbSize = sizeof(di); di.lpszDocName = _T("AveryDennisonUtil"); if (dc.StartDoc(&di) > 0) { if (dc.StartPage() > 0) { int dpiX = dc.GetDeviceCaps(LOGPIXELSX); int dpiY = dc.GetDeviceCaps(LOGPIXELSY); // プリンタ DC の原点(0,0)は「印字可能領域」の左上。ラベル mm(0,0) を // そこに合わせる(印字可能領域の考慮)。物理マージン分を引くと逆に // 非印字領域へはみ出して切れるため、オフセットは加えない。 CoordMap map(dpiX, dpiY, 1.0, 0, 0); // 実 DPI・原点(0,0)・等倍 Render::DrawLayout(dc, m_layout, map, /*screen=*/false, nullptr); dc.EndPage(); } dc.EndDoc(); } dc.Detach(); ::DeleteDC(hdc); } // 「バージョン情報」ダイアログを表示する。 void CMainDialog::OnAppAbout() { CAboutDlg dlg; dlg.DoModal(); } // ===== 編集操作(キャンバスへ委譲) ===== void CMainDialog::OnItemCopy() { m_canvas.CopySelected(); } void CMainDialog::OnItemPaste() { m_canvas.Paste(); } void CMainDialog::OnItemDuplicate() { m_canvas.DuplicateSelected(); } void CMainDialog::OnItemFront() { m_canvas.BringSelectedToFront(); } void CMainDialog::OnItemBack() { m_canvas.SendSelectedToBack(); } // 「整列...」ボタン。左/中央/右・上/中/下のポップアップメニューを出す。 // 選択された項目は ON_COMMAND_RANGE 経由で OnAlignCmd に届く。 void CMainDialog::OnAlignMenu() { if (!m_canvas.HasSelection()) { AfxMessageBox(_T("整列するアイテムを選択してください。")); return; } CMenu menu; menu.CreatePopupMenu(); menu.AppendMenu(MF_STRING, ID_ALIGN_LEFT, _T("左揃え")); menu.AppendMenu(MF_STRING, ID_ALIGN_HCENTER, _T("左右中央")); menu.AppendMenu(MF_STRING, ID_ALIGN_RIGHT, _T("右揃え")); menu.AppendMenu(MF_SEPARATOR, 0, (LPCTSTR)nullptr); menu.AppendMenu(MF_STRING, ID_ALIGN_TOP, _T("上揃え")); menu.AppendMenu(MF_STRING, ID_ALIGN_VCENTER, _T("上下中央")); menu.AppendMenu(MF_STRING, ID_ALIGN_BOTTOM, _T("下揃え")); CRect rc; GetDlgItem(ID_ALIGN_MENU)->GetWindowRect(&rc); menu.TrackPopupMenu(TPM_LEFTALIGN | TPM_TOPALIGN | TPM_LEFTBUTTON, rc.right, rc.top, this); } // 整列メニュー項目のハンドラ。ID の連番を how(0..5)に変換して整列を実行。 void CMainDialog::OnAlignCmd(UINT nID) { m_canvas.AlignSelected((int)(nID - ID_ALIGN_LEFT)); } // 「グリッド/スナップ」チェックの切替をキャンバスへ反映する。 void CMainDialog::OnToggleGrid() { CButton* p = (CButton*)GetDlgItem(IDC_CHECK_GRID); if (p) m_canvas.SetSnap(p->GetCheck() == BST_CHECKED); } // ===== 原紙(背景下絵) ===== // 「原紙を印刷」チェックの表示をモデル(m_printTemplate)に合わせる。 void CMainDialog::UpdateTemplateCheck() { CButton* p = (CButton*)GetDlgItem(IDC_CHECK_PRINT_TEMPLATE); if (p) p->SetCheck(m_layout.m_printTemplate ? BST_CHECKED : BST_UNCHECKED); } // 「原紙...」ボタン。画像を読み込み、続いて用紙サイズを原紙に合わせるか // (埋め込みDPI / 用紙DPI / 変更しない)をダイアログで選ばせる。 void CMainDialog::OnTemplateLoad() { CFileDialog dlg(TRUE, nullptr, nullptr, OFN_FILEMUSTEXIST | OFN_HIDEREADONLY, _T("画像ファイル (*.png;*.jpg;*.jpeg;*.bmp;*.gif)|*.png;*.jpg;*.jpeg;*.bmp;*.gif|すべて (*.*)|*.*||"), this); if (dlg.DoModal() != IDOK) return; if (!m_layout.LoadTemplate(dlg.GetPathName())) { AfxMessageBox(_T("原紙を読み込めませんでした。")); return; } // 用紙サイズを原紙ファイルのサイズに合わせる(px→mm 換算の基準を選択) int pw = 0, ph = 0; if (m_layout.GetTemplatePixelSize(pw, ph) && pw > 0 && ph > 0) { int pdpi = m_layout.m_dpi; double papW = pw * 25.4 / pdpi, papH = ph * 25.4 / pdpi; double edx = 0, edy = 0; BOOL hasEmbed = m_layout.GetTemplateDpi(edx, edy); CString msg; int r; if (hasEmbed) { double embW = pw * 25.4 / edx, embH = ph * 25.4 / edy; msg.Format( _T("用紙サイズを原紙に合わせます。\n\n画素: %d × %d px\n\n") _T("[はい] 画像の埋め込みDPI %.0f dpi → %.1f × %.1f mm\n") _T("[いいえ] 用紙のDPI %d dpi → %.1f × %.1f mm\n") _T("[キャンセル] 用紙サイズは変更しない"), pw, ph, edx, embW, embH, pdpi, papW, papH); r = AfxMessageBox(msg, MB_YESNOCANCEL | MB_ICONQUESTION); if (r == IDYES) m_layout.SetPaperSizeFromTemplate(TRUE); else if (r == IDNO) m_layout.SetPaperSizeFromTemplate(FALSE); // IDCANCEL: 用紙サイズは変更しない(原紙は現用紙に伸縮表示) } else { msg.Format( _T("用紙サイズを原紙に合わせます。\n(画像に埋め込みDPI情報がありません)\n\n") _T("画素: %d × %d px\n\n") _T("[はい] 用紙のDPI %d dpi → %.1f × %.1f mm で合わせる\n") _T("[いいえ] 用紙サイズは変更しない"), pw, ph, pdpi, papW, papH); r = AfxMessageBox(msg, MB_YESNO | MB_ICONQUESTION); if (r == IDYES) m_layout.SetPaperSizeFromTemplate(FALSE); } } m_canvas.SetDirty(TRUE); m_canvas.FitZoom(); m_canvas.Invalidate(); } // 「消去」ボタン。読み込んだ原紙を破棄して再描画する。 void CMainDialog::OnTemplateClear() { if (!m_layout.HasTemplate()) return; m_layout.ClearTemplate(); m_canvas.SetDirty(TRUE); m_canvas.Invalidate(); } // 「原紙を印刷」チェックの切替をモデル(m_printTemplate)へ反映する。 void CMainDialog::OnTogglePrintTemplate() { CButton* p = (CButton*)GetDlgItem(IDC_CHECK_PRINT_TEMPLATE); if (p) { m_layout.m_printTemplate = (p->GetCheck() == BST_CHECKED); m_canvas.SetDirty(TRUE); } }