#pragma comment(lib, "winmm")
#include <Windows.h>
#include <WindowsX.h>
#include <CommCtrl.h>
#include <wchar.h>
#include <time.h>
#include "resource.h"
#define TIMER_MAX 20
#define SECT TEXT("General")
#define APP_NAME TEXT("MultiTimer")
#define IDC_CLOCK 1000
#define IDC_EDIT 1100
#define IDC_TIMER 1200
enum STATUS {
OFF = 0,
ON,
WARN1,
WARN2,
END,
COUNTUP,
};
typedef struct {
STATUS status;
time_t time;
// int sec;
BOOL tool;
} TimerList;
// 外部変数
HINSTANCE hInst;
TCHAR iniFile[MAX_PATH];
HFONT hFontEdit;
HFONT hFontTimer;
HBRUSH hBrush[COUNTUP+1];
HWND hTool;
HWND hClock;
INT_PTR uIDEvent;
time_t timer;
TimerList tl[TIMER_MAX];
size_t timerNum;
int nearest = -1;
int warnSec[2];
UINT uTime;
// 関数プロトタイプ宣言
void GetIniFileName();
void Trace(LPCTSTR format, ...);
INT_PTR CALLBACK DlgMain(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
void OnInitDialog(HWND hDlg, LPARAM lParam);
void OnDestroy(HWND hDlg);
INT_PTR OnCtlColorStatic(WPARAM wParam, LPARAM lParam);
void OnTimer(HWND hDlg);
void OnXButtonDown(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
void OnLButtonDown(HWND hDlg, size_t i);
void SetTimerList(HWND hDlg, size_t i, STATUS status, time_t time);
void Nearest();
void AddTool(HWND hDlg, size_t i);
INT_PTR CALLBACK DlgInput(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
//==============================================================================
int CALLBACK WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int nCmdShow)
{
hInst = hInstance;
GetIniFileName();
DialogBox(hInstance, MAKEINTRESOURCE(IDD_MAIN), NULL, DlgMain);
return 0;
}
void GetIniFileName()
{
TCHAR path [_MAX_PATH];
TCHAR drive [_MAX_DRIVE];
TCHAR dir [_MAX_DIR];
TCHAR fname [_MAX_FNAME];
TCHAR ext [_MAX_EXT];
GetModuleFileName(NULL, path, _countof(path));
_wsplitpath_s(path, drive, dir, fname, ext);
_wmakepath_s(iniFile, drive, dir, fname, L"ini");
}
void Trace(LPCTSTR format, ...)
{
va_list arg_ptr;
va_start(arg_ptr, format);
TCHAR buffer[256];
int size = _vsnwprintf_s(buffer, _TRUNCATE, format, arg_ptr);
va_end(arg_ptr);
OutputDebugString(buffer);
if (size < 0) {
OutputDebugString(L"...\n");
}
}
//==============================================================================
INT_PTR CALLBACK DlgMain(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
INT_PTR res = TRUE; // メッセージを処理した
switch (uMsg) {
case WM_TIMER:
OnTimer(hDlg);
break;
case WM_CTLCOLORSTATIC:
return OnCtlColorStatic(wParam, lParam);
case WM_LBUTTONDOWN:
case WM_RBUTTONDOWN:
OnXButtonDown(hDlg, uMsg, wParam, lParam);
break;
case WM_INITDIALOG:
OnInitDialog(hDlg, lParam);
//res = FALSE; // SetFocusでフォーカスを設定した場合はFALSE
break;
case WM_CLOSE:
EndDialog(hDlg, 0);
break;
case WM_ENDSESSION:
if (wParam == TRUE) {
EndDialog(hDlg, 0);
}
break;
case WM_DESTROY:
OnDestroy(hDlg);
break;
default:
res = FALSE; // メッセージを処理しなかった
}
return res;
}
void OnInitDialog(HWND hDlg, LPARAM lParam)
{
// LPCREATESTRUCT pcs = (LPCREATESTRUCT)lParam;
timerNum = GetPrivateProfileInt(SECT, L"TimerNum", 0, iniFile);
warnSec[0] = GetPrivateProfileInt(SECT, L"WarningSec1", 120, iniFile);
warnSec[1] = GetPrivateProfileInt(SECT, L"WarningSec2", 65, iniFile);
int X = GetPrivateProfileInt(SECT, L"X", 100, iniFile);
int Y = GetPrivateProfileInt(SECT, L"Y", 100, iniFile);
if (timerNum < 1) timerNum = 11;
if (TIMER_MAX < timerNum) timerNum = TIMER_MAX;
RECT rw;
RECT rc;
GetWindowRect(hDlg, &rw);
GetClientRect(hDlg, &rc);
int cx = (rw.right - rw.left) - rc.right + 210;
int cy = (rw.bottom - rw.top) - rc.bottom + 35 + 25 * timerNum;
SetWindowPos(hDlg, HWND_TOP, X, Y, cx, cy, 0);
// フォント作成
LOGFONT lf;
ZeroMemory(&lf, sizeof lf);
lf.lfCharSet = DEFAULT_CHARSET;
lf.lfHeight = 16;
wcscpy_s(lf.lfFaceName, L"MS Pゴシック");
hFontEdit = CreateFontIndirect(&lf);
lf.lfHeight = 24;
wcscpy_s(lf.lfFaceName, L"Arial");
hFontTimer = CreateFontIndirect(&lf);
// ブラシ
hBrush[OFF] = (HBRUSH)GetStockObject(LTGRAY_BRUSH);
hBrush[ON] = (HBRUSH)GetStockObject(WHITE_BRUSH);
hBrush[WARN1] = CreateSolidBrush(RGB(0x00,0xff,0xff));
hBrush[WARN2] = CreateSolidBrush(RGB(0x00,0xff,0x00));
hBrush[END] = CreateSolidBrush(RGB(0x00,0xff,0x00));
hBrush[COUNTUP] = CreateSolidBrush(RGB(0xff,0xff,0x7f));
// 時計
hClock = CreateWindowEx(WS_EX_CLIENTEDGE, WC_STATIC, NULL,
WS_CHILD | WS_VISIBLE | SS_CENTER,
105, 5, 100, 25, hDlg, (HMENU)IDC_CLOCK, hInst, NULL);
SendMessage(hClock, WM_SETFONT, (WPARAM)hFontTimer, MAKELPARAM(FALSE, 0));
// ツールチップ
hTool = CreateWindowEx(0, TOOLTIPS_CLASS, NULL, TTS_ALWAYSTIP,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, hDlg, NULL, hInst, NULL);
// タイマー
for (size_t i = 0; i < timerNum; i++) {
HWND hWnd;
int y = 30 + 25 * i;
hWnd = CreateWindowEx(WS_EX_CLIENTEDGE, WC_EDIT, NULL,
WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL,
5, y, 100, 25, hDlg, HMENU(IDC_EDIT + i), hInst, NULL);
SendMessage(hWnd, WM_SETFONT, (WPARAM)hFontEdit, MAKELPARAM(FALSE, 0));
hWnd = CreateWindowEx(WS_EX_CLIENTEDGE, WC_STATIC, NULL,
WS_CHILD | WS_VISIBLE | SS_CENTER/* | SS_NOTIFY*/,
105, y, 100, 25, hDlg, HMENU(IDC_TIMER + i), hInst, NULL);
SendMessage(hWnd, WM_SETFONT, (WPARAM)hFontTimer, MAKELPARAM(FALSE, 0));
TCHAR key[16];
TCHAR buf[256];
swprintf_s(key, L"Edit%d", i);
GetPrivateProfileString(SECT, key, NULL, buf, _countof(buf), iniFile);
SetDlgItemText(hDlg, IDC_EDIT + i, buf);
swprintf_s(key, L"Timer%d", i);
GetPrivateProfileString(SECT, key, NULL, buf, _countof(buf), iniFile);
time_t time = 0LL;
swscanf_s(buf, L"%lld", &time);
if (time) {
tl[i].status = ON;
tl[i].time = time;
AddTool(hDlg, i);
}
}
Nearest();
time(&timer);
uIDEvent = SetTimer(hDlg, 1, 1000, NULL);
}
void OnDestroy(HWND hDlg)
{
WINDOWPLACEMENT wp;
wp.length = sizeof wp;
GetWindowPlacement(hDlg, &wp);
TCHAR buf[256];
swprintf_s(buf, L"%d", wp.rcNormalPosition.left);
WritePrivateProfileString(SECT, L"X", buf, iniFile);
swprintf_s(buf, L"%d", wp.rcNormalPosition.top);
WritePrivateProfileString(SECT, L"Y", buf, iniFile);
for (size_t i = 0; i < timerNum; i++) {
TCHAR key[16];
swprintf_s(key, L"Edit%d", i);
GetDlgItemText(hDlg, IDC_EDIT + i, buf, _countof(buf));
WritePrivateProfileString(SECT, key, buf, iniFile);
swprintf_s(key, L"Timer%d", i);
swprintf_s(buf, L"%lld", (tl[i].status == ON) ? tl[i].time : 0LL);
WritePrivateProfileString(SECT, key, buf, iniFile);
}
DeleteObject(hFontEdit);
DeleteObject(hFontTimer);
for (int i = 0; i < _countof(hBrush); i++) {
DeleteObject(hBrush[i]);
}
KillTimer(hDlg, uIDEvent);
}
INT_PTR OnCtlColorStatic(WPARAM wParam, LPARAM lParam)
{
LONG id = GetWindowLong((HWND)lParam, GWL_ID);
if (id == IDC_CLOCK) {
return (INT_PTR)hBrush[ON];
}
size_t i = id - IDC_TIMER;
if (i < timerNum) {
HDC hdc = (HDC)wParam;
SetBkMode(hdc, TRANSPARENT);
if (i == nearest) {
SetTextColor(hdc, RGB(0xff,0x00,0x00));
}
return (INT_PTR)hBrush[tl[i].status];
}
return FALSE;
}
void OnTimer(HWND hDlg)
{
TCHAR buf[8+1];
time(&timer);
tm tm;
localtime_s(&tm, &timer);
swprintf_s(buf, L"%2d:%02d:%02d", tm.tm_hour, tm.tm_min, tm.tm_sec);
SetWindowText(hClock, buf);
for (size_t i = 0; i < timerNum; i++) {
switch (tl[i].status) {
case ON:
case WARN1:
case WARN2:
{
int sec = int(tl[i].time - timer);
if (tl[i].status == ON && sec <= warnSec[0]) {
tl[i].status = WARN1;
Nearest();
}
if (tl[i].status == WARN1 && sec <= warnSec[1]) {
tl[i].status = WARN2;
TCHAR path[MAX_PATH];
GetPrivateProfileString(SECT, L"WaveFile", L"sample.wav",
path, _countof(path), iniFile);
PlaySound(path, NULL, SND_FILENAME | SND_ASYNC);
}
if (sec <= 0) {
sec = 0;
tl[i].status = END;
}
// tl[i].sec = sec;
swprintf_s(buf, L"%2d:%02d:%02d", sec / 3600, (sec / 60) % 60, sec % 60);
SetDlgItemText(hDlg, IDC_TIMER + i, buf);
}
break;
case COUNTUP:
{
int sec = int(timer - tl[i].time);
if (99 * 3600 + 59 * 60 + 59 < sec) {
sec = 0;
tl[i].status = END;
}
swprintf_s(buf, L"%2d:%02d:%02d", sec / 3600, (sec / 60) % 60, sec % 60);
SetDlgItemText(hDlg, IDC_TIMER + i, buf);
}
break;
}
}
}
void OnXButtonDown(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
POINT pt;
pt.x = GET_X_LPARAM(lParam);
pt.y = GET_Y_LPARAM(lParam);
HWND hChild = ChildWindowFromPoint(hDlg, pt);
LONG id = GetWindowLong(hChild, GWL_ID);
// Trace(L"OnXButtonDown(%d, %d, %d)\n", pt.x, pt.y, id);
size_t i = id - IDC_TIMER;
if (i < timerNum) {
switch (uMsg) {
case WM_LBUTTONDOWN:
OnLButtonDown(hDlg, i);
break;
case WM_RBUTTONDOWN:
tl[i].status = OFF;
SetDlgItemText(hDlg, IDC_TIMER + i, NULL);
Nearest();
break;
}
}
}
void OnLButtonDown(HWND hDlg, size_t i)
{
INT_PTR ret = DialogBox(hInst, MAKEINTRESOURCE(IDD_INPUT), hDlg, DlgInput);
switch (ret) {
case IDC_TIME1: // 残り時間
{
int sec = (uTime / 10000) * 3600 + ((uTime / 100) % 100) * 60 + (uTime % 100);
SetTimerList(hDlg, i, ON, timer + sec);
}
break;
case IDC_TIME2: // 時刻指定
{
tm tm;
localtime_s(&tm, &timer);
tm.tm_hour = uTime / 100;
tm.tm_min = uTime % 100;
tm.tm_sec = 0;
time_t time = mktime(&tm);
if (time <= timer) {
time += 24 * 60 * 60;
}
SetTimerList(hDlg, i, ON, time);
}
break;
case IDC_COUNTUP: // カウントアップ
{
SetTimerList(hDlg, i, COUNTUP, timer);
}
break;
}
}
void SetTimerList(HWND hDlg, size_t i, STATUS status, time_t time)
{
tl[i].status = status;
tl[i].time = time;
AddTool(hDlg, i);
Nearest();
}
void Nearest()
{
time_t time;
nearest = -1;
for (size_t i = 0; i < timerNum; i++) {
if (tl[i].status == ON) {
if (nearest < 0 || tl[i].time < time) {
nearest = i;
time = tl[i].time;
}
}
}
}
void AddTool(HWND hDlg, size_t i)
{
RECT rc;
GetWindowRect(GetDlgItem(hDlg, IDC_TIMER + i), &rc);
MapWindowPoints(NULL, hDlg, (LPPOINT)&rc, 2);
TCHAR buf[8+1];
tm tm;
localtime_s(&tm, &tl[i].time);
swprintf_s(buf, L"%d:%02d:%02d", tm.tm_hour, tm.tm_min, tm.tm_sec);
TOOLINFO ti;
ti.cbSize = sizeof ti - 4; // ComCtl32.dll ver 5.0
ti.uFlags = TTF_SUBCLASS;
ti.hwnd = hDlg;
ti.uId = i;
ti.rect = rc;
ti.lpszText = buf;
if (tl[i].tool) {
SendMessage(hTool, TTM_DELTOOL, 0, (LPARAM)&ti);
} else {
tl[i].tool = TRUE;
}
SendMessage(hTool, TTM_ADDTOOL, 0, (LPARAM)&ti);
}
//==============================================================================
INT_PTR CALLBACK DlgInput(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
INT_PTR res = TRUE; // メッセージを処理した
switch (uMsg) {
case WM_COMMAND:
{
WORD wCtrlId = LOWORD(wParam);
switch (wCtrlId) {
case IDOK:
{
BOOL trans;
uTime = GetDlgItemInt(hDlg, IDC_TIME1, &trans, FALSE);
if (trans) {
if (uTime <= 995959) {
EndDialog(hDlg, IDC_TIME1);
break;
}
}
uTime = GetDlgItemInt(hDlg, IDC_TIME2, &trans, FALSE);
if (trans) {
if (uTime <= 2359 && uTime % 100 < 60) {
EndDialog(hDlg, IDC_TIME2);
break;
}
}
MessageBeep(MB_ICONWARNING);
}
break;
case IDC_COUNTUP:
case IDCANCEL:
EndDialog(hDlg, wCtrlId);
break;
}
}
break;
case WM_INITDIALOG:
// res = FALSE; // SetFocusでフォーカスを設定した場合はFALSE
break;
case WM_CLOSE:
EndDialog(hDlg, IDCANCEL);
break;
default:
res = FALSE; // メッセージを処理しなかった
}
return res;
}