/** photomapwin.cpp
 * 撮影場所をマッピング
 *
 * @copyright	(c)studio pahoo
 * @author		パパぱふぅ
 * @開発環境	MinGW C++ + cURL + OpenSSL + Boost C++ Libraries
 *					+ tinyxml2 + TinyEXIF
 *					+ WebView2Loader.dll + Leaflet + OSM (+ GoogleMap)
 * @参考URL		https://www.pahoo.org/e-soul/webtech/cpp01/cpp01-13-01.shtm
*/

// 初期化処理 ======================================================
#include <iostream>
#include <fstream>
#include <stdio.h>
#include <stdlib.h>
#include <tchar.h>
#include <time.h>
#include <sstream>
#include <string>
#include <winsock2.h>
#include <windows.h>
#include <shlobj.h>
#include <commctrl.h>
#include <boost/format.hpp>
#include <gdiplus/gdiplus.h>
#include <ole2.h>
#include <richedit.h>
#include <regex> 
#include <boost/property_tree/xml_parser.hpp>
#include <boost/algorithm/string.hpp>
#include "mystrings.h"
#include "apikey.h"
#include "WebView2.h"
#include "pahooWebView2.hpp"
#include "pahooGeocode.hpp"
#include "resource.h"
#include "TinyEXIF.h"

using namespace std;
using namespace Gdiplus;
using namespace boost;
using namespace boost::property_tree;

// 定数など ==================================================================
#define MAKER		"pahoo.org"				// 作成者
#define APPNAME		"photomapwin"			// アプリケーション名
#define APPNAMEJP	"撮影場所をマッピング"	// アプリケーション名（日本語）
#define APPVERSION	"2.4.1"					// バージョン
#define APPYEAR		"2020-25"				// 作成年
#define REFERENCE	"https://www.pahoo.org/e-soul/webtech/cpp01/cpp01-13-01.shtm"	// 参考サイト

// char*バッファサイズ
#define SIZE_BUFF		512

// ListViewItemの最大文字長：変更不可
#define MAX_LISTVIEWITEM	258

// ヘルプ・ファイル
#define HELPFILE	".\\etc\\help.chm"

// デフォルト保存ファイル名
#define SAVEFILE	"photomapwin.csv"

// マップID
#define MAP_ID			"map_id"
// 地図の大きさ
#define MAP_WIDTH		650		// 地図の幅（ピクセル）
#define MAP_HEIGHT		345		// 地図の高さ（ピクセル）
// 経度・緯度（初期値）
#define DEF_LONGITUDE	139.766667
double Longitude = DEF_LONGITUDE;
#define DEF_LATITUDE	35.681111
double Latitude  = DEF_LATITUDE;
// 地図拡大率（初期値）
#define DEF_ZOOM	13
int Zoom = DEF_ZOOM;
// 地図の種類（初期値）
#define DEF_MAPTYPE		"GSISTD"
string Maptype = DEF_MAPTYPE;

// エラー・メッセージ格納用
string ErrorMessage;

// UserAgent
string UserAgent;

// 現在のインターフェイス
HINSTANCE hInst;

// アプリケーション・ウィンドウ
HWND hParent;

// WebView2ウィンドウ
HWND hWebView2;

// ICoreWebView2：pahooWebView2.cpp で定義する．
extern ICoreWebView2* webView2;

// テンポラリファイル名
char tmpFname[MAX_PATH + 1];
wchar_t wUri[MAX_PATH * 2 + 1];

// アプリケーション・ウィンドウ位置
unsigned hParent_X, hParent_Y;

// GDI+用
ULONG_PTR lpToken;

// pahooGeocodeオブジェクト
pahooGeocode* pGC;

// WebView2ハンドラの動作を指定する．【変更不可】
enum class eAction {
	GetLatLong,				// 緯度・経度取得してブラウザ表示
	GetZoomType,			// 拡大率・マップタイプ取得してブラウザ表示
	GetLatLongZoomType,		// 緯度・経度・拡大率・マップタイプ取得してブラウザ表示
	Finish					// プログラム終了
};
eAction selectAction = eAction::GetLatLongZoomType;

// EXIFStreamFileクラス
class EXIFStreamFile : public TinyEXIF::EXIFStream {
public:
	explicit EXIFStreamFile(const char* fileName)
		: file(fileName, std::ifstream::in|std::ifstream::binary) {}
	bool IsValid() const override {
		return file.is_open();
	}
	const uint8_t* GetBuffer(unsigned desiredLength) override {
		buffer.resize(desiredLength);
		if (!file.read((char*)buffer.data(), desiredLength))
			return NULL;
		return buffer.data();
	}
	bool SkipBuffer(unsigned desiredLength) override {
		return (bool)file.seekg(desiredLength,std::ios::cur);
	}
private:
	std::ifstream file;
	std::vector<uint8_t> buffer;
};

// Exif格納クラス
#define SIZE_EXIFS		100		// 最大格納数
class _Exif {
public:
	wstring label = L"";		// データ項目
	wstring data  = L"";		// データ本体
	wstring unit  = L"";		// 単位
} Exif[SIZE_EXIFS];

// バージョン情報
string Version = (boost::format(R"(
%1%  バージョン %2%

本アプリケーションはMIT Licenseです．
個人での無償利用が可能です．自由に改造できます．
再配布の際は，下記の著作権表記，およびURLと本使用条件を必ず明記してください．

　Copyright by (c)studio pahoo, %3%
　https://www.pahoo.org/

なお，本アプリケーションの利用または改造することによって生じた得失ついては一切関知いたしません．WebAPIの変更・停止によって正常に機能しなくなる場合があります．また，二次利用先の組織・企業・団体の目的・内容・活動については一切関知いたしません．

本アプリケーションは，下記のライブラリ，フレームワーク，APIを利用しています．
　MinGW（g++）／Boost C++ライブラリ／libcurl／OpenSSL+／
　WebView2Loader.dll／TinyEXIF
　Leaflet／OpenStreetMap／Googleマップ
)")
% APPNAMEJP
% APPVERSION
% APPYEAR
).str();

// サブ・プログラム =====================================================
/**
 * パラメータの初期化
 * @param	なし
 * @return	なし
 */
void initParameter(void) {
	Longitude	= DEF_LONGITUDE;
	Latitude	= DEF_LATITUDE;
	Zoom		= DEF_ZOOM;
	Maptype		= DEF_MAPTYPE;
	hParent_X	= 0;
	hParent_Y	= 0;
}

/**
 * パラメータの読み込み
 * @param	なし
 * @return	なし
 */
void loadParameter(void) {
	ptree pt;

	// 初期値設定
	initParameter();

	// XMLファイル読み込み
	try {
		xml_parser::read_xml(getMyPath(APPNAME) + APPNAME + ".xml", pt);

		// XML解釈
		try {
			// 形式チェック
			if (optional<string>str = pt.get_optional<string>("parameter")) {
			} else {
				return;
			}
			// パラメータ読み込み
			for (auto it : pt.get_child("parameter")) {
				string type= it.second.get_optional<string>("<xmlattr>.type").value();
				if (type == "latitude") {
					Latitude = stod(it.second.data());
				} else if (type == "longitude") {
					Longitude = stod(it.second.data());
				} else if (type == "zoom") {
					Zoom = stoi(it.second.data());
				} else if (type == "maptype") {
					Maptype = (string)it.second.data();
				} else if (type == "wx") {
					hParent_X = (unsigned)stoi(it.second.data());
				} else if (type == "wy") {
					hParent_Y = (unsigned)stoi(it.second.data());
				}
			}
		// 解釈失敗したら初期値設定
		} catch (xml_parser_error& e) {
			initParameter();
			return;
		}
	// 読み込み失敗したら初期値設定
	} catch (xml_parser_error& e) {
		initParameter();
		return;
	}

	// アプリケーション・ウィンドウの位置（デスクトップ範囲外なら原点移動）
	HWND hDesktop = GetDesktopWindow();
	WINDOWINFO windowInfo;
	windowInfo.cbSize = sizeof(WINDOWINFO);
	GetWindowInfo(hDesktop, &windowInfo);
	if (hParent_X >= (unsigned)windowInfo.rcWindow.right) {
		hParent_X = 0;
	}
	if (hParent_Y >= (unsigned)windowInfo.rcWindow.bottom) {
		hParent_Y = 0;
	}
}

