1234 lines
32 KiB
C++
Executable File
1234 lines
32 KiB
C++
Executable File
#include "wxinc.h"
|
|
#include "wx/filename.h"
|
|
#include "wx/image.h"
|
|
#include "wx/paper.h"
|
|
#include "wx/printdlg.h"
|
|
#include "wx/msw/helpchm.h"
|
|
|
|
#include "oswin32.h"
|
|
|
|
#include "xvt_menu.h"
|
|
#include "xvt_help.h"
|
|
#include "xvtart.h"
|
|
|
|
#include "xvt_defs.h"
|
|
#include "xvt_env.h"
|
|
#include "xvt_type.h"
|
|
#include "xvtwin.h"
|
|
|
|
#include <aclapi.h>
|
|
#include <psapi.h>
|
|
#include <shlobj.h>
|
|
|
|
bool OsWin32_CheckPrinterInfo(const void* data, unsigned int size)
|
|
{
|
|
bool ok = data != NULL;
|
|
if (ok)
|
|
{
|
|
LPDEVMODE pdm = (LPDEVMODE)data;
|
|
const unsigned int s = pdm->dmSize + pdm->dmDriverExtra;
|
|
ok = s > 0 && s == size;
|
|
}
|
|
return ok;
|
|
}
|
|
|
|
static int AdjustDevmodePlease(PDEVMODE dm)
|
|
{
|
|
// Forse dovremmo fare una Black List su file delle stampanti incompatibili):
|
|
// 1) "NRG SP 4100N PCL 5e"
|
|
// 2) aggiungere qui le altre
|
|
// Ma per ora zappiamo tutti i driver troppo grandi (> 3Kb)
|
|
if (dm->dmDriverExtra > 3*1024)
|
|
dm->dmDriverExtra = 0;
|
|
|
|
// Controllo il formato della carta
|
|
wxPrintPaperType* paper = wxThePrintPaperDatabase->FindPaperTypeByPlatformId(dm->dmPaperSize);
|
|
if (paper == NULL)
|
|
{
|
|
dm->dmFields |= DM_PAPERSIZE;
|
|
wxThePrintPaperDatabase->WXADDPAPER((wxPaperSize)dm->dmPaperSize /*wxPAPER_NONE*/, dm->dmPaperSize,
|
|
dm->dmFormName, dm->dmPaperWidth, dm->dmPaperLength);
|
|
}
|
|
|
|
return dm->dmSize + dm->dmDriverExtra;
|
|
}
|
|
|
|
void* OsWin32_ConvertFromNativePrinterInfo(void* hGlobal, unsigned int& nDataSize)
|
|
{
|
|
void* buff = NULL;
|
|
if (hGlobal != NULL)
|
|
{
|
|
PDEVMODE dm = (PDEVMODE)::GlobalLock(hGlobal);
|
|
nDataSize = AdjustDevmodePlease(dm);
|
|
buff = new char[nDataSize];
|
|
memcpy(buff, dm, nDataSize);
|
|
::GlobalUnlock(hGlobal);
|
|
}
|
|
return buff;
|
|
}
|
|
|
|
void* OsWin32_ConvertToNativePrinterInfo(void* data, unsigned int nDataSize)
|
|
{
|
|
HGLOBAL hGlobal = ::GlobalAlloc(GHND, nDataSize); // Alloco lo spazio necessario
|
|
if (hGlobal != NULL)
|
|
{
|
|
PDEVMODE dm = (PDEVMODE)::GlobalLock(hGlobal); // Trasformo l'handle in puntatore
|
|
memcpy(dm, data, nDataSize); // Ricopio i dati della stampante
|
|
const unsigned int sz = AdjustDevmodePlease(dm); // Metto a posto parametri non standard
|
|
wxASSERT(nDataSize == sz);
|
|
::GlobalUnlock(hGlobal); // Libero il lock sull'handle
|
|
}
|
|
return hGlobal;
|
|
}
|
|
|
|
struct XvtData
|
|
{
|
|
char** families;
|
|
long* sizes;
|
|
short* scalable;
|
|
int max_count;
|
|
int cur_count;
|
|
|
|
XvtData() { memset(this, 0, sizeof(XvtData)); }
|
|
};
|
|
|
|
int CALLBACK FamilyEnumerator(
|
|
const LOGFONT *plf, // pointer to logical-font data
|
|
const TEXTMETRIC * WXUNUSED(lpntme), // pointer to physical-font data
|
|
unsigned long WXUNUSED(FontType), // type of font
|
|
LPARAM lParam // application-defined data
|
|
)
|
|
{
|
|
XvtData* d = (XvtData*)lParam;
|
|
int& n = d->cur_count;
|
|
int i;
|
|
for (i = n-1; i >= 0 && wxStricmp(d->families[i], plf->lfFaceName); i--);
|
|
if (i < 0) // Controlla che il nome del font non ci sia gia'
|
|
d->families[n++] = wxStrdup(plf->lfFaceName);
|
|
return n < d->max_count;
|
|
}
|
|
|
|
int CALLBACK SizeEnumerator(
|
|
const LOGFONT *plf, // pointer to logical-font data
|
|
const TEXTMETRIC *lpntme, // pointer to physical-font data
|
|
unsigned long WXUNUSED(FontType), // type of font
|
|
LPARAM lParam // application-defined data
|
|
)
|
|
{
|
|
XvtData* d = (XvtData*)lParam;
|
|
int& i = d->cur_count;
|
|
int size = (plf->lfHeight+5) / 10;
|
|
if (size <= 0)
|
|
{
|
|
for (const char* n = plf->lfFaceName; *n; n++)
|
|
if (*n >= '1' && *n <= '9')
|
|
{
|
|
size = int(120.0 / atoi(n) + 0.5);
|
|
break;
|
|
}
|
|
if (size <= 0)
|
|
size = 12;
|
|
}
|
|
if (i == 0 || size > d->sizes[i-1])
|
|
{
|
|
d->sizes[i] = size;
|
|
if (lpntme->tmPitchAndFamily & TMPF_TRUETYPE)
|
|
*d->scalable = TRUE;
|
|
i++;
|
|
}
|
|
return i < d->max_count;
|
|
}
|
|
|
|
int FamilySorter(const void* p1,const void* p2)
|
|
{
|
|
const char* s1 = *(const char**)p1;
|
|
const char* s2 = *(const char**)p2;
|
|
return wxStricmp(s1, s2);
|
|
}
|
|
|
|
int OsWin32_EnumerateFamilies(WXHDC hDC, char** families, int max_count)
|
|
{
|
|
XvtData data;
|
|
data.families = families;
|
|
data.max_count = max_count;
|
|
LOGFONT lf; memset(&lf, 0, sizeof(lf));
|
|
lf.lfCharSet = DEFAULT_CHARSET;
|
|
::EnumFontFamiliesEx((HDC)hDC, &lf, FamilyEnumerator, (LPARAM)&data, 0);
|
|
qsort(families, data.cur_count, sizeof(char*), FamilySorter);
|
|
return data.cur_count;
|
|
}
|
|
|
|
int OsWin32_EnumerateSizes(WXHDC hDC, const char* name, long* sizes, short* scalable, int max_count)
|
|
{
|
|
XvtData data;
|
|
data.sizes = sizes;
|
|
data.scalable = scalable;
|
|
data.max_count = max_count;
|
|
LOGFONT lf; memset(&lf, 0, sizeof(lf));
|
|
lf.lfCharSet = DEFAULT_CHARSET;
|
|
strcpy(lf.lfFaceName, name);
|
|
::EnumFontFamiliesEx((HDC)hDC, &lf, SizeEnumerator, (LPARAM)&data, 0);
|
|
|
|
return data.cur_count;
|
|
}
|
|
|
|
void* OsWin32_GetPrinterInfo(int& size, const char* printer)
|
|
{
|
|
LPDEVMODE pdm = NULL;
|
|
size = 0;
|
|
|
|
char name[_MAX_PATH] = "";
|
|
if (printer == NULL || *printer == '\0')
|
|
{
|
|
unsigned long namelen = _MAX_PATH;
|
|
::GetDefaultPrinter(name, &namelen);
|
|
}
|
|
else
|
|
wxStrncpy(name, printer, sizeof(name));
|
|
|
|
HANDLE hPrinter = NULL;
|
|
if (::OpenPrinter(name, &hPrinter, NULL))
|
|
{
|
|
size = ::DocumentProperties(0, hPrinter, name, NULL, NULL, 0); // Determina dimensione DEVMODE
|
|
if (size > 0)
|
|
{
|
|
pdm = (LPDEVMODE)new BYTE[size]; // Alloca un DEVMODE sufficientemente capiente
|
|
memset(pdm, 0, size); // Azzera tutto per bene
|
|
::DocumentProperties(0, hPrinter, name, pdm, NULL, DM_OUT_BUFFER); // Legge DEVMODE
|
|
size = AdjustDevmodePlease(pdm);
|
|
}
|
|
else
|
|
size = 0;
|
|
::ClosePrinter(hPrinter);
|
|
}
|
|
return pdm;
|
|
}
|
|
|
|
void OsWin32_SetCaptionStyle(WXHWND handle, long style)
|
|
{
|
|
HWND hWnd = (HWND)handle;
|
|
LONG s = ::GetWindowLong(hWnd, GWL_STYLE);
|
|
|
|
if (style & wxSYSTEM_MENU)
|
|
s |= WS_CAPTION;
|
|
else
|
|
s &= ~WS_CAPTION;
|
|
|
|
if (style & wxCLOSE_BOX)
|
|
s |= WS_SYSMENU;
|
|
else
|
|
s &= ~WS_SYSMENU;
|
|
|
|
s |= WS_CLIPSIBLINGS; // Forzatura necessaria da wx261
|
|
::SetWindowLong(hWnd, GWL_STYLE, s);
|
|
|
|
if (style & wxCLOSE_BOX)
|
|
{
|
|
HMENU hMenu = ::GetSystemMenu(hWnd, FALSE);
|
|
::EnableMenuItem(hMenu, SC_CLOSE, MF_BYCOMMAND | MF_ENABLED);
|
|
|
|
WXHICON hIcon = xvtart_GetIconResource(0).GetHICON();
|
|
::SendMessage(hWnd, WM_SETICON, ICON_SMALL, (LPARAM)hIcon);
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////
|
|
// Drawing bitmaps
|
|
///////////////////////////////////////////////////////////
|
|
|
|
HBITMAP OsWin32_CreateBitmap(const wxImage& img, wxDC& dc)
|
|
{
|
|
static wxPalette pal;
|
|
|
|
HDC hDC = (HDC)dc.GetHDC();
|
|
int nDepth = dc.GetDepth();
|
|
|
|
// Altrimenti le stampanti in B/N perdono i toni di grigio
|
|
if (nDepth == 1 && !OsWin32_IsWindowsServer())
|
|
{
|
|
hDC = NULL;
|
|
nDepth = 24;
|
|
}
|
|
|
|
if (nDepth == 8)
|
|
{
|
|
if (!pal.Ok())
|
|
{
|
|
unsigned char red[256], green[256], blue[256];
|
|
PALETTEENTRY pe[256]; memset(pe, 0, sizeof(pe));
|
|
UINT nEntries = ::GetSystemPaletteEntries(hDC, 0, 256, pe);
|
|
for (UINT i = 0; i < nEntries; i++)
|
|
{
|
|
red[i] = pe[i].peRed;
|
|
green[i] = pe[i].peGreen;
|
|
blue[i] = pe[i].peBlue;
|
|
}
|
|
pal.Create(nEntries, red, green, blue);
|
|
}
|
|
dc.SetPalette(pal);
|
|
}
|
|
|
|
const int nWidth = img.GetWidth();
|
|
const int nHeight = img.GetHeight();
|
|
|
|
int nBytesPerLine = 0;
|
|
int nPadding = 0;
|
|
int nBitCount = 24;
|
|
|
|
switch (nDepth)
|
|
{
|
|
case 32: // Better if > Win98 :-) too
|
|
nBitCount = 32;
|
|
nBytesPerLine = nWidth*4;
|
|
break;
|
|
default:
|
|
nBytesPerLine = nWidth*3;
|
|
break;
|
|
}
|
|
const int nResto = nBytesPerLine % 4;
|
|
if (nResto != 0)
|
|
{
|
|
nPadding = 4 - nResto;
|
|
nBytesPerLine += nPadding;
|
|
}
|
|
|
|
const int nImageSize = nHeight*nBytesPerLine;
|
|
|
|
// Create the DIB section
|
|
unsigned char* pbits = (unsigned char*)calloc(nImageSize, 1);
|
|
const size_t bi_size = sizeof(BITMAPINFOHEADER);
|
|
BITMAPINFO* bi = (BITMAPINFO*)calloc(bi_size, 1);
|
|
bi->bmiHeader.biSize = bi_size;
|
|
bi->bmiHeader.biWidth = nWidth;
|
|
bi->bmiHeader.biHeight = -nHeight;
|
|
bi->bmiHeader.biCompression = BI_RGB;
|
|
bi->bmiHeader.biPlanes = 1;
|
|
bi->bmiHeader.biBitCount = nBitCount;
|
|
bi->bmiHeader.biSizeImage = nImageSize;
|
|
|
|
switch (nBitCount)
|
|
{
|
|
case 24:
|
|
{
|
|
unsigned char* d = img.GetData();
|
|
unsigned char* p = pbits;
|
|
for (int y = 0; y < nHeight; y++)
|
|
{
|
|
for (int x = 0; x < nWidth; x++)
|
|
{
|
|
*(p++) = *(d+2);
|
|
*(p++) = *(d+1);
|
|
*(p++) = *(d+0);
|
|
d += 3;
|
|
}
|
|
for (int i = 0; i < nPadding; i++)
|
|
*(p++) = 0;
|
|
}
|
|
}
|
|
break;
|
|
case 32:
|
|
{
|
|
unsigned char* d = img.GetData();
|
|
unsigned char* p = pbits;
|
|
for (int y = 0; y < nHeight; y++)
|
|
{
|
|
for (int x = 0; x < nWidth; x++)
|
|
{
|
|
*(p++) = *(d+2);
|
|
*(p++) = *(d+1);
|
|
*(p++) = *(d+0);
|
|
*(p++) = 0;
|
|
d += 3;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
HBITMAP hBitmap = ::CreateCompatibleBitmap(hDC, nWidth, nHeight);
|
|
if (hBitmap)
|
|
{
|
|
HDC memdc = ::CreateCompatibleDC( hDC );
|
|
HBITMAP hOldBitmap = (HBITMAP)::SelectObject( memdc, hBitmap);
|
|
|
|
HPALETTE hOldPalette = NULL;
|
|
if (nDepth == 8)
|
|
{
|
|
hOldPalette = ::SelectPalette(memdc, (HPALETTE)pal.GetHPALETTE(), FALSE);
|
|
::RealizePalette(memdc);
|
|
}
|
|
::StretchDIBits( memdc, 0, 0, nWidth, nHeight, 0, 0, nWidth, nHeight, pbits, bi, DIB_RGB_COLORS, SRCCOPY);
|
|
|
|
if (hOldBitmap)
|
|
::SelectObject(memdc, hOldBitmap);
|
|
if (hOldPalette)
|
|
::SelectPalette(memdc, hOldPalette, FALSE);
|
|
::DeleteDC(memdc);
|
|
|
|
}
|
|
|
|
free(pbits);
|
|
free(bi);
|
|
|
|
return hBitmap;
|
|
}
|
|
|
|
bool OsWin32_DrawBitmap(HBITMAP hBMP, wxDC& dc, const wxRect& dst, const wxRect& src)
|
|
{
|
|
static wxPalette pal;
|
|
|
|
bool ok = hBMP != NULL;
|
|
|
|
if (ok)
|
|
{
|
|
HDC hDC = (HDC)dc.GetHDC();
|
|
const int nTechno = ::GetDeviceCaps(hDC, TECHNOLOGY);
|
|
|
|
HDC hMemDC = NULL;
|
|
if (OsWin32_IsWindowsServer())
|
|
hMemDC = ::CreateCompatibleDC(hDC); // Per Terminal Server devo fare cosi'
|
|
else
|
|
hMemDC = ::CreateCompatibleDC(NULL); // Per gli altri sistemi devo fare cosa'
|
|
|
|
BITMAP bmp; ::GetObject(hBMP, sizeof(bmp), &bmp);
|
|
|
|
if (nTechno == DT_RASPRINTER) // Sto stampando!
|
|
{
|
|
const size_t bi_size = sizeof(BITMAPINFOHEADER) + 256 * sizeof(RGBQUAD);
|
|
BITMAPINFO* bi = (BITMAPINFO*)calloc(bi_size, 1); // Alloca ed azzera
|
|
BITMAPINFOHEADER& bih = bi->bmiHeader;
|
|
bih.biSize = sizeof(bih);
|
|
GetDIBits(hMemDC, hBMP, 0, bmp.bmHeight, NULL, bi, DIB_RGB_COLORS);
|
|
ok = bih.biSizeImage > 0;
|
|
if (ok)
|
|
{
|
|
LPBYTE bits = new BYTE[bih.biSizeImage];
|
|
::GetDIBits(hMemDC, hBMP, 0, src.height, bits, bi, DIB_RGB_COLORS);
|
|
::StretchDIBits(hDC, dst.x, dst.y, dst.width, dst.height, src.x, src.y, src.width, src.height,
|
|
bits, bi, DIB_RGB_COLORS, SRCCOPY);
|
|
delete bits;
|
|
}
|
|
free(bi);
|
|
}
|
|
else
|
|
{
|
|
HGDIOBJ hOldBitmap = ::SelectObject(hMemDC, hBMP);
|
|
::SetStretchBltMode(hDC, HALFTONE);
|
|
::StretchBlt(hDC, dst.x, dst.y, dst.width, dst.height,
|
|
hMemDC, src.x, src.y, src.width, src.height, SRCCOPY);
|
|
::SelectObject(hMemDC, hOldBitmap);
|
|
}
|
|
|
|
::DeleteDC(hMemDC);
|
|
}
|
|
return ok;
|
|
}
|
|
|
|
void OsWin32_DrawDottedRect(WXHDC hDC, int left, int top, int right, int bottom)
|
|
{
|
|
LOGBRUSH lBrush;
|
|
lBrush.lbHatch = 0; lBrush.lbStyle = BS_SOLID;
|
|
lBrush.lbColor = ::GetTextColor((HDC)hDC);
|
|
HPEN hPen = ::ExtCreatePen(PS_COSMETIC|PS_ALTERNATE, 1, &lBrush, 0, NULL);
|
|
HGDIOBJ hOldPen = ::SelectObject((HDC)hDC, hPen);
|
|
HGDIOBJ hBrush = ::GetStockObject(HOLLOW_BRUSH);
|
|
HGDIOBJ hOldBrush = ::SelectObject((HDC)hDC, hBrush);
|
|
::Rectangle((HDC)hDC, left, top, right, bottom);
|
|
::SelectObject((HDC)hDC, hOldBrush);
|
|
::SelectObject((HDC)hDC, hOldPen);
|
|
::DeleteObject(hPen);
|
|
}
|
|
|
|
void OsWin32_Beep(int severity)
|
|
{
|
|
switch (severity)
|
|
{
|
|
case 1: ::MessageBeep(MB_ICONEXCLAMATION); break;
|
|
case 2: ::MessageBeep(MB_ICONSTOP); break;
|
|
default: ::MessageBeep(MB_OK); break;
|
|
}
|
|
}
|
|
|
|
static wxString GetHelpDir()
|
|
{ return "htmlhelp/"; }
|
|
|
|
static wxString FindHelpFile(const char* topic)
|
|
{
|
|
wxString strTopic = topic;
|
|
|
|
wxString strApp;
|
|
wxFileName::SplitPath(wxTheApp->argv[0], NULL, &strApp, NULL);
|
|
|
|
if (strTopic.IsEmpty())
|
|
{
|
|
strTopic = strApp;
|
|
strTopic += "100a";
|
|
}
|
|
|
|
wxString str;
|
|
for (int i = 0; i < 2; i++)
|
|
{
|
|
str = GetHelpDir();
|
|
str += i == 0 ? strTopic.Left(2) : strApp.Left(2);
|
|
str += "help.pdf";
|
|
if (::wxFileExists(str))
|
|
return str;
|
|
}
|
|
|
|
for (int i = 0; i < 2; i++)
|
|
{
|
|
str = GetHelpDir();
|
|
str += i == 0 ? strTopic.Left(2) : strApp.Left(2);
|
|
str += "help.chm";
|
|
if (::wxFileExists(str))
|
|
return str;
|
|
}
|
|
for (int i = 0; i < 2; i++)
|
|
{
|
|
str = GetHelpDir();
|
|
str += i == 0 ? strTopic.Left(2) : strApp.Left(2);
|
|
str += "/";
|
|
str += strTopic;
|
|
str += ".html";
|
|
if (::wxFileExists(str))
|
|
return str;
|
|
}
|
|
|
|
return wxEmptyString;
|
|
}
|
|
|
|
int OsWin32_Help(WXHWND handle, const char* hlp, unsigned int cmd, const char* topic)
|
|
{
|
|
wxString str = hlp;
|
|
if (str.IsEmpty() || !wxFileExists(str))
|
|
{
|
|
switch(cmd)
|
|
{
|
|
case M_HELP_ONCONTEXT:
|
|
str = FindHelpFile(topic);
|
|
if (wxFileExists(str))
|
|
break;
|
|
default:
|
|
str = FindHelpFile(topic);
|
|
if (wxFileExists(str))
|
|
{
|
|
topic = NULL;
|
|
}
|
|
else
|
|
{
|
|
str = GetHelpDir();
|
|
str += "index.html";
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
if (!str.IsEmpty() && wxFileExists(str))
|
|
{
|
|
if (str.EndsWith(".pdf"))
|
|
{
|
|
wxString strCmd = OsWin32_File2App(str);
|
|
if (topic && *topic)
|
|
strCmd << " /A nameddest=" << topic;
|
|
strCmd << " " << str;
|
|
::wxExecute(strCmd);
|
|
} else
|
|
if (str.EndsWith(".chm"))
|
|
{
|
|
static wxCHMHelpController* hlp = new wxCHMHelpController;
|
|
if (hlp->LoadFile(str))
|
|
{
|
|
wxString strSection = topic;
|
|
strSection += ".html";
|
|
hlp->DisplaySection(strSection);
|
|
}
|
|
} else
|
|
if (str.EndsWith(".html"))
|
|
{
|
|
wxFileName fn = str;
|
|
fn.MakeAbsolute();
|
|
str = fn.GetFullPath();
|
|
::ShellExecute((HWND)handle, "open", str, NULL, NULL, SW_SHOWNORMAL);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
OsWin32_Beep(1); // Error beep
|
|
return false;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////
|
|
// Execute in window support
|
|
///////////////////////////////////////////////////////////
|
|
|
|
struct TFindWindowInfo
|
|
{
|
|
HINSTANCE _instance;
|
|
wxString _file;
|
|
HWND _hwnd;
|
|
|
|
TFindWindowInfo() : _instance(NULL), _hwnd(NULL) { }
|
|
};
|
|
|
|
static BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam)
|
|
{
|
|
TFindWindowInfo* w = (TFindWindowInfo*)lParam;
|
|
|
|
if (w->_instance != NULL)
|
|
{
|
|
HINSTANCE inst = (HINSTANCE)::GetWindowLong(hwnd, GWL_HINSTANCE);
|
|
if (inst == w->_instance)
|
|
{
|
|
// Cerco di capire se e' la finetra principale dal fatto che abbia la caption ed i bottoni di chiusura
|
|
const DWORD dwWanted = WS_CAPTION | WS_SYSMENU;
|
|
const DWORD style = ::GetWindowLong(hwnd, GWL_STYLE);
|
|
if ((style & dwWanted) == dwWanted)
|
|
{
|
|
w->_hwnd = hwnd;
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
if (!w->_file.IsEmpty())
|
|
{
|
|
char str[_MAX_PATH];
|
|
if (::GetWindowText(hwnd, str, sizeof(str)))
|
|
{
|
|
wxString title = str;
|
|
title.MakeUpper();
|
|
if (title.Find(w->_file) >= 0)
|
|
{
|
|
w->_hwnd = hwnd;
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
WXHINSTANCE OsWin32_ProcessModule(const char* name)
|
|
{
|
|
WXHINSTANCE hModule = NULL;
|
|
|
|
DWORD* aProcesses = NULL;
|
|
DWORD nItems = 0, nFound = 0;
|
|
for (nItems = 256; ; nItems *= 2)
|
|
{
|
|
DWORD cbNeeded = 0;
|
|
free(aProcesses);
|
|
aProcesses = (DWORD*)calloc(nItems, sizeof(DWORD));
|
|
if (!EnumProcesses(aProcesses, nItems*sizeof(DWORD), &cbNeeded))
|
|
{
|
|
free(aProcesses);
|
|
return false;
|
|
}
|
|
nFound = cbNeeded / sizeof(DWORD);
|
|
if (nFound < nItems)
|
|
break;
|
|
}
|
|
|
|
for (DWORD i = 0; i < nFound && !hModule; i++) if (aProcesses[i])
|
|
{
|
|
HANDLE hProcess = ::OpenProcess( PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, aProcesses[i] );
|
|
if (hProcess != NULL)
|
|
{
|
|
HMODULE hMod; DWORD cbNeeded;
|
|
if (::EnumProcessModules( hProcess, &hMod, sizeof(hMod), &cbNeeded) )
|
|
{
|
|
TCHAR szProcessName[MAX_PATH] = { 0 };
|
|
::GetModuleBaseName( hProcess, hMod, szProcessName, sizeof(szProcessName)/sizeof(TCHAR) );
|
|
if (wxStricmp(szProcessName, name) == 0)
|
|
hModule = (WXHINSTANCE)hMod;
|
|
}
|
|
// Release the handle to the process.
|
|
CloseHandle( hProcess );
|
|
}
|
|
}
|
|
|
|
free(aProcesses);
|
|
return hModule;
|
|
}
|
|
|
|
void OsWin32_PlaceProcessInWindow(unsigned int instance, const char* name, unsigned int parent)
|
|
{
|
|
TFindWindowInfo w;
|
|
w._instance = (HINSTANCE)instance;
|
|
w._file = name;
|
|
w._file.MakeUpper();
|
|
|
|
for (int i = 0; w._hwnd == NULL && i < 20; i++)
|
|
{
|
|
::wxMilliSleep(500);
|
|
::EnumWindows(EnumWindowsProc, LPARAM(&w));
|
|
}
|
|
|
|
if (w._hwnd != NULL) // L'ho trovata!
|
|
{
|
|
RECT rct; ::GetClientRect((HWND)parent, &rct);
|
|
::SetParent(w._hwnd, (HWND)parent);
|
|
const int fx = ::GetSystemMetrics(SM_CXFRAME);
|
|
const int fy = ::GetSystemMetrics(SM_CYFRAME);
|
|
int cy = ::GetSystemMetrics(SM_CYCAPTION)+GetSystemMetrics(SM_CYBORDER);
|
|
if (::GetMenu(w._hwnd) != NULL)
|
|
cy += ::GetSystemMetrics(SM_CYMENU);
|
|
::SetWindowPos(w._hwnd, (HWND)parent, -fx, -fy-cy, rct.right+2*fx, rct.bottom+cy+2*fy, SWP_NOZORDER);
|
|
}
|
|
}
|
|
|
|
static BOOL CALLBACK EnumCampoChildrenProc(HWND hwnd, LPARAM lParam)
|
|
{
|
|
char str[_MAX_PATH];
|
|
if (::GetWindowText(hwnd, str, sizeof(str)))
|
|
{
|
|
TFindWindowInfo* w = (TFindWindowInfo*)lParam;
|
|
if (w->_file == str) // str == "__CAMPO_HOST_WINDOW__"
|
|
{
|
|
str[13] = '\0'; // Impedisce che questa finestra abbia altri figli indesiderati
|
|
::SetWindowText(hwnd, str);
|
|
w->_hwnd = hwnd;
|
|
return FALSE; // Fine della ricerca
|
|
}
|
|
}
|
|
return TRUE; // Continua a cercare
|
|
}
|
|
|
|
static BOOL CALLBACK EnumCampoMenuChildrenProc(HWND hwnd, LPARAM lParam)
|
|
{
|
|
char str[_MAX_PATH];
|
|
if (::GetWindowText(hwnd, str, sizeof(str)))
|
|
{
|
|
if (strstr(str, " - ") != NULL)
|
|
{
|
|
const TFindWindowInfo* w = (TFindWindowInfo*)lParam;
|
|
::EnumChildWindows(hwnd, EnumCampoChildrenProc, lParam);
|
|
if (w->_hwnd != NULL)
|
|
return FALSE; // Fine della ricerca
|
|
}
|
|
}
|
|
return TRUE; // Continua a cercare
|
|
}
|
|
|
|
unsigned int OsWin32_FindMenuContainer()
|
|
{
|
|
wxString strApp;
|
|
wxFileName::SplitPath(wxTheApp->argv[0], NULL, &strApp, NULL);
|
|
strApp.MakeLower();
|
|
if (strApp == "ba0" || strApp == "ba1" || strApp == "ba7")
|
|
return 0; // Special programs that can't be hosted by ba0
|
|
|
|
TFindWindowInfo w;
|
|
w._file = "__CAMPO_HOST_WINDOW__";
|
|
::EnumWindows(EnumCampoMenuChildrenProc, LPARAM(&w));
|
|
return (unsigned int)w._hwnd;
|
|
}
|
|
|
|
static BOOL CALLBACK CountChildrenProc(HWND WXUNUSED(hwnd), LPARAM lParam)
|
|
{
|
|
if (lParam)
|
|
{
|
|
LONG* n = (LONG*)lParam;
|
|
(*n)++;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
long OsWin32_GetChildrenCount(unsigned int parent)
|
|
{
|
|
LONG n = 0;
|
|
::EnumChildWindows((HWND)parent, CountChildrenProc, (LPARAM)&n);
|
|
return n;
|
|
}
|
|
|
|
static BOOL CALLBACK CloseChildrenProc(HWND hwnd, LPARAM lParam)
|
|
{
|
|
::PostMessage(hwnd, WM_CLOSE, 0, 0);
|
|
return CountChildrenProc(hwnd, lParam);
|
|
}
|
|
|
|
long OsWin32_CloseChildren(unsigned int parent)
|
|
{
|
|
LONG n = 0;
|
|
::EnumChildWindows((HWND)parent, CloseChildrenProc, (LPARAM)&n);
|
|
return n;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////
|
|
// Ex-Golem utilities
|
|
///////////////////////////////////////////////////////////
|
|
|
|
static long GetRegistryString(HKEY key, const char* subkey, wxString& retstr)
|
|
{
|
|
HKEY hkey;
|
|
long retval = ::RegOpenKey(key, subkey, &hkey);
|
|
if (retval == ERROR_SUCCESS)
|
|
{
|
|
char retdata[_MAX_PATH];
|
|
long datasize = sizeof(retdata);
|
|
::RegQueryValue(hkey, NULL, retdata, &datasize);
|
|
::RegCloseKey(hkey);
|
|
retstr = retdata;
|
|
}
|
|
return retval;
|
|
}
|
|
|
|
wxString OsWin32_File2App(const char* filename)
|
|
{
|
|
wxString app;
|
|
|
|
if (*filename != '.')
|
|
{
|
|
char retdata[_MAX_PATH];
|
|
HINSTANCE hinst = ::FindExecutable(filename, ".", retdata);
|
|
DWORD* pinst = (DWORD*)hinst;
|
|
UINT err = LOWORD(pinst);
|
|
if (err > 32)
|
|
app = retdata;
|
|
}
|
|
|
|
if (app.IsEmpty())
|
|
{
|
|
wxString ext;
|
|
if (*filename == '.')
|
|
ext = filename;
|
|
else
|
|
{
|
|
wxSplitPath(filename, NULL, NULL, &ext);
|
|
if (!ext.StartsWith("."))
|
|
ext = "." + ext;
|
|
}
|
|
ext.MakeLower();
|
|
|
|
wxString key;
|
|
if (GetRegistryString(HKEY_CLASSES_ROOT, ext, key) == ERROR_SUCCESS)
|
|
{
|
|
key << "\\shell\\open\\command";
|
|
if (GetRegistryString(HKEY_CLASSES_ROOT, key, key) == ERROR_SUCCESS)
|
|
{
|
|
key.Replace("\"", " ");
|
|
int pos = key.Find("%1");
|
|
if (pos > 0)
|
|
key.Truncate(pos);
|
|
key.Trim(false); key.Trim(true);
|
|
app = key;
|
|
}
|
|
}
|
|
}
|
|
|
|
return app;
|
|
}
|
|
|
|
static bool IsInternetAddress(const char* filename)
|
|
{
|
|
wxString url(filename); url.MakeLower();
|
|
if (url.StartsWith("http:") || url.StartsWith("ftp:"))
|
|
return true;
|
|
if (url.Find("www.") >= 0)
|
|
return true;
|
|
|
|
wxString ext; wxFileName::SplitPath(url, NULL, NULL, NULL, &ext);
|
|
const char* const extensions[] = { "com","edu","eu","gov","it","mil","net","org", NULL };
|
|
for (int e = 0; extensions[e]; e++)
|
|
if (ext == extensions[e])
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
wxIcon OsWin32_LoadIcon(const char* filename)
|
|
{
|
|
int icon_number = 0;
|
|
|
|
wxString ext;
|
|
if (*filename == '.' && strlen(filename) < _MAX_EXT)
|
|
ext = filename;
|
|
else
|
|
{
|
|
if (IsInternetAddress(filename))
|
|
ext = ".htm";
|
|
else
|
|
{
|
|
wxFileName::SplitPath(filename, NULL, NULL, NULL, &ext);
|
|
if (!ext.StartsWith("."))
|
|
ext.insert(0, ".");
|
|
}
|
|
}
|
|
ext.MakeLower();
|
|
|
|
wxString key;
|
|
if (ext != ".exe")
|
|
{
|
|
if (::GetRegistryString(HKEY_CLASSES_ROOT, ext, key) == ERROR_SUCCESS)
|
|
{
|
|
key << "\\DefaultIcon";
|
|
if (::GetRegistryString(HKEY_CLASSES_ROOT, key, key) == ERROR_SUCCESS) // Windows 95 only
|
|
{
|
|
const int comma = key.find(',');
|
|
if (comma > 0)
|
|
{
|
|
icon_number = atoi(key.Mid(comma+1));
|
|
key.Truncate(comma);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
key = OsWin32_File2App(filename);
|
|
if (key.IsEmpty())
|
|
key = OsWin32_File2App(".htm");
|
|
}
|
|
}
|
|
}
|
|
else
|
|
key = filename;
|
|
|
|
// Toglie eventuali parametri sulla riga si comando
|
|
const int ext_pos = key.Find(".exe");
|
|
if (ext_pos > 0)
|
|
key.Truncate(ext_pos+4);
|
|
|
|
wxString strFullName = key;
|
|
if (icon_number > 0)
|
|
strFullName << ";" << icon_number;
|
|
|
|
wxIcon ico(strFullName, wxBITMAP_TYPE_ICO);
|
|
return ico;
|
|
}
|
|
|
|
// action = [ open, edit, print ];
|
|
bool OsWin32_GotoUrl(const char* url, const char* action)
|
|
{
|
|
bool ok = false;
|
|
|
|
// Sarebbe meglio un flag esplicito, ma per ora attendiamo solo le stampe
|
|
if (action && wxStricmp(action, "print") == 0)
|
|
{
|
|
SHELLEXECUTEINFO sei; memset(&sei, 0, sizeof(sei));
|
|
sei.cbSize = sizeof(sei);
|
|
sei.fMask = SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_DDEWAIT;
|
|
sei.lpVerb = action;
|
|
sei.lpFile = url;
|
|
sei.nShow = SW_SHOWNORMAL;
|
|
if (::ShellExecuteEx(&sei))
|
|
{
|
|
if (sei.hProcess != NULL)
|
|
{
|
|
::WaitForSingleObject(sei.hProcess, 0);
|
|
::CloseHandle(sei.hProcess);
|
|
}
|
|
ok = true;
|
|
}
|
|
} else
|
|
if (wxStrstr(url, ".jar"))
|
|
{
|
|
const wxFileName fn = url;
|
|
wxString args; args << "-jar " << fn.GetFullPath();
|
|
HINSTANCE hinst = ::ShellExecute(NULL, NULL, "java.exe", args, fn.GetPath(), SW_HIDE); // Hide java console
|
|
DWORD winst = DWORD((DWORD*)hinst); // Tutto 'sto giro per evitare un warning
|
|
ok = UINT(winst) > 32;
|
|
}
|
|
else
|
|
{
|
|
HINSTANCE hinst = ::ShellExecute(NULL, action, url, NULL, NULL, SW_SHOWNORMAL);
|
|
DWORD winst = DWORD((DWORD*)hinst); // Tutto 'sto giro per evitare un warning
|
|
ok = UINT(winst) > 32;
|
|
}
|
|
return ok;
|
|
}
|
|
|
|
#ifdef SPEECH_API
|
|
|
|
#include <sapi.h>
|
|
|
|
static ISpVoice* m_pVoice = NULL;
|
|
|
|
bool OsWin32_InitializeSpeech()
|
|
{
|
|
if (m_pVoice == NULL)
|
|
CoCreateInstance(CLSID_SpVoice, NULL, CLSCTX_ALL, IID_ISpVoice, (void **)&m_pVoice);
|
|
return m_pVoice != NULL;
|
|
}
|
|
|
|
void OsWin32_DeinitializeSpeech()
|
|
{
|
|
if (m_pVoice != NULL)
|
|
{
|
|
m_pVoice->WaitUntilDone(1000);
|
|
m_pVoice->Release();
|
|
m_pVoice = NULL;
|
|
}
|
|
}
|
|
|
|
bool OsWin32_Speak(const char* text, bool async)
|
|
{
|
|
if (m_pVoice != NULL)
|
|
{
|
|
WCHAR str[1204];
|
|
MultiByteToWideChar(CP_ACP, 0, text, -1, str, strlen(text)+1);
|
|
if (async)
|
|
m_pVoice->Speak(str, SPF_ASYNC | SPF_PURGEBEFORESPEAK, NULL);
|
|
else
|
|
m_pVoice->Speak(str, SPF_PURGEBEFORESPEAK, NULL);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
#endif
|
|
|
|
int OsWin32_GetSessionId()
|
|
{
|
|
DWORD session = 0;
|
|
::ProcessIdToSessionId(::GetCurrentProcessId(), &session);
|
|
return (int)session;
|
|
// return WTSGetActiveConsoleSessionId(); // Always 1! :-(
|
|
}
|
|
|
|
bool OsWin32_IsWindowsServer()
|
|
{
|
|
return ::GetSystemMetrics(SM_REMOTESESSION) != 0;
|
|
}
|
|
|
|
void OsWin32_NumberFormat(char* str, int size)
|
|
{
|
|
static char decsep = '\0', thosep = '\0';
|
|
|
|
char buf[80] = "";
|
|
if (!decsep)
|
|
{
|
|
::GetNumberFormat(LOCALE_USER_DEFAULT, 0, "1936.27", NULL, buf, sizeof(buf));
|
|
decsep = buf[strlen(buf)-3];
|
|
thosep = buf[1] == '9' ? '\0' : buf[1];
|
|
}
|
|
|
|
if (str && *str)
|
|
{
|
|
int j = 0;
|
|
for (int i = 0; str[i]; i++)
|
|
{
|
|
switch (str[i])
|
|
{
|
|
case '.': buf[j++] = decsep; break;
|
|
case ',': break; // Ignore thousand separator
|
|
default : buf[j++] = str[i]; break;
|
|
}
|
|
}
|
|
buf[j] = '\0';
|
|
wxStrncpy(str, buf, size);
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////
|
|
// OsWin32_Progress...
|
|
///////////////////////////////////////////////////////////
|
|
|
|
static const wchar_t* str2wstr(const char* str, int maxlen = -1)
|
|
{
|
|
static wchar_t wstr[260];
|
|
if (str && *str && maxlen)
|
|
{
|
|
if (maxlen < 0 || maxlen > 256)
|
|
maxlen = strlen(str);
|
|
wxConvCurrent->ToWChar(wstr, 260, str, maxlen);
|
|
}
|
|
else
|
|
wstr[0] = '\0';
|
|
return wstr;
|
|
}
|
|
|
|
class Win32ProgressIndicator
|
|
{
|
|
IProgressDialog* m_ppd;
|
|
long _curr, _total, _perc;
|
|
bool _cancellable;
|
|
int _lines;
|
|
clock_t _start;
|
|
|
|
static int __nProgress; // Instances
|
|
|
|
public:
|
|
bool IsOk() const { return m_ppd != NULL; }
|
|
bool SetProgress(long curr, long tot);
|
|
void SetText(const char* msg);
|
|
|
|
Win32ProgressIndicator(WXHWND hwndParent, const char* strTitle, long nMax, bool bCanCancel);
|
|
~Win32ProgressIndicator();
|
|
};
|
|
|
|
int Win32ProgressIndicator::__nProgress = 0;
|
|
|
|
bool Win32ProgressIndicator::SetProgress(long nCurrent, long nTotal)
|
|
{
|
|
bool ok = IsOk();
|
|
if (ok)
|
|
{
|
|
if (nCurrent <= 0 || nTotal != _total)
|
|
{
|
|
m_ppd->Timer(PDTIMER_RESET, NULL);
|
|
_start = clock();
|
|
_perc = 0;
|
|
}
|
|
m_ppd->SetProgress(nCurrent, nTotal);
|
|
_curr = nCurrent;
|
|
_total = nTotal;
|
|
|
|
if (_lines < 2)
|
|
{
|
|
const int newperc = nTotal > 0 && nCurrent > 0 ? nCurrent*100/nTotal : 0;
|
|
if (newperc != _perc)
|
|
{
|
|
_perc = newperc;
|
|
const long nSec = (clock() -_start) / CLOCKS_PER_SEC;
|
|
if (nSec > 0)
|
|
{
|
|
const int nSpeed = nCurrent / nSec;
|
|
int s = nSec;
|
|
const int h = s / 3600; s %= 3600;
|
|
const int m = s / 60; s %= 60;
|
|
wxString str;
|
|
if (nSpeed < 120)
|
|
str.Format("%d%% - %ld / %ld - %02d:%02d:%02d - %d / sec", _perc, nCurrent, nTotal, h, m, s, nSpeed);
|
|
else
|
|
str.Format("%d%% - %ld / %ld - %02d:%02d:%02d - %d / min", _perc, nCurrent, nTotal, h, m, s, nSpeed/60);
|
|
m_ppd->SetLine(2, str2wstr(str), FALSE, NULL);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (_cancellable && m_ppd->HasUserCancelled())
|
|
ok = false;
|
|
}
|
|
return ok;
|
|
}
|
|
|
|
void Win32ProgressIndicator::SetText(const char* msg)
|
|
{
|
|
if (IsOk())
|
|
{
|
|
if (msg && *msg)
|
|
{
|
|
const char* acapo = strchr(msg, '\n');
|
|
if (acapo)
|
|
{
|
|
m_ppd->SetLine(1, str2wstr(msg, acapo-msg), FALSE, NULL);
|
|
m_ppd->SetLine(2, str2wstr(acapo+1), FALSE, NULL);
|
|
_lines = 2;
|
|
}
|
|
else
|
|
{
|
|
m_ppd->SetLine(1, str2wstr(msg), FALSE, NULL);
|
|
m_ppd->SetLine(2, L"", FALSE, NULL);
|
|
_lines = 1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_ppd->SetLine(1, L"", FALSE, NULL);
|
|
m_ppd->SetLine(2, L"", FALSE, NULL);
|
|
_lines = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
Win32ProgressIndicator::Win32ProgressIndicator(WXHWND hwndParent, const char* strTitle, long nMax, bool bCanCancel)
|
|
{
|
|
m_ppd = NULL;
|
|
::CoCreateInstance(CLSID_ProgressDialog, NULL, CLSCTX_INPROC_SERVER, IID_IProgressDialog, (void **)&m_ppd);
|
|
if (m_ppd)
|
|
{
|
|
if (strTitle && *strTitle)
|
|
m_ppd->SetTitle(str2wstr(strTitle)); // Set the title of the dialog.
|
|
|
|
DWORD dwFlags = PROGDLG_AUTOTIME | PROGDLG_MODAL | PROGDLG_NOMINIMIZE;
|
|
if (nMax <= 1)
|
|
{
|
|
if (nMax == 1)
|
|
dwFlags |= PROGDLG_MARQUEEPROGRESS;
|
|
else
|
|
dwFlags |= PROGDLG_NOPROGRESSBAR;
|
|
}
|
|
|
|
_cancellable = bCanCancel && nMax > 1;
|
|
if (_cancellable)
|
|
m_ppd->SetCancelMsg(L"Attendere prego...", NULL); // Will only be displayed if Cancel button is pressed.
|
|
else
|
|
dwFlags |= PROGDLG_NOCANCEL;
|
|
|
|
_lines = 0; // No text right now!
|
|
_perc = 0; // No progress right now
|
|
m_ppd->StartProgressDialog((HWND)hwndParent, NULL, dwFlags, NULL); // Display and enable automatic estimated time remaining.
|
|
m_ppd->Timer(PDTIMER_RESET, NULL);
|
|
_start = clock();
|
|
|
|
IOleWindow* m_wnd = NULL;
|
|
if (SUCCEEDED(m_ppd->QueryInterface(IID_IOleWindow, (void**)m_wnd)))
|
|
{
|
|
HWND hwnd = NULL;
|
|
if (m_wnd && SUCCEEDED(m_wnd->GetWindow(&hwnd)))
|
|
{
|
|
RECT rct; ::GetWindowRect(hwnd, &rct);
|
|
const int x = rct.left;
|
|
const int y = int((1.5*__nProgress+0.5)*(rct.bottom-rct.top));
|
|
::SetWindowPos(hwnd, NULL, x, y, 0, 0, SWP_NOSIZE | SWP_NOZORDER);
|
|
|
|
WXHICON hIcon = xvtart_GetIconResource(0).GetHICON();
|
|
::SendMessage(hwnd, WM_SETICON, ICON_SMALL, (LPARAM)hIcon);
|
|
}
|
|
}
|
|
__nProgress++;
|
|
}
|
|
}
|
|
|
|
Win32ProgressIndicator::~Win32ProgressIndicator()
|
|
{
|
|
if (m_ppd)
|
|
{
|
|
wxASSERT(__nProgress >= 0);
|
|
m_ppd->StopProgressDialog();
|
|
m_ppd->Release();
|
|
m_ppd = NULL;
|
|
__nProgress--;
|
|
}
|
|
}
|
|
|
|
WXHWND OsWin32_ProgressCreate(WXHWND hwndParent, const char* strTitle, long nMax, bool bCanCancel)
|
|
{
|
|
Win32ProgressIndicator* pi = new Win32ProgressIndicator(hwndParent, strTitle, nMax, bCanCancel);
|
|
if (pi && !pi->IsOk())
|
|
{
|
|
delete pi;
|
|
pi = NULL;
|
|
}
|
|
return (WXHWND)pi;
|
|
}
|
|
|
|
void OsWin32_ProgressDestroy(WXHWND prog)
|
|
{
|
|
if (prog)
|
|
{
|
|
Win32ProgressIndicator* ppd = (Win32ProgressIndicator*)prog;
|
|
delete ppd;
|
|
}
|
|
}
|
|
|
|
bool OsWin32_ProgressSetStatus(WXHWND prog, long nCurrent, long nTotal)
|
|
{
|
|
bool ok = true;
|
|
if (prog)
|
|
{
|
|
Win32ProgressIndicator* ppd = (Win32ProgressIndicator*)prog;
|
|
ok = ppd->SetProgress(nCurrent, nTotal);
|
|
}
|
|
return ok;
|
|
}
|
|
|
|
void OsWin32_ProgressSetText(WXHWND prog, const char* msg)
|
|
{
|
|
Win32ProgressIndicator* pd = (Win32ProgressIndicator*)prog;
|
|
if (pd && pd->IsOk())
|
|
pd->SetText(msg);
|
|
}
|
|
|