Initial commit: OtozaiPrinter (AveryDennisonUtil) ラベル/レシート レイアウト設計・印刷 MFC アプリ

- 用紙設計キャンバス(1/100mm 基準)・テキスト/画像/矩形/直線アイテム
- コピー/複製/Zオーダー/整列/グリッドスナップ、原紙(下絵)読込と印刷可否
- GDI 直接印刷、.otz 保存/再読込(版数つき)
- MFC/CRT 静的リンクで VS2022 IDE から F5 デバッグ可
This commit is contained in:
yoshikazu
2026-06-05 17:27:29 +09:00
commit 19d1c39370
33 changed files with 3049 additions and 0 deletions
+422
View File
@@ -0,0 +1,422 @@
// 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);
}
}