/**
 * パラメータの保存
 * @param	なし
 * @return	なし
 */
void saveParameter(void) {
#ifndef CMDAPP
	// アプリケーション・ウィンドウの位置取得
	WINDOWINFO windowInfo;
	windowInfo.cbSize = sizeof(WINDOWINFO);
	GetWindowInfo(hParent, &windowInfo);
	hParent_X = (unsigned)windowInfo.rcWindow.left;
	hParent_Y = (unsigned)windowInfo.rcWindow.top;
	if (hParent_X >= (unsigned)windowInfo.rcWindow.right) {
		hParent_X = 0;
	}
	if (hParent_Y >= (unsigned)windowInfo.rcWindow.bottom) {
		hParent_Y = 0;
	}
#endif

	char lng[SIZE_BUFF + 1], lat[SIZE_BUFF + 1];
	snprintf(lng, SIZE_BUFF, "%.5f", Longitude);
	snprintf(lat, SIZE_BUFF, "%.5f", Latitude);

	// XMLファイルへ書き込む
	ptree pt;
	ptree& child1 = pt.add("parameter.param", lat);
	child1.add("<xmlattr>.type", "latitude");
	ptree& child2 = pt.add("parameter.param", lng);
	child2.add("<xmlattr>.type", "longitude");
	ptree& child3 = pt.add("parameter.param", to_string(Zoom));
	child3.add("<xmlattr>.type", "zoom");
	ptree& child4 = pt.add("parameter.param", Maptype);
	child4.add("<xmlattr>.type", "maptype");
	ptree& child5 = pt.add("parameter.param", hParent_X);
	child5.add("<xmlattr>.type", "wx");
	ptree& child6 = pt.add("parameter.param", hParent_Y);
	child6.add("<xmlattr>.type", "wy");

	const int indent = 4;
	write_xml(getMyPath(APPNAME) + APPNAME + ".xml", pt, std::locale(),
		xml_writer_make_settings<std::string>(' ', indent));
}

/**
 * パラメータの削除
 * @param	なし
 * @return	なし
 */
void delParameter(void) {
	remove((getMyPath(APPNAME) + APPNAME + ".xml").c_str());

	// 初期値設定
	initParameter();
}

// バージョン表示ダイアログ =================================================
/**
 * イベントハンドラ：バージョン表示ダイアログ
 * @param	HWND hDlg			ウィンドウ・ハンドラ
 * @paramm	UINT uMsg			メッセージ識別子
 * @param	WPARAM wParam		メッセージの最初のパラメータ
 * @paramL	PARAM lParam		メッセージの2番目のパラメータ
 * @return	INT_PTR CALLBACK	TRUE：メッセージ処理完了／FALSE：未完了
*/
INT_PTR CALLBACK processHelp(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
	string help;

	switch(uMsg){
	// ダイアログ初期化
	case WM_INITDIALOG:
		CenterWindow(hDlg);
		setStrEditBox(hDlg, IDC_TEXT_HELP, Version);
	break;

	// ボタン押下
	case WM_COMMAND:
		switch (LOWORD(wParam)) {
			// 実行
		case IDC_BUTTON_OK:
			EndDialog(hDlg, 0);
			break;
		default:
			return 1;
		}
		break;
	// プログラム終了
	case WM_CLOSE:
		EndDialog(hDlg, 0);
		break;
	}
    return 0;
}

/**
 * ヘルプ・ダイアログを作成
 * @param  HWND hDlg		親ウィンドウ・ハンドラ
 * @param  DLGPROC handler	イベント・ハンドラ
 * @return なし
*/
void createHelp(HWND hDlg, DLGPROC handler) {
	DialogBox(hInst, MAKEINTRESOURCE(IDD_DIALOG_VER), hDlg, (DLGPROC)handler);
}

// 保存・コピー =============================================================
/**
 * ファイル名（フルパス）からファイル名（拡張子を除く）を取り出す
 * @param	wstring path ファイル名（フルパス）
 * @return	wstring ファイル名（拡張子を除く）
*/
wstring wgetBasename(wstring path) {
	wstring basename = L"";

	// 最後のディレクトリ識別子を探す
	size_t pos = path.rfind(_SW("\\"));
	if (pos == string::npos) {
		return basename;
	}
	// ファイル名＋拡張子
	wstring fname = path.substr(pos + 1);

	// 拡張子を除く
	pos = fname.rfind(_SW("."));
	if (pos == string::npos) {
		basename = fname;
	} else {
		basename = fname.substr(0, pos);
	}
	return basename;
}

/**
 * 検索結果をCSVファイルに保存する
 * @param	const char *fname 保存ファイル名
 * @return	なし
*/
void __saveCSV(const char *fname) {
	ofstream outputfile(fname);

	// ラベル
	outputfile << "\"" << "項目" << "\",";
	outputfile << "\"" << "データ" << "\",";
	outputfile << "\"" << "単位" << "\"" << endl;

	// データ本体
	regex pattern(R"(^[^0].[0-9\.\+\-]+$)");	// 数値かどうか
	for (int i = 0; i < SIZE_EXIFS; i++) {
		if (Exif[i].label.length() == 0)	break;
		outputfile << "\"" << _WS(Exif[i].label) << "\",";
		if (regex_match(_WS(Exif[i].data), pattern)) {
			outputfile << _WS(Exif[i].data) << ",";
		} else {
			outputfile << "\"" << _WS(Exif[i].data) << "\",";
		}
		if (! Exif[i].unit.empty()) {
			outputfile << "\"" << _WS(Exif[i].unit) << "\"" << endl;
		} else {
			outputfile << endl;
		}
		if (outputfile.bad()) {
			ErrorMessage = "CSVファイル書き込みエラー";
			break;
		}
	}
	outputfile.close();
}

/**
 * 検索結果をCSVファイルに保存する
 * @param	char *fname 保存ファイル名
 * @return	なし
*/
void saveCSV(char *savefname) {
	static char fname[MAX_PATH + 1];
	strcpy(fname, savefname);
	OPENFILENAME of;

	// OPENFILENAME構造体のサイズをセット
	memset(&of, 0, sizeof(OPENFILENAME));
	of.lStructSize = sizeof(OPENFILENAME);
	// ダイアログボックスを所有するウィンドウへのハンドル
	of.hwndOwner = hParent;
	of.lpstrFilter = TEXT("CSVファイル(*.csv;*.txt)\0*.csv;*.txt\0すべてのファイル(*.txt)\0*.txt\0\0");
	// ファイル名を格納したバッファのアドレス
	of.lpstrFile = (LPTSTR)fname;
	// lpstrFileメンバで指定されるバッファのサイズ
	of.nMaxFile = MAX_PATH;
	of.Flags = OFN_OVERWRITEPROMPT;
	// デフォルトの拡張子を格納したバッファのアドレス
	of.lpstrDefExt = TEXT("csv");
	// コモンダイアログの表示
	GetSaveFileName(&of);
	// ファイル保存
	__saveCSV(fname);
}

