Files
AveryDennisonUtil/MainDialog.cpp
T
yoshikazu 19d1c39370 Initial commit: OtozaiPrinter (AveryDennisonUtil) ラベル/レシート レイアウト設計・印刷 MFC アプリ
- 用紙設計キャンバス(1/100mm 基準)・テキスト/画像/矩形/直線アイテム
- コピー/複製/Zオーダー/整列/グリッドスナップ、原紙(下絵)読込と印刷可否
- GDI 直接印刷、.otz 保存/再読込(版数つき)
- MFC/CRT 静的リンクで VS2022 IDE から F5 デバッグ可
2026-06-05 17:27:29 +09:00

423 lines
15 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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);
}
}