// 画像処理 =================================================================
/**
 * 画像ファイルをGDI+を使って読み込む
 * Orientation情報があれば，画像を回転させる
 * @param	TCHAR* szFile	画像ファイル名
 * @param	HDC* hdcBMP		オフスクリーン用デバイスコンテキストを格納
 * @param	HDC  hdc		デバイスコンテキスト
 * @param	int* ix, iy		読み込んだ画像の横・縦ピクセル数
 * @return	なし
*/
bool loadImageOnMemory(TCHAR* szFile, HDC* hdcBMP, HDC hdc, int* ix, int* iy) {
	Image* imageP;
	WCHAR wTitle[MAX_PATH];
	BITMAPINFOHEADER bmih;
	HBITMAP hBitmap;
	BYTE *pBits;

	// 読み込むファイル名
	MultiByteToWideChar(932, 0, szFile, -1, wTitle, sizeof(wTitle) / sizeof(TCHAR));
	imageP = Bitmap::FromFile(wTitle);
	if (imageP == 0) {
		*ix = 0;
		*iy = 0;
		*hdcBMP = 0;
		return FALSE;
	}

	// ExifのOrientationを取得
	UINT size = imageP->GetPropertyItemSize(PropertyTagOrientation);
	if (size > 0) {
		PropertyItem* prop = (PropertyItem*)malloc(size);
		// malloc成功の場合
		if (prop) {
			// sizeがPropertyItem構造体のvalue領域を含む正しいサイズの場合
			if (imageP->GetPropertyItem(PropertyTagOrientation, size, prop) == Ok) {
				// prop->valueがnullptrでない場合
				if (! prop->value) {
					int orientation = *(short*)prop->value;
					cout << "GetPropertyItem" << endl;

					// Orientationに基づいて回転
					switch (orientation) {
					case 3:		// 180度回転
						imageP->RotateFlip(Rotate180FlipNone);
						break;
					case 6:		// 90度時計回り
						imageP->RotateFlip(Rotate90FlipNone);
						break;
					case 8:		// 90度反時計回り
						imageP->RotateFlip(Rotate270FlipNone);
						break;
					case 2:		// 水平方向に反転
						imageP->RotateFlip(RotateNoneFlipX);
						break;
					case 4:		// 垂直方向に反転
						imageP->RotateFlip(RotateNoneFlipY);
						break;
					case 5:		// 90度反時計回り＋垂直反転
						imageP->RotateFlip(Rotate270FlipX);
						break;
					case 7:		// 90度時計回り＋垂直反転
						imageP->RotateFlip(Rotate90FlipX);
						break;
					default:	// Orientationが1の場合はそのまま
						break;
					}
				}
			}
			free(prop);
		}
	}

	// 画像サイズを取得
	bmih.biSize = sizeof(bmih);
	bmih.biWidth = *ix = imageP->GetWidth();
	bmih.biHeight = *iy = imageP->GetHeight();
	bmih.biPlanes = 1;
	bmih.biBitCount = 32;
	bmih.biCompression = BI_RGB;
	bmih.biSizeImage = 0;
	bmih.biXPelsPerMeter = 0;
	bmih.biYPelsPerMeter = 0;
	bmih.biClrUsed = 0;
	bmih.biClrImportant = 0;
	hBitmap = CreateDIBSection(NULL, (BITMAPINFO*)&bmih, 0, (void**)&pBits, NULL, 0);
	cout << "CreateDIBSection" << endl;
	if (pBits == NULL) {
		ErrorMessage = "メモリ不足";
		DeleteObject(hBitmap);
		delete imageP;
		*ix = 0;
		*iy = 0;
		return FALSE;
	}
	*hdcBMP = CreateCompatibleDC(hdc);
//	HOldBitmap = (HBITMAP)SelectObject(*hdcBMP, hBitmap);
	SelectObject(*hdcBMP, hBitmap);
	Graphics MyGraphics(*hdcBMP);
	MyGraphics.DrawImage(imageP, 0, 0, imageP->GetWidth(), imageP->GetHeight());
	delete imageP;

	return TRUE;
}

/**
 * 縦横比を保った画像サイズ計算
 * @param	int* sx, sy  変換後画像サイズを格納
 * @param	int  px, py  元画像の縦横サイズ
 * @param	int  dx, dy  変換後領域の縦横サイズ
 * @return	なし
*/
void reSizeImage(int* sx, int* sy, int px, int py, int dx, int dy) {
	double pxy = double(px) / double(py);	// 元画像の横縦比
	double dxy = double(dx) / double(dy);	// 変換後の横縦比
	int dx2, dy2;

	// 横を基準に縦を縮小
	if(pxy > dxy) {
		dx2 = dx;
		dy2 = (int)double(dx / pxy);
	} else {
		dy2 = dy;
		dx2 = (int)double(dy * pxy);
	}
	*sx = dx2;
	*sy = dy2;
}

/**
 * 画像ファイルの表示
 * @param	HWND  hWnd 表示するウィンドウ・ハンドル
 * @param	const char *fname 画像ファイル名
 * @return	bool TRUE：表示成功／FALSE：失敗
*/
bool onPaint(HWND hWnd, const char *fname) {
	HDC hdc;
	PAINTSTRUCT ps;
	static HDC hdcBMP;
	static int ix,iy;
	int sx, sy;
	RECT rect;
	bool ret = TRUE;

	// 画像ファイル読み込み
	hdc = GetDC(hWnd);
	if(loadImageOnMemory((TCHAR*)fname, &hdcBMP, hdc, &ix, &iy) == FALSE) {
		ErrorMessage = "画像ファイル " + (string)fname + " の読み込みに失敗";
		return FALSE;
	}
	InvalidateRect(hWnd, 0, TRUE);
	hdc = BeginPaint(hWnd, &ps);
	GetClientRect(hWnd, &rect);

	// 領域クリア
	HPEN   hNewPen   = (HPEN)CreatePen(PS_INSIDEFRAME, 4, RGB(255, 255, 255));
	HPEN   hOldPen   = (HPEN)SelectObject(hdc,hNewPen);
	HBRUSH hNewBrush = (HBRUSH)CreateSolidBrush(RGB(255, 255, 255));
	HBRUSH hOldBrush = (HBRUSH)SelectObject(hdc, hNewBrush);
	Rectangle(hdc, 0, 0, rect.right, rect.bottom);
	DeleteObject(SelectObject(hdc, hOldBrush));
	DeleteObject(SelectObject(hdc, hOldPen));

	// 縮小表示
	reSizeImage(&sx, &sy, ix, iy, (rect.right - rect.left), (rect.bottom - rect.top));
	SetStretchBltMode(hdc, HALFTONE);
	StretchBlt(hdc, 0, 0, sx, sy, hdcBMP, 0, 0, ix, iy, SRCCOPY);
	EndPaint(hWnd, &ps);
	ReleaseDC(hWnd, hdc);

	return ret;
}

// Exif情報処理 =============================================================
/**
 * 画像ファイルを開いてExif情報を取得する（実作業）
 * @param	const char *fname 保存ファイル名
 * @return	bool TRUE：取得成功／FALSE：失敗
*/
bool loadExif(const char *fname) {
	double f;
	string ss;
	char buff[SIZE_BUFF + 1];
	bool ret = TRUE;

	// 画像ファイルのオープン
	EXIFStreamFile stream(fname);
	if (! stream.IsValid()) {
		ErrorMessage = "画像ファイル " + (string)fname + " の読み込みに失敗";
		return FALSE;
	}
	// Exif情報の取得
	TinyEXIF::EXIFInfo imageEXIF(stream);

	// Exif情報を配列Exifへ代入
	// 初期化
	for (int i = 0; i < SIZE_EXIFS; i++) {
		Exif[i].label = L"";
		Exif[i].data  = L"";
		Exif[i].unit  = L"";
	}
	// 代入
	int i = 0;

	// 位置情報
	if (imageEXIF.GeoLocation.hasLatLon()) {
		Exif[i].label = _SW("緯度");
		Exif[i].data  = _SW(to_string(imageEXIF.GeoLocation.Latitude));
		Exif[i].unit  = _SW("度");
		Latitude = imageEXIF.GeoLocation.Latitude;
		i++;
		Exif[i].label = _SW("経度");
		Exif[i].data  = _SW(to_string(imageEXIF.GeoLocation.Longitude));
		Exif[i].unit  = _SW("度");
		Longitude = imageEXIF.GeoLocation.Longitude;
		i++;
	} else {
		ErrorMessage = "画像ファイル " + (string)fname + " に位置情報が存在しない";
		ret = FALSE;
	}
	if (imageEXIF.GeoLocation.hasAltitude()) {
		snprintf(buff, SIZE_BUFF, "%.1f", imageEXIF.GeoLocation.Altitude);
		switch (imageEXIF.GeoLocation.AltitudeRef) {
			case 0:
				Exif[i].unit  = _SW("メートル(海抜)");
				break;
			case 1:
				Exif[i].unit  = _SW("メートル(海面下)");
				break;
			default:
				Exif[i].unit  = _SW("メートル");
				break;
		}
		Exif[i].label = _SW("高度");
		Exif[i].data  = _SW((string)buff);
		i++;
	}
	if (! imageEXIF.GeoLocation.GPSMapDatum.empty()) {
		Exif[i].label = _SW("測地系");
		Exif[i].data  = _SW(imageEXIF.GeoLocation.GPSMapDatum);
		i++;
	}
	// 画像情報
	if (imageEXIF.ImageWidth || imageEXIF.ImageHeight) {
		Exif[i].label = _SW("画像の幅");
		Exif[i].data  = _SW(to_string(imageEXIF.ImageWidth));
		Exif[i].unit  = _SW("ピクセル");
		i++;
		Exif[i].label = _SW("画像の高さ");
		Exif[i].data  = _SW(to_string(imageEXIF.ImageHeight));
		Exif[i].unit  = _SW("ピクセル");
		i++;
	}
	if (imageEXIF.RelatedImageWidth || imageEXIF.RelatedImageHeight) {
		Exif[i].label = _SW("画像の幅");
		Exif[i].data  = _SW(to_string(imageEXIF.RelatedImageWidth));
		Exif[i].unit  = _SW("ピクセル");
		i++;
		Exif[i].label = _SW("画像の高さ");
		Exif[i].data  = _SW(to_string(imageEXIF.RelatedImageHeight));
		Exif[i].unit  = _SW("ピクセル");
		i++;
	}
	if (! imageEXIF.ImageDescription.empty()) {
		Exif[i].label = _SW("画像タイトル");
		Exif[i].data  = _SW(imageEXIF.ImageDescription);
		i++;
	}
	if (! imageEXIF.Make.empty() || ! imageEXIF.Model.empty()) {
		Exif[i].label = _SW("カメラのメーカー名");
		Exif[i].data  = _SW(imageEXIF.Make);
		i++;
		Exif[i].label = _SW("カメラのモデル名");
		Exif[i].data  = _SW(imageEXIF.Model);
		i++;
	}
	if (! imageEXIF.SerialNumber.empty()) {
		Exif[i].label = _SW("シリアル番号");
		Exif[i].data  = _SW(imageEXIF.SerialNumber);
		i++;
	}
	if (imageEXIF.Orientation) {
		Exif[i].label = _SW("画像方向");
		switch (imageEXIF.Orientation) {
			case 1:
				Exif[i].data  = _SW("左上");
				break;
			case 3:
				Exif[i].data  = _SW("右下");
				break;
			case 6:
				Exif[i].data  = _SW("右上");
				break;
			case 8:
				Exif[i].data  = _SW("左下");
				break;
			default:
				Exif[i].data  = _SW("不明");
				break;
		}
		i++;
	}
	if (imageEXIF.XResolution || imageEXIF.YResolution || imageEXIF.ResolutionUnit) {
		ss = "";
		switch (imageEXIF.ResolutionUnit) {
			case 2:
				ss = "インチ";
				break;
			case 3:
				ss = "センチ";
				break;
			default:
				ss = "";
				break;
		}
		Exif[i].label = _SW("幅の解像度");
		Exif[i].data  = _SW(to_string((int)imageEXIF.XResolution));
		Exif[i].unit  = _SW(ss);
		i++;
		Exif[i].label = _SW("高さの解像度");
		Exif[i].data  = _SW(to_string((int)imageEXIF.YResolution));
		Exif[i].unit  = _SW(ss);
		i++;
	}
	if (imageEXIF.BitsPerSample) {
		Exif[i].label = _SW("画像のビットの深さ");
		Exif[i].data  = _SW(to_string(imageEXIF.BitsPerSample));
		i++;
	}
	if (! imageEXIF.Software.empty()) {
		Exif[i].label = _SW("ソ\フトウェア");
		Exif[i].data  = _SW(imageEXIF.Software);
		i++;
	}

	// 撮影日時関係
	regex pattern(R"((\d{4}):(\d{2}):(\d{2})\s+(\d{2}:\d{2}:\d{2}))");
	if (! imageEXIF.DateTime.empty()) {
		Exif[i].label = _SW("撮影日時");
		string ss = regex_replace(imageEXIF.DateTime, pattern, "$1-$2-$3T$4");
		if (! imageEXIF.OffsetTime.empty()) {
			ss += imageEXIF.OffsetTime;
		}
		Exif[i].data  = _SW(ss);
		i++;
	}
	if (! imageEXIF.DateTimeOriginal.empty()) {
		string ss = regex_replace(imageEXIF.DateTimeOriginal, pattern, "$1-$2-$3T$4");
		Exif[i].label = _SW("撮影日時(オリジナル)");
		if (! imageEXIF.OffsetTimeOriginal.empty()) {
			ss += imageEXIF.OffsetTimeOriginal;
		}
		Exif[i].data  = _SW(ss);
		i++;
	}
	if (! imageEXIF.DateTimeDigitized.empty()) {
		string ss = regex_replace(imageEXIF.DateTimeDigitized, pattern, "$1-$2-$3T$4");
		Exif[i].label = _SW("撮影日時(デジタイズ)");
		if (! imageEXIF.OffsetTimeDigitized.empty()) {
			ss += imageEXIF.OffsetTimeDigitized;
		}
		Exif[i].data  = _SW(ss);
		i++;
	}
	if (! imageEXIF.SubSecTimeOriginal.empty()) {
		Exif[i].label = _SW("撮影日時");
		Exif[i].data  = _SW(imageEXIF.SubSecTimeOriginal);
		Exif[i].unit  = _SW("ミリ秒");
		i++;
	}

	if (! imageEXIF.Copyright.empty()) {
		Exif[i].label = _SW("著作権者");
		Exif[i].data  = _SW(imageEXIF.Copyright);
		i++;
	}
	Exif[i].label = _SW("露光制御");
	switch (imageEXIF.ExposureProgram) {
		case 1:
			Exif[i].data  = _SW("マニュアル設定");
			break;
		case 2:
			Exif[i].data  = _SW("通常のプログラムAE");
			break;
		case 3:
			Exif[i].data  = _SW("絞り優先");
			break;
		case 4:
			Exif[i].data  = _SW("シャッター速度優先");
			break;
		case 5:
			Exif[i].data  = _SW("低速プログラム");
			break;
		case 6:
			Exif[i].data  = _SW("高速プログラム");
			break;
		case 7:
			Exif[i].data  = _SW("ポートレートモード");
			break;
		case 8:
			Exif[i].data  = _SW("風景モード");
			break;
		default:
			Exif[i].data  = _SW("不明");
			break;
	}
	i++;
	Exif[i].label = _SW("ISO感度");
	Exif[i].data  = _SW(to_string(imageEXIF.ISOSpeedRatings));
	i++;
	Exif[i].label = _SW("シャッター速度");
	f = 1.0 / imageEXIF.ShutterSpeedValue;
	Exif[i].data  = _SW("1/" + to_string((int)f));
	Exif[i].unit  = _SW("秒");
	i++;
	Exif[i].label = _SW("露出時間");
	f = 1.0 / imageEXIF.ExposureTime;
	Exif[i].data  = _SW("1/" + to_string((int)f));
	Exif[i].unit  = _SW("秒");
	i++;
	Exif[i].label = _SW("絞り値");
	Exif[i].data  = _SW(to_string(imageEXIF.ApertureValue));
	i++;
	Exif[i].label = _SW("F値");
	snprintf(buff, SIZE_BUFF, "%g", imageEXIF.FNumber);
	Exif[i].data  = _SW(buff);
	i++;
	Exif[i].label = _SW("レンズ焦点距離");
	Exif[i].data  = _SW(to_string((int)imageEXIF.FocalLength));
	Exif[i].unit  = _SW("ミリ");
	i++;
	if ((float)imageEXIF.BrightnessValue > 0) {
		Exif[i].label = _SW("輝度値");
		Exif[i].data  = _SW(to_string(imageEXIF.BrightnessValue));
		i++;
	}
	if ((float)imageEXIF.ExposureBiasValue > 0) {
		Exif[i].label = _SW("露出補正値");
		Exif[i].data  = _SW(to_string(imageEXIF.ExposureBiasValue));
		i++;
	}
	if ((float)imageEXIF.SubjectDistance > 0) {
		Exif[i].label = _SW("被写体距離");
		Exif[i].data  = _SW(to_string(imageEXIF.SubjectDistance));
		i++;
	}
	Exif[i].label = _SW("フラッシュ");
	switch (imageEXIF.Flash) {
		case 0:
			Exif[i].data  = _SW("非発光");
			break;
		case 1:
			Exif[i].data  = _SW("発光");
			break;
		case 5:
			Exif[i].data  = _SW("発光したが反射光検出できず");
			break;
		case 6:
			Exif[i].data  = _SW("発光して反射光検出");
			break;
		default:
			Exif[i].data  = _SW("不明");
			break;
	}
	i++;
	Exif[i].label = _SW("測光モード");
	switch (imageEXIF.MeteringMode) {
		case 0:
			Exif[i].data  = _SW("平均測光");
			break;
		case 1:
			Exif[i].data  = _SW("中央重点");
			break;
		case 2:
			Exif[i].data  = _SW("スポット");
			break;
		case 4:
			Exif[i].data  = _SW("多点スポット");
			break;
		case 5:
			Exif[i].data  = _SW("マルチセグメント");
			break;
		case 6:
			Exif[i].data  = _SW("部分測光");
			break;
		default:
			Exif[i].data  = _SW("不明");
			break;
	}
	i++;
	Exif[i].label = _SW("光源");
	switch (imageEXIF.LightSource) {
		case 1:
			Exif[i].data  = _SW("昼光");
			break;
		case 2:
			Exif[i].data  = _SW("蛍光燈");
			break;
		case 3:
			Exif[i].data  = _SW("白熱電球");
			break;
		case 4:
			Exif[i].data  = _SW("フラッシュ");
			break;
		case 9:
			Exif[i].data  = _SW("快晴");
			break;
		case 10:
			Exif[i].data  = _SW("曇天");
			break;
		case 11:
			Exif[i].data  = _SW("日陰");
			break;
		case 12:
			Exif[i].data  = _SW("昼光色");
			break;
		case 13:
			Exif[i].data  = _SW("昼白色");
			break;
		case 14:
			Exif[i].data  = _SW("蛍光色");
			break;
		case 15:
			Exif[i].data  = _SW("白色");
			break;
		case 17:
			Exif[i].data  = _SW("標準ライトA");
			break;
		case 18:
			Exif[i].data  = _SW("標準ライトB");
			break;
		case 19:
			Exif[i].data  = _SW("標準ライトC");
			break;
		case 20:
			Exif[i].data  = _SW("D55");
			break;
		case 21:
			Exif[i].data  = _SW("D65");
			break;
		case 22:
			Exif[i].data  = _SW("D75");
			break;
		case 23:
			Exif[i].data  = _SW("D50");
			break;
		case 24:
			Exif[i].data  = _SW("ISO白熱電球");
			break;
		default:
			Exif[i].data  = _SW("不明");
			break;
	}
	i++;
	if (imageEXIF.Calibration.FocalLength != 0) {
		Exif[i].label = _SW("(ピクセル)");
		Exif[i].data  = _SW(to_string(imageEXIF.Calibration.FocalLength));
		i++;
	}
	if (imageEXIF.Calibration.OpticalCenterX != 0) {
		Exif[i].label = _SW("(ピクセル)");
		Exif[i].data  = _SW(to_string(imageEXIF.Calibration.OpticalCenterX));
		i++;
	}
	if (imageEXIF.Calibration.OpticalCenterY != 0) {
		Exif[i].label = _SW("(ピクセル)");
		Exif[i].data  = _SW(to_string(imageEXIF.Calibration.OpticalCenterY));
		i++;
	}
	// レンズ情報
	if ((int)imageEXIF.LensInfo.FStopMin > 0) {
		Exif[i].label = _SW("最小絞り値");
		Exif[i].data  = _SW(to_string((int)imageEXIF.LensInfo.FStopMin));
		i++;
	}
	if ((int)imageEXIF.LensInfo.FStopMax > 0) {
		Exif[i].label = _SW("最大絞り値");
		Exif[i].data  = _SW(to_string((int)imageEXIF.LensInfo.FStopMax));
		i++;
	}
	if ((int)imageEXIF.LensInfo.FocalLengthMin > 0) {
		Exif[i].label = _SW("広角側");
		Exif[i].data  = _SW(to_string((int)imageEXIF.LensInfo.FocalLengthMin));
		Exif[i].unit  = _SW("ミリ");
		i++;
	}
	if ((int)imageEXIF.LensInfo.FocalLengthMax > 0) {
		Exif[i].label = _SW("望遠側");
		Exif[i].data  = _SW(to_string((int)imageEXIF.LensInfo.FocalLengthMax));
		Exif[i].unit  = _SW("ミリ");
		i++;
	}
	if ((int)imageEXIF.LensInfo.DigitalZoomRatio > 0) {
		Exif[i].label = _SW("デジタルズーム倍率");
		Exif[i].data  = _SW(to_string((int)imageEXIF.LensInfo.DigitalZoomRatio));
		i++;
	}
	if ((int)imageEXIF.LensInfo.FocalLengthIn35mm > 0) {
		Exif[i].label = _SW("焦点距離(35ミリ判相当");
		Exif[i].data  = _SW(to_string((int)imageEXIF.LensInfo.FocalLengthIn35mm));
		Exif[i].unit  = _SW("ミリ");
		i++;
	}
	ss = "";
	switch (imageEXIF.LensInfo.FocalPlaneResolutionUnit) {
		case 2:
			ss = "インチ";
			break;
		case 3:
			ss = "センチ";
			break;
		default:
			ss = "";
			break;
	}
	Exif[i].label = _SW("焦点面の幅の解像度");
	Exif[i].data  = _SW(to_string(imageEXIF.LensInfo.FocalPlaneXResolution));
	Exif[i].unit  = _SW(ss);
	i++;
	Exif[i].label = _SW("焦点面の高さの解像度");
	Exif[i].data  = _SW(to_string(imageEXIF.LensInfo.FocalPlaneYResolution));
	Exif[i].unit  = _SW(ss);
	i++;

	// レンズ関係の情報
	if (! imageEXIF.LensInfo.Make.empty()) {
		Exif[i].label = _SW("レンズのメーカー名");
		Exif[i].data  = _SW(imageEXIF.LensInfo.Make);
		i++;
	}
	if (! imageEXIF.LensInfo.Model.empty()) {
		Exif[i].label = _SW("レンズのモデル名");
		Exif[i].data  = _SW(imageEXIF.LensInfo.Model);
		i++;
	}

	// 位置情報
	if (imageEXIF.GeoLocation.hasRelativeAltitude()) {
		Exif[i].label = _SW("相対高度");
		Exif[i].data  = _SW(to_string(imageEXIF.GeoLocation.RelativeAltitude));
		Exif[i].unit  = _SW("メートル");
		i++;
	}
	if (imageEXIF.GeoLocation.hasOrientation()) {
		Exif[i].label = _SW("ロール角");
		Exif[i].data  = _SW(to_string(imageEXIF.GeoLocation.RollDegree));
		Exif[i].unit  = _SW("度");
		i++;
		Exif[i].label = _SW("ピッチ角");
		Exif[i].data  = _SW(to_string(imageEXIF.GeoLocation.PitchDegree));
		Exif[i].unit  = _SW("度");
		i++;
		Exif[i].label = _SW("ヨー角");
		Exif[i].data  = _SW(to_string(imageEXIF.GeoLocation.YawDegree));
		Exif[i].unit  = _SW("度");
		i++;
	}
	if (imageEXIF.GeoLocation.hasSpeed()) {
		Exif[i].label = _SW("飛行速度(X方向)");
		Exif[i].data  = _SW(to_string(imageEXIF.GeoLocation.SpeedX));
		Exif[i].unit  = _SW("メートル/秒");
		i++;
		Exif[i].label = _SW("飛行速度(Y方向)");
		Exif[i].data  = _SW(to_string(imageEXIF.GeoLocation.SpeedY));
		Exif[i].unit  = _SW("メートル/秒");
		i++;
		Exif[i].label = _SW("飛行速度(Z方向)");
		Exif[i].data  = _SW(to_string(imageEXIF.GeoLocation.SpeedZ));
		Exif[i].unit  = _SW("メートル/秒");
		i++;
	}
	if (imageEXIF.GeoLocation.AccuracyXY > 0) {
		Exif[i].label = _SW("GPS精度(XY方向)");
		Exif[i].data  = _SW(to_string(imageEXIF.GeoLocation.AccuracyXY));
		Exif[i].unit  = _SW("メートル");
		i++;
	}
	if (imageEXIF.GeoLocation.AccuracyZ > 0) {
		Exif[i].label = _SW("GPS精度(Z方向)");
		Exif[i].data  = _SW(to_string(imageEXIF.GeoLocation.AccuracyZ));
		Exif[i].unit  = _SW("メートル");
		i++;
	}

	if (imageEXIF.GeoLocation.GPSDOP >= 0) {
		Exif[i].label = _SW("GPS精度低下率");
		snprintf(buff, SIZE_BUFF, "%g", imageEXIF.GeoLocation.GPSDOP);
		Exif[i].data  = _SW(buff);
		i++;
	}
	if (imageEXIF.GeoLocation.GPSDifferential >= 0) {
		Exif[i].label = _SW("GPS差分補正");
		switch (imageEXIF.GeoLocation.GPSDifferential) {
			case 0:
				ss = "差分補正なし";
				break;
			case 1:
				ss = "差分補正適用";
				break;
			default:
				ss = "不明";
				break;
		}
		Exif[i].data  = _SW(ss);
		i++;
	}

	// GPS日時関係
	if (! imageEXIF.GeoLocation.GPSDateStamp.empty()) {
		regex pattern(R"((\d{4}):(\d{2}):(\d{2}))");
		string ss = regex_replace(imageEXIF.GeoLocation.GPSDateStamp, pattern, "$1-$2-$3T");
		if (! imageEXIF.GeoLocation.GPSTimeStamp.empty()) {
			istringstream iss((string)imageEXIF.GeoLocation.GPSTimeStamp);
			int hour, min, sec;
			iss >> hour >> min >> sec;
			snprintf(buff, SIZE_BUFF, "T%02d:%02d:%02dZ", hour, min, sec);
			ss += buff;
		}
		Exif[i].label = _SW("GPS日時");
		Exif[i].data  = _SW(ss);
		i++;
	}

	return ret;
}

/**
 * 画像ファイルを開いてExif情報を取得する（ダイアログ処理）
 * @param	HWND  hWnd      表示するウィンドウ・ハンドル
 * @param	char *loadfname 画像ファイル名を格納
 * @return	bool TRUE：Exif情報取得成功／FALSE：失敗
*/
bool loadImageAndExif(HWND hWnd, char *loadfname) {
	static char fname[MAX_PATH + 1];
	strcpy(fname, "");
	OPENFILENAME of;
	bool ret = TRUE;

	// OPENFILENAME構造体のサイズをセット
	memset(&of, 0, sizeof(OPENFILENAME));
	of.lStructSize = sizeof(OPENFILENAME);
	// ダイアログボックスを所有するウィンドウへのハンドル
	of.hwndOwner = hParent;
	of.lpstrFilter = TEXT("JPEGファイル(*.jpg;*.jpeg)\0*.jpg;*.jpeg\0\0");
	// ファイル名を格納したバッファのアドレス
	of.lpstrFile = (LPTSTR)fname;
	// lpstrFileメンバで指定されるバッファのサイズ
	of.nMaxFile = MAX_PATH;
	of.Flags = OFN_OVERWRITEPROMPT;
	// デフォルトの拡張子を格納したバッファのアドレス
	of.lpstrDefExt = TEXT("jpg");
	// コモンダイアログの表示
	GetOpenFileName(&of);

	// 画像表示
	ret = onPaint(hWnd, fname);
	if (ret == TRUE) {
		// 画像ファイル読み込み
		ret = loadExif(fname);
	}
	// ファイル名をコピー
	strncpy(loadfname, fname, MAX_PATH);

	return ret;
}

// 地図表示（ブラウザ） ======================================================
/**
 * 地図表示用HTML生成
 * @param	int cnt 表示するインフォメーション数
 * @return	string 生成したHTML文
 */
string makeMapHTML(int cnt=0) {
//	if (pGC->GoogleAPIkey.length() == 0) {
//		Maptype = DEF_MAPTYPE;
//	}
	string script = pGC->makeMapLeaflet(MAP_ID, Longitude, Latitude, Zoom, Maptype, pGC->Ppoints, cnt);
	string html = (boost::format(R"(<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="SJIS">
<title>フォトマップ</title>
<meta name="author" content="studio pahoo" />
<meta name="copyright" content="studio pahoo" />
<meta name="ROBOTS" content="NOINDEX,NOFOLLOW" />
<meta http-equiv="pragma" content="no-cache">
<meta http-equiv="cache-control" content="no-cache">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
%8%
</head>
<body>
<div id="%1%" style="width:%2%px; height:%3%px;"></div>
<form>
<input id="latitude"  type="hidden" value="%4%" />
<input id="longitude" type="hidden" value="%5%" />
<input id="zoom"      type="hidden" value="%6%" />
<input id="maptype"   type="hidden" value="%7%" />
</form>
</body>
</html>
)")
% MAP_ID						// 地図ID
% MAP_WIDTH						// 地図の幅
% MAP_HEIGHT					// 地図の高さ
% Latitude						// 緯度
% Longitude						// 経度
% Zoom							// 地図拡大率
% Maptype						// 地図タイプ
% script						// 地図描画スクリプト
).str();

	return html;
}

/**
 * エラーメッセージをHTML文にする
 * @param	なし
 * @return	string HTML文
*/
string makeErrorHTML(void) {
	string msg;
	if (pGC->isError()) {
		msg = _WS(pGC->getError());
	} else {
		msg = ErrorMessage;
	}
	return (boost::format(R"(<!DOCTYPE html>
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="SJIS">
<title>エラー発生</title>
<meta name="author" content="studio pahoo" />
<meta name="copyright" content="studio pahoo" />
<meta name="ROBOTS" content="NOINDEX,NOFOLLOW" />
<meta http-equiv="pragma" content="no-cache">
<meta http-equiv="cache-control" content="no-cache">
</head>
<body>
<p style="color:red; font-size:large;">%1%</p>
</body>
</html>
)")
% ErrorMessage
).str();
}

/**
 * ブラウザ・コントロールを表示
 * @param	const char* tmpname 読み込むHTMLファイル名
 * @return	なし
*/
void viewBrowser(const char* tmpname) {
	string html;
	string fname;
	ofstream ofs;

	// 表示用HTMLファイル作成
	if ((ErrorMessage == "") && (! pGC->isError())) {
		html = makeMapHTML(0);
	} else {
		html = makeErrorHTML();
	}
	ofs.open(tmpname);
	ofs << html;
	ofs.close();

	fname = (string)tmpname;
	std::replace(fname.begin(), fname.end(), '\\', '/');

	if (isWeb2Ready()) {
		webView2->Reload();
	}
}

// リストビューの列
enum {
	COL_LABEL,
	COL_DATA
};

/**
 * Exif一覧のフレーム作成
 * @param	HWND hDlg  ウィンドウハンドル
 * @return	なし
*/
void makeListViewFrame(HWND hWnd) {
	LVCOLUMNA lvcol;
	lvcol.mask = LVCF_TEXT | LVCF_SUBITEM | LVCF_WIDTH | LVCF_FMT;
	lvcol.fmt = LVCFMT_LEFT;

	lvcol.cx = 180;
	lvcol.pszText = (char *)"項目";
	SendMessage(hWnd, LVM_INSERTCOLUMNA, COL_LABEL, (WPARAM)&lvcol);
	lvcol.cx = 180;
	lvcol.pszText = (char *)"内容";
	SendMessage(hWnd, LVM_INSERTCOLUMNA, COL_DATA, (WPARAM)&lvcol);

	// 罫線表示＋1行選択
	DWORD dwStyle = ListView_GetExtendedListViewStyle(hWnd);
	dwStyle = dwStyle | LVS_EX_GRIDLINES | LVS_EX_ONECLICKACTIVATE | LVS_EX_FULLROWSELECT;
	ListView_SetExtendedListViewStyle(hWnd, dwStyle);
}

/**
 * Exif一覧作成
 * @param	HWND hDlg  ウィンドウハンドル
 * @return	なし
*/
void makeListView(HWND hWnd) {
	LVITEMA item;

	// 初期化
	for (int i = 0; i < SIZE_EXIFS; i++) {
		item.iItem = i;
		SendMessage(hWnd, LVM_DELETEITEM, 0,(WPARAM)&item);
	}

	// データ行
	for (int i = 0; i < SIZE_EXIFS; i++) {
		if (Exif[i].label.length() == 0)	break;
		// データ項目
		item.mask = LVIF_TEXT;
		item.iItem = i;
		item.iSubItem = COL_LABEL;
		item.pszText = (LPSTR)_WS(Exif[i].label).c_str();
		SendMessage(hWnd, LVM_INSERTITEMA, 0,(WPARAM)&item);
		// データ本体
		item.iSubItem = COL_DATA;
		item.pszText = (LPSTR)_WS(Exif[i].data + Exif[i].unit).c_str();
		SendMessage(hWnd, LVM_SETITEMA, 0,(WPARAM)&item);
	}
}

/**
 * 位置情報コピー
 * @param	なし
 * @return	なし
*/
void copyLocationData(void) {
	string str = "";

	str = "緯度(度)：" + to_string(Latitude);
	str += "\n経度(度)：" + to_string(Longitude);

	setClipboardData(str);
}

/**
 * 全情報コピー
 * @param	なし
 * @return	なし
*/
void copyAllData(void) {
	string str = "";

	// データ行
	for (int i = 0; i < SIZE_EXIFS; i++) {
		if (Exif[i].label.length() == 0)	break;
		// データ項目
		str += _WS(Exif[i].label) + "：" + _WS(Exif[i].data) + _WS(Exif[i].unit) + "\n";
	}
	setClipboardData(str);
}

// WebView2制御用関数群 ======================================================
/**
 * WebView2：ダイアログを初期化する
 * @param	HWND hDlg	親ウィンドウ・ハンドラ
 * @return	なし
*/
void initDialog(HWND hDlg) {
	HICON hIcon;
	ULONG_PTR lpToken;
	GdiplusStartupInput gpSI;
	hIcon = (HICON)LoadImage(hInst, MAKEINTRESOURCE(IDI_ICON), IMAGE_ICON, 16, 16, 0);
	SendMessage(hDlg, WM_SETICON, ICON_SMALL, (LPARAM)hIcon);
	ErrorMessage = "";

	// カーソルを砂時計に
	SetCursor(LoadCursor(NULL, IDC_WAIT));
	// オプション読み込み
	loadParameter();
	// アプリケーション・ウィンドウ移動
	SetWindowPos(hParent, NULL, hParent_X, hParent_Y, 0, 0, (SWP_NOSIZE | SWP_NOZORDER | SWP_NOOWNERZORDER));

	SendMessage(hParent, WM_SETICON, ICON_SMALL, (LPARAM)hIcon);
	makeListViewFrame(GetDlgItem(hDlg, IDC_LISTVIEW_EXIF));
	// GDI+初期化
	if (GdiplusStartup(&lpToken, &gpSI, NULL) != Gdiplus::Ok) {
		return;
	}

	// テンポラリ・ファイル名取得
	getTempFname(tmpFname);
	size_t wLen = 0;
	mbstowcs_s(&wLen, wUri, MAX_PATH * 2, tmpFname, MAX_PATH);
	// WebView2生成
	hWebView2 = createWebView2(hInst, hDlg, 10, 235, MAP_WIDTH + 20, MAP_HEIGHT + 20, wUri);

	// Leafletが利用可能かどうか調べる
	const string url = "https://unpkg.com/leaflet@latest/";
	string contents = "";
	int httpStatus = 0;
	bool res = readWebContents(url, UserAgent, &contents, &httpStatus);
	if (res == FALSE) {
		ErrorMessage = "地図描画ライブラリ（Leaflet）が利用できません";
	} else if ((httpStatus < 200) || (httpStatus > 299)) {
		ErrorMessage = "地図描画ライブラリ（Leaflet）が利用できません";
	}

	// メッセージ・ループ
	MSG msg;
	static bool flagWebView2 = false;
	while (GetMessage(&msg, nullptr, 0, 0)) {
		if (msg.message == WM_QUIT) {
			// オプション保存
			// saveOption();
			DestroyWindow(hDlg);
		}
		TranslateMessage(&msg);
		DispatchMessage(&msg);
		if (!flagWebView2 && isWeb2Ready()) {
			// カーソルを砂時計に
			SetCursor(LoadCursor(NULL, IDC_WAIT));
			// ブラウザ・コントロールに表示
			viewBrowser(tmpFname);
			webView2->Reload();
			flagWebView2 = true;
		}
	}
}

/**
 * WebView2のパラメータを取り出す．
 * selectActionの値によって動作を変える。
 * @param	char* result	JavaScript終了ハンドラから渡るデータ
 * @return	なし
*/
void execScriptCompleted(string result) {
	// パラメータを読み込む
	vector<string> tokens;
	split(tokens, result, is_any_of(",\""));
	int cnt = 0;
	for (string ss: tokens) {
		switch (cnt) {
			case 1:
				Longitude = stod(ss);
				break;
			case 2:
				Latitude = stod(ss);
				break;
			case 3:
				Zoom = stoi(ss);
				break;
			case 4:
				Maptype = ss;
				break;
		}
		cnt++;
	}

	switch (selectAction) {
		// プログラムを終了する．
		case eAction::Finish:
			// テンポラリ・ファイルを削除する．
			remove(tmpFname);
			// パラメータを保存する．
			saveParameter();
			// GDI+終了
			GdiplusShutdown(lpToken);
			// プログラムを終了する
			EndDialog(hParent, 0);
			DestroyWindow(hParent);
			break;
		// ブラウザ・コントロールに表示
		default:
			viewBrowser(tmpFname);
			break;
	}
//	cout << "Longitude: " << Longitude << endl;
//	cout << "Latitude: " << Latitude << endl;
//	cout << "Zoom: " << Zoom << endl;
//	cout << "Maptype: " << Maptype << endl;
}

/**
 * WebView2：JavaScript終了ハンドラ
 * @param	HWND hDlg			親ウィンドウ・ハンドラ
 * @paramm	UINT uMsg			メッセージ識別子
 * @param	WPARAM wParam		メッセージの最初のパラメータ
 * @paramL	PARAM lParam		メッセージの2番目のパラメータ
 * @return	INT_PTR CALLBACK	TRUE：メッセージ処理完了／FALSE：未完了
*/
class MyScriptCompletedHandler : public ICoreWebView2ExecuteScriptCompletedHandler {
public:
	HRESULT STDMETHODCALLTYPE Invoke(HRESULT error, PCWSTR result) override {
		if (SUCCEEDED(error)) {
			// PCWSTR → char* 変換
			int requiredSize = WideCharToMultiByte(CP_UTF8, 0, result, -1, nullptr, 0, nullptr, nullptr);
			char* buffer = new char[requiredSize];
			WideCharToMultiByte(CP_UTF8, 0, result, -1, buffer, requiredSize, nullptr, nullptr);
			std::string res(buffer);
			// JavaScript終了時に実行する関数
			execScriptCompleted(res);
		}
		return S_OK;
	}

	HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppvObject) override {
		return E_NOINTERFACE;
	}

	ULONG STDMETHODCALLTYPE AddRef(void) override {
		return 1;
	}

	ULONG STDMETHODCALLTYPE Release(void) override {
		return 1;
	}
};

// WebView2のJavaScriptを実行してDOM要素を取得
MyScriptCompletedHandler* scriptCompletedHandler = new MyScriptCompletedHandler();

/**
 * WebView2でスクリプトを実行した後，selectActionで指定したアクションを実行する．
 * @param	なし
 * @param	なし
 * @return	なし
*/
void execScriptAndAction(void) {
	// プログラム終了時に実行するJavaScript
	LPCWSTR js = L"document.getElementById('longitude').value + ',' + document.getElementById('latitude').value + ',' + document.getElementById('zoom').value + ',' + document.getElementById('maptype').value;";

	webView2->ExecuteScript(js, scriptCompletedHandler);
}

// メインウィンドウ ==========================================================
/**
 * イベントハンドラ：メインウィンドウ
 * @param	HWND hDlg			親ウィンドウ・ハンドラ
 * @paramm	UINT uMsg			メッセージ識別子
 * @param	WPARAM wParam		メッセージの最初のパラメータ
 * @paramL	PARAM lParam		メッセージの2番目のパラメータ
 * @return	INT_PTR CALLBACK	TRUE：メッセージ処理完了／FALSE：未完了
*/
INT_PTR CALLBACK processMain(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
	HWND hImage;
	GdiplusStartupInput gpSI;
	static char fname[MAX_PATH + 1];
	wstring basename;
	wstring val;

	switch(uMsg) {
	// ダイアログ初期化
	case WM_INITDIALOG:
		break;

	// ボタン押下
	case WM_COMMAND:
		 switch (LOWORD(wParam)) {
		// 画像ファイル読み込み
		case IDM_LOAD:
		case IDC_BUTTON_LOAD:
			ErrorMessage = "";
			// カーソルを砂時計に
			SetCursor(LoadCursor(NULL, IDC_WAIT));
			// WebView2のパラメータを取得する．
			execScriptAndAction();
			// 画像表示とExif情報取得
			hImage = GetDlgItem(hDlg, IDC_TEXT_IMAGE);
			loadImageAndExif(hImage, fname);
			// 一覧表示
			makeListView(GetDlgItem(hDlg, IDC_LISTVIEW_EXIF));
			// ブラウザ・コントロールに表示
			viewBrowser(tmpFname);
			// エラー・リセット
			ErrorMessage = "";
			pGC->resetError();
			break;
		// 保存
		case IDC_BUTTON_SAVE:
		case IDM_SAVE:
			basename = wgetBasename(_SW(fname)) + _SW(".csv");
			saveCSV((char *)_WS(basename).c_str());
			break;
		// GoogleAPIキー保存
		case IDM_GOOGLE_APIKEY:
			createSetAPIkey(hDlg, LABEL_GOOGLE_API, FNAME_GOOGLE_API, URL_GOOGLE_API, &pGC->GoogleAPIkey);
			pGC->readGoogleApiKey();
			// エラー・リセット
			ErrorMessage = "";
			pGC->resetError();
			// GoogleAPIキー未登録ならデフォルトのマップタイプに戻す．
			Maptype = pGC->isGoogleApiKey() ? "GMRD" : DEF_MAPTYPE;
			// ブラウザ・コントロールに表示
			viewBrowser(tmpFname);
			break;
		// 設定クリア＋アプリ終了
		case IDM_CLEAR_PARAMETER:
			delParameter();
			// テンポラリ・ファイル削除
			remove(tmpFname);
			// GDI+終了
			GdiplusShutdown(lpToken);
			EndDialog(hDlg, 0);
			return 0;
			break;
		// ヘルプ
		case IDM_HELP:
			ShellExecute(hDlg, _T("open"), _T(HELPFILE), NULL, NULL, SW_RESTORE);
			break;
		// バージョン表示
		case IDM_VERSION:
			createHelp(hDlg, processHelp);
			break;
		// 技術情報
		case IDM_PAHOO:
			ShellExecute(NULL, _T("open"), _T(REFERENCE), NULL, NULL, SW_RESTORE);
			break;
		// 位置情報コピー
		case IDC_BUTTON_COPY1:
		case IDM_COPY1:
			copyLocationData();
			break;
		// 全情報コピー
		case IDC_BUTTON_COPY2:
		case IDM_COPY2:
			copyAllData();
			break;
		// プログラム終了
		case IDM_QUIT:
			selectAction = eAction::Finish;
			execScriptAndAction();
			break;
		default:
			return 1;
		}
		break;

	// プログラム終了
	case WM_CLOSE:
		selectAction = eAction::Finish;
		execScriptAndAction();
		break;
	}
	return 0;
}

// メイン・プログラム =======================================================
/**
 * Windowsメインプログラム
 * @param	HINSTANCE hInstance			インスタンスハンドル
 * @paramm	HINSTANCE hPrevInstance		未使用(常にNULL)：Win16時代の名残
 * @param	LPSTR lpCmdLine				コマンドライン引数
 * @paramL	int nShowCmd				ウィンドウの表示方法
 * @return	int リターンコード
*/
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) {
	LoadLibrary("RICHED20.DLL");

	// UserAgent生成
	static OSVERSIONINFOEX os;
	GetVersion2(&os);
	UserAgent = (string)"Mozilla/5.0 (" + APPNAME + "/"
		+ APPVERSION + "/" + MAKER
		+ ", Windows NT " + to_string(os.dwMajorVersion) + "."
		+ to_string(os.dwMinorVersion) + ")";

	// pahooGeocodeオブジェクト
	pGC = new pahooGeocode(APPNAME);

	// メイン・ダイアログ
	HWND hDlg = CreateDialog(hInstance, MAKEINTRESOURCE(IDD_MAIN), NULL, (DLGPROC)processMain);
	hInst = hInstance;
	hParent = hDlg;
	// メイン・ダイアログを初期化する．
	initDialog(hDlg);

	// オブジェクト解放
	delete pGC;

	return 0;
}

/*
** バージョンアップ履歴 =====================================================
 *
 * @version 2.4.2  2025/10/25 使用ライブラリ更新
 * @version 2.4.1  2025/06/07 使用ライブラリ更新
 * @version 2.4.0  2025/03/15 Leafletアクセス可否チェック追加, UserAgent追加, 使用ライブラリ更新
 * @version 2.3.0  2024/10/26 loadImageOnMemory()--Orientation情報があれば表示画像を回転させる
 * @version 2.2.0  2024/10/13 UTCとの時差，その他表記方法見直し，使用ライブラリ更新
 * @version 2.1.1  2024/08/17 使用ライブラリ更新
 * @version 2.1.0  2024/05/03 enum eActionクラス導入，API入力処理を改良
 * @version 2.0.0  2024/04/27 Edgeブラウザ対応，64ビット対応
 * @version 1.5.5  2024/03/23 使用ライブラリ更新
 * @version 1.5.4  2023/09/20 使用ライブラリ更新
 * @version 1.5.3  2023/07/02 使用ライブラリ更新
 * @version 1.5.2  2023/03/18 使用ライブラリ更新
 * @version 1.5.1  2022/11/20 使用ライブラリ更新
 * @version 1.5.0  2022/09/24 設定クリア機能を追加，ライブラリ更新
 * @version 1.4.2  2022/04/16 libcurl・OpenSSLを最新版に更新，描画領域クリア
 * @version 1.4.1  2022/02/26 地図の種類を保存できないことがある不具合を修正
 * @version 1.4    2021/12/05 ウィンドウ位置を保存
 * @version 1.3    2021/11/13 Googleマップ非使用時にmaptypeを保存できるように
 * @version 1.2    2020/12/20 leafletスクリプトの参照URL変更
 * @version 1.1    2020/11/22 パラメータ保存機能追加
 * @version 1.0.1  2020/11/22 apikey.cpp, mystrings.cpp, pahooGeocode.cpp 分離
 * @version 1.0    2020/09/20 初版
*/
