/************************************************************************ MeOS - Orienteering Software Copyright (C) 2009-2024 Melin Software HB This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Melin Software HB - software@melin.nu - www.melin.nu Eksoppsvägen 16, SE-75646 UPPSALA, Sweden ************************************************************************/ // gdioutput.cpp: implementation of the gdioutput class. // ////////////////////////////////////////////////////////////////////// #include "stdafx.h" #include "gdioutput.h" #include "gdiconstants.h" #include "meosException.h" #include "process.h" #include "meos.h" #include #include #include #include #include #include #include #include #include "meos_util.h" #include "Table.h" #define _USE_MATH_DEFINES #include "math.h" #include "Localizer.h" #include "TabBase.h" #include "toolbar.h" #include "gdiimpl.h" #include "Printer.h" #include "recorder.h" #include "animationdata.h" #include "image.h" #include "autocomplete.h" extern Image image; ////////////////////////////////////////////////////////////////////// // Construction/Destruction ////////////////////////////////////////////////////////////////////// //Fulhack... #ifndef IDC_HAND #define IDC_HAND MAKEINTRESOURCE(32649) #endif //#define DEBUGRENDER #ifdef DEBUGRENDER static int counterRender = 0; static bool breakRender = false; static int debugDrawColor = 0; #endif extern int defaultCodePage; GuiHandler &BaseInfo::getHandler() const { if (managedHandler) return *managedHandler; if (handler == 0) throw meosException("Handler not definied."); return *handler; } void GuiHandler::handle(gdioutput &gdi, BaseInfo &info, GuiEventType type) { throw meosException("Handler not definied."); } InputInfo::InputInfo() : hWnd(0), callBack(0), ignoreCheck(false), isEditControl(true), bgColor(colorDefault), fgColor(colorDefault), writeLock(false), updateLastData(0) {} EventInfo::EventInfo() : callBack(0), keyEvent(KC_NONE) {} /** Return true if rendering text should be skipped for this format. */ bool gdioutput::skipTextRender(int format) { format &= 0xFF | hiddenText; return format == pageNewPage || format == pagePageInfo || format == pageNewChapter || (format & hiddenText) == hiddenText; } #ifndef MEOSDB gdioutput::gdioutput(const string &_tag, double _scale) : recorder((Recorder *)0, false) { tag = _tag; po_default = new PrinterObject(); tabs = 0; hasAnyTimer = false; constructor(_scale); isTestMode = false; } extern gdioutput *gdi_main; gdioutput::gdioutput(double _scale, HWND hWnd, const PrinterObject &prndef) : recorder((Recorder *)0, false) { hasAnyTimer = false; po_default = new PrinterObject(prndef); tabs = 0; setWindow(hWnd); constructor(_scale); if (gdi_main) { isTestMode = gdi_main->isTestMode; if (isTestMode) cmdAnswers.swap(gdi_main->cmdAnswers); } else isTestMode = false; } void gdioutput::constructor(double _scale) { currentFontSet = 0; commandLock = false; commandUnlockTime = 0; lockUpDown = false; Background = 0; backgroundColor1 = -1; backgroundColor2 = -1; foregroundColor = -1; backgroundImage = -1; toolbar = 0; initCommon(_scale, L"Arial"); OffsetY=0; OffsetX=0; manualUpdate = false; itTL = TL.end(); hWndTarget = 0; hWndToolTip = 0; hWndAppMain = 0; onClear = 0; postClear = 0; clearPage(true); hasCleared = false; highContrast = false; hideBG = false; fullScreen = false; lockRefresh = 0; autoSpeed = 0; autoPos = 0; lastSpeed = 0; autoCounter = 0; } #endif void gdioutput::setFont(int size, const wstring &font) { double ss = size * sqrt(size); double s = 1 + double(ss)*0.25; initCommon(s, font); } void gdioutput::setFontCtrl(HWND hWnd) { SendMessage(hWnd, WM_SETFONT, (WPARAM) getGUIFont(), MAKELPARAM(TRUE, 0)); } static void scaleWindow(HWND hWnd, double scale, int &w, int &h) { RECT rc; GetWindowRect(hWnd, &rc); w = rc.right - rc.left; h = rc.bottom - rc.top; w = int(w * scale + 0.5); h = int(h * scale + 0.5); } int transformX(int x, double scale) { if (x<40) return int(x * scale + 0.5); else return int((x-40) * scale + 0.5) + 40; } void gdioutput::scaleSize(double scale_, bool allowSmallScale, ScaleOperation op) { if (fabs(scale_ - 1.0) < 1e-4) return; // No scaling double ns = scale*scale_; if (!allowSmallScale && ns + 1e-6 < 1.0 ) { ns = 1.0; scale_ = 1.0; } initCommon(ns, currentFont); if (op == ScaleOperation::NoUpdate) return; for (list::iterator it = TL.begin(); it!=TL.end(); ++it) { it->xlimit = int(it->xlimit * scale_ + 0.5); it->xp = transformX(it->xp, scale_); it->yp = int(it->yp * scale_ + 0.5); } int w, h; OffsetY = int (OffsetY * scale_ + 0.5); OffsetX = int (OffsetX * scale_ + 0.5); for (list::iterator it = BI.begin(); it!=BI.end(); ++it) { if (it->fixedRightTop) it->xp = int(scale_ * it->xp + 0.5); else it->xp = transformX(it->xp, scale_); it->yp = int(it->yp * scale_ + 0.5); if (it->isCheckbox) scaleWindow(it->hWnd, 1.0, w, h); else scaleWindow(it->hWnd, scale_, w, h); setFontCtrl(it->hWnd); MoveWindow(it->hWnd, it->xp-OffsetX, it->yp-OffsetY, w, h, true); } for (list::iterator it = II.begin(); it!=II.end(); ++it) { it->xp = transformX(it->xp, scale_); it->yp = int(it->yp * scale_ + 0.5); it->height *= scale_; it->width *= scale_; setFontCtrl(it->hWnd); MoveWindow(it->hWnd, it->xp-OffsetX, it->yp-OffsetY, int(it->width+0.5), int(it->height+0.5), true); } for (list::iterator it = LBI.begin(); it!=LBI.end(); ++it) { it->xp = transformX(it->xp, scale_); it->yp = int(it->yp * scale_ + 0.5); it->height *= scale_; it->width *= scale_; setFontCtrl(it->hWnd); MoveWindow(it->hWnd, it->xp-OffsetX, it->yp-OffsetY, int(it->width+0.5), int(it->height+0.5), true); } for (list::iterator it = Rectangles.begin(); it!=Rectangles.end(); ++it) { it->rc.bottom = int(it->rc.bottom * scale_ + 0.5); it->rc.top = int(it->rc.top * scale_ + 0.5); it->rc.right = transformX(it->rc.right, scale_); it->rc.left = transformX(it->rc.left, scale_); } for (list::iterator it = Tables.begin(); it != Tables.end(); ++it) { it->xp = transformX(it->xp, scale_); it->yp = int(it->yp * scale_ + 0.5); } MaxX = transformX(MaxX, scale_); MaxY = int (MaxY * scale_ + 0.5); CurrentX = transformX(CurrentX, scale_); CurrentY = int (CurrentY * scale_ + 0.5); SX = transformX(SX, scale_); SY = int (SY * scale_ + 0.5); for (map::iterator it = restorePoints.begin(); it != restorePoints.end(); ++it) { RestoreInfo &r = it->second; r.sMX = transformX(r.sMX, scale_); r.sMY = int (r.sMY * scale_ + 0.5); r.sCX = transformX(r.sCX, scale_); r.sCY = int (r.sCY * scale_ + 0.5); r.sOX = transformX(r.sOX, scale_); r.sOY = int (r.sOY * scale_ + 0.5); } if (op == ScaleOperation::Refresh) { refresh(); } else { HDC hDC = GetDC(hWndTarget); for (auto &ti : TL) { calcStringSize(ti, hDC); } ReleaseDC(hWndTarget, hDC); } } void gdioutput::initCommon(double _scale, const wstring &font) { guiMeasure.reset(); dbErrorState = false; currentFontSet = 0; scale = _scale; currentFont = font; deleteFonts(); enableTables(); lineHeight = int(scale*14); Background=CreateSolidBrush(GetSysColor(COLOR_WINDOW)); fontHeightCache.clear(); fonts[currentFont].init(scale, currentFont, L""); updateTabFont(); } void gdioutput::updateTabFont() { if (this == gdi_main && hWndTab) { HFONT gui = fonts[currentFont].getGUIFont(); SendMessage(hWndTab, WM_SETFONT, WPARAM(gui), TRUE); RECT rc; GetClientRect(hWndAppMain, &rc); SendMessage(hWndAppMain, WM_SIZE, 0, MAKELONG(rc.right, rc.bottom)); } } double getLocalScale(const wstring &fontName, wstring &faceName) { double locScale = 1.0; vector res; split(fontName, L";", res); if (res.empty() || res.size() > 2) throw meosException(L"Cannot load font: " + fontName); if (res.size() == 2) { locScale = _wtof(res[1].c_str()); if (!(locScale>0.001 && locScale < 100)) throw meosException(L"Cannot scale font with factor: " + res[1]); } faceName = res[0]; return locScale; } const GDIImplFontSet & gdioutput::loadFont(const wstring &font) { currentFontSet = 0; vector< pair > fontIx; getEnumeratedFonts(fontIx); double relScale = 1.0; for (size_t k = 0; k < fontIx.size(); k++) { if (stringMatch(fontIx[k].first, font)) { relScale = enumeratedFonts[fontIx[k].second].getRelScale(); } } wstring faceName; double locScale = getLocalScale(font, faceName); if (faceName.empty()) faceName = currentFont; fonts[font].init(scale * relScale * locScale, faceName, font); return fonts[font]; } void gdioutput::deleteFonts() { if (Background) DeleteObject(Background); Background = 0; currentFontSet = 0; fonts.clear(); } #ifndef MEOSDB gdioutput::~gdioutput() { while(!timers.empty()) { KillTimer(hWndTarget, (UINT_PTR)&timers.back()); timers.back().setWnd = 0; timers.back().parent = 0; timers.pop_back(); } animationData.reset(); deleteFonts(); if (toolbar) delete toolbar; toolbar = 0; Tables.clear(); if (tabs) { delete tabs; tabs = 0; } initRecorder(0); delete po_default; po_default = 0; } #endif FixedTabs &gdioutput::getTabs() { #ifndef MEOSDB if (!tabs) tabs = new FixedTabs(); #endif return *tabs; } void gdioutput::fetchPrinterSettings(PrinterObject &po) const { po = *po_default; } void gdioutput::drawBackground(HDC hDC, RECT &rc) { if (backgroundColor1 != -1) { SelectObject(hDC, GetStockObject(NULL_PEN)); SelectObject(hDC, GetStockObject(DC_BRUSH)); SetDCBrushColor(hDC, backgroundColor1); Rectangle(hDC, -1, -1, rc.right + 1, rc.bottom + 1); return; } else if (!backgroundImage.empty()) { // TODO } GRADIENT_RECT gr[1]; SelectObject(hDC, GetStockObject(NULL_PEN)); SelectObject(hDC, Background); if (highContrast) { Rectangle(hDC, -1, -1, rc.right + 1, rc.bottom + 1); HFONT hInfo = CreateFont(min(30, int(scale*22)), 0, 900, 900, FW_LIGHT, false, false, false, DEFAULT_CHARSET, OUT_TT_ONLY_PRECIS, CLIP_DEFAULT_PRECIS, CLEARTYPE_QUALITY, DEFAULT_PITCH|FF_ROMAN, L"Arial"); SelectObject(hDC, hInfo); RECT mrc; mrc.left = 0; mrc.right = 0; mrc.top = 0; mrc.bottom = 0; DrawText(hDC, listDescription.c_str(), listDescription.length(), &mrc, DT_LEFT|DT_CALCRECT|DT_NOPREFIX); int height = mrc.right + mrc.right / 3; SetBkMode(hDC, TRANSPARENT); for (int k = height; k < MaxY; k += height) { mrc.left = 5 - OffsetX; mrc.right = 1000; mrc.top = k - OffsetY; mrc.bottom = MaxY; SetTextColor(hDC, RGB(192, 192, 192)); DrawText(hDC, listDescription.c_str(), listDescription.length(), &mrc, DT_LEFT|DT_NOCLIP|DT_NOPREFIX); mrc.top -= 1; mrc.left -= 1; SetTextColor(hDC, RGB(92, 32, 32)); DrawText(hDC, listDescription.c_str(), listDescription.length(), &mrc, DT_LEFT|DT_NOCLIP|DT_NOPREFIX); } SelectObject(hDC, GetStockObject(ANSI_FIXED_FONT)); DeleteObject(hInfo); return; } if (!hideBG) { Rectangle(hDC, -1, -1, rc.right-OffsetX+1, 10-OffsetY+1); Rectangle(hDC, -1, -1, 11-OffsetX, rc.bottom+1); Rectangle(hDC, MaxX+10-OffsetX, 0, rc.right+1, rc.bottom+1); Rectangle(hDC, 10-OffsetX, MaxY+13-OffsetY, MaxX+11-OffsetX, rc.bottom+1); } if (dbErrorState) { SelectObject(hDC, GetStockObject(DC_BRUSH)); SetDCBrushColor(hDC, RGB(255, 100, 100)); Rectangle(hDC, -1, -1, rc.right+1, rc.bottom+1); HFONT hInfo = CreateFont(30, 0, 900, 900, FW_BOLD, false, false, false, DEFAULT_CHARSET, OUT_TT_ONLY_PRECIS, CLIP_DEFAULT_PRECIS, CLEARTYPE_QUALITY, DEFAULT_PITCH|FF_ROMAN, L"Arial"); wstring err = lang.tl(L"DATABASE ERROR"); SelectObject(hDC, hInfo); RECT mrc; mrc.left = 0; mrc.right = 0; mrc.top = 0; mrc.bottom = 0; DrawText(hDC, err.c_str(), err.length(), &mrc, DT_LEFT|DT_CALCRECT|DT_NOPREFIX); int width = mrc.bottom + mrc.bottom / 4; int height = mrc.right + mrc.right / 4; SetBkMode(hDC, TRANSPARENT); SetTextColor(hDC, RGB(64, 0, 0)); for (int k = height; k < max(MaxY, rc.bottom + height); k += height) { mrc.left = rc.right - 50 - OffsetX; mrc.right = mrc.left + 1000; mrc.top = k - OffsetY; mrc.bottom = MaxY; DrawText(hDC, err.c_str(), err.length(), &mrc, DT_LEFT|DT_NOCLIP|DT_NOPREFIX); mrc.left -= width; mrc.top -= height / 2; DrawText(hDC, err.c_str(), err.length(), &mrc, DT_LEFT|DT_NOCLIP|DT_NOPREFIX); } SelectObject(hDC, GetStockObject(ANSI_FIXED_FONT)); DeleteObject(hInfo); } /* DWORD c=GetSysColor(COLOR_3DFACE); double red = double(GetRValue(c)) *0.9; double green = double(GetGValue(c)) * 0.85; double blue = min(255.0, double(GetBValue(c)) * 1.05); if (blue<100) { //Invert red = 255-red; green = 255-green; blue = 255-blue; } double blue1=min(255., blue*1.3); double green1=min(255., green*1.3); double red1=min(255., red*1.3); */ double red = 242.0; double green = 247.0; double blue = 254.0; double blue1 = 250.0; double green1 = 232.0; double red1 = 223.0; TRIVERTEX vert[2]; if (hideBG) { vert [0] .x = 0; vert [0] .y = 0; } else { vert [0] .x = 10-OffsetX; vert [0] .y = 10-OffsetY; } vert [0] .Red = 0xff00&DWORD(red1*256); vert [0] .Green = 0xff00&DWORD(green1*256); vert [0] .Blue = 0xff00&DWORD(blue1*256); vert [0] .Alpha = 0x0000; if (hideBG) { vert [1] .x = rc.right + 1; vert [1] .y = rc.bottom + 1; } else { vert [1] .x = MaxX+10-OffsetX; vert [1] .y = MaxY+13-OffsetY; } vert [1] .Red = 0xff00&DWORD(red*256); vert [1] .Green = 0xff00&DWORD(green*256); vert [1] .Blue = 0xff00&DWORD(blue*256); vert [1] .Alpha = 0x0000; gr[0].UpperLeft=0; gr[0].LowerRight=1; if (MaxY>max(800, MaxX) || hideBG) GradientFill(hDC,vert, 2, gr, 1,GRADIENT_FILL_RECT_H); else GradientFill(hDC,vert, 2, gr, 1,GRADIENT_FILL_RECT_V); if (!hideBG) { SelectObject(hDC, GetSysColorBrush(COLOR_3DSHADOW)); Rectangle(hDC, vert[0].x+3, vert[1].y, vert[1].x+1, vert[1].y+3); Rectangle(hDC, vert[1].x, vert[0].y+3, vert[1].x+3, vert[1].y+3); SelectObject(hDC, GetStockObject(NULL_BRUSH)); SelectObject(hDC, GetStockObject(DC_PEN)); SetDCPenColor(hDC, RGB(DWORD(red*0.4), DWORD(green*0.4), DWORD(blue*0.4))); Rectangle(hDC, vert[0].x, vert[0].y, vert[1].x, vert[1].y); } } void gdioutput::setDBErrorState(bool state) { if (dbErrorState != state) { dbErrorState = state; refresh(); } } void gdioutput::draw(HDC hDC, RECT& rc, RECT& drawArea) { #ifdef DEBUGRENDER if (debugDrawColor) { string ds = "DebugDraw" + itos(drawArea.left) + "-" + itos(drawArea.right) + ", " + itos(drawArea.top) + "-" + itos(drawArea.bottom) + "\n"; OutputDebugString(ds.c_str()); SelectObject(hDC, GetStockObject(DC_BRUSH)); SetDCBrushColor(hDC, debugDrawColor); Rectangle(hDC, rc.left, rc.top, rc.right, rc.bottom); return; } #endif if (highContrast) drawBackground(hDC, drawArea); else drawBackground(hDC, rc); if (drawArea.left > MaxX - OffsetX + 15) { drawBoxes(hDC, rc); return; } if (animationData) { int page = 0; animationData->renderPage(hDC, *this, GetTickCount()); return; } SelectObject(hDC, GetStockObject(DC_BRUSH)); for (auto& rit : Rectangles) renderRectangle(hDC, 0, rit); if (useTables) for (list::iterator tit = Tables.begin(); tit != Tables.end(); ++tit) { tit->table->draw(*this, hDC, tit->xp, tit->yp, rc); } resetLast(); TIList::iterator it; int BoundYup = OffsetY - maxTextBlockHeight - 2 + drawArea.top; int BoundYupTight = OffsetY - 2 + drawArea.top; int BoundYdown = OffsetY + drawArea.bottom + 2; for (auto imgTL : imageReferences) { RenderString(*imgTL, hDC); } if (!renderOptimize || itTL == TL.end()) { #ifdef DEBUGRENDER //if (breakRender) // DebugBreak(); OutputDebugString(("Raw render" + itos(size_t(this)) + "\n").c_str()); #endif for (it = TL.begin(); it != TL.end(); ++it) { TextInfo& ti = *it; if ((ti.format & 0xFF) == textImage) continue; if ((ti.yp > BoundYup || ti.textRect.bottom > BoundYupTight) && ti.yp < BoundYdown) RenderString(*it, hDC); } } else { #ifdef DEBUGRENDER OutputDebugString((itos(++counterRender) + " opt render " + itos(size_t(this)) + "\n").c_str()); #endif while (itTL != TL.end() && itTL->yp < BoundYup) ++itTL; if (itTL != TL.end()) while (itTL != TL.begin() && itTL->yp > BoundYup) --itTL; it = itTL; while (it != TL.end() && it->yp < BoundYdown) { if ((it->format & 0xFF) != textImage) RenderString(*it, hDC); ++it; } } updateStringPosCache(); drawBoxes(hDC, rc); } void gdioutput::renderRectangle(HDC hDC, RECT *clipRegion, const RectangleInfo &ri) { if (ri.drawBorder) { SelectObject(hDC, GetStockObject(DC_PEN)); SetDCPenColor(hDC, RGB(40,40,60)); } else SelectObject(hDC, GetStockObject(NULL_PEN)); if (ri.color == colorTransparent) SelectObject(hDC, GetStockObject(NULL_BRUSH)); else { SetDCBrushColor(hDC, ri.color); } RECT rect_rc=ri.rc; OffsetRect(&rect_rc, -OffsetX, -OffsetY); Rectangle(hDC, rect_rc.left, rect_rc.top, rect_rc.right, rect_rc.bottom); if (ri.color == colorTransparent) SelectObject(hDC, GetStockObject(DC_BRUSH)); } void gdioutput::updateStringPosCache() { RECT rc; GetClientRect(hWndTarget, &rc); int BoundYup = OffsetY-100; int BoundYdown = OffsetY+rc.bottom+10; shownStrings.clear(); TIList::iterator it; if (!renderOptimize || itTL == TL.end()) { for (it=TL.begin();it!=TL.end(); ++it) { TextInfo &ti=*it; if ( ti.yp > BoundYup && ti.yp < BoundYdown) { if (ti.textRect.top != ti.yp - OffsetY) { int diff = it->textRect.top - (ti.yp - OffsetY); ti.textRect.top -= diff; ti.textRect.bottom -= diff; } shownStrings.push_back(&ti); } } } else { TIList::iterator itC = itTL; while( itC != TL.end() && itC->yp < BoundYup) ++itC; if (itC!=TL.end()) while( itC != TL.begin() && itC->yp > BoundYup) --itC; it=itC; while( it != TL.end() && it->yp < BoundYdown) { shownStrings.push_back(&*it); if (it->textRect.top != it->yp - OffsetY) { int diff = it->textRect.top - (it->yp - OffsetY); it->textRect.top -= diff; it->textRect.bottom -= diff; } ++it; } } } TextInfo &gdioutput::addTimer(int yp, int xp, int format, DWORD zeroTime, int xlimit, GUICALLBACK cb, int timeOut, const wchar_t *fontFace) { hasAnyTimer = true; DWORD zt=GetTickCount()-1000*zeroTime; wstring text = getTimerText(zeroTime, format, true); addStringUT(yp, xp, format, text, xlimit, cb, fontFace); TextInfo &ti=TL.back(); ti.hasTimer=true; ti.zeroTime=zt; if (timeOut != NOTIMEOUT) ti.timeOut = ti.zeroTime + timeOut*1000; return ti; } TextInfo &gdioutput::addTimeout(int TimeOut, GUICALLBACK cb) { addStringUT(0, 0, 0, "", 0, cb); TextInfo &ti=TL.back(); ti.hasTimer=true; ti.zeroTime=GetTickCount(); if (TimeOut!=NOTIMEOUT) ti.timeOut=ti.zeroTime+(TimeOut)*1000; return ti; } void CALLBACK gdiTimerProc(HWND hWnd, UINT a, UINT_PTR ptr, DWORD b) { wstring msg; KillTimer(hWnd, ptr); TimerInfo *it = (TimerInfo *)ptr; it->setWnd = 0; try { if (it->parent) { it->parent->timerProc(*it, b); } } catch (const meosCancel&) { return; } catch (meosException &ex) { msg = ex.wwhat(); } catch(std::exception &ex) { string2Wide(ex.what(), msg); if (msg.empty()) msg = L"Ett okänt fel inträffade."; } catch(...) { msg = L"Unexpected error"; } if (!msg.empty()) { MessageBox(hWnd, msg.c_str(), L"MeOS", MB_OK|MB_ICONEXCLAMATION); } } int TimerInfo::globalTimerId = 0; void gdioutput::timerProc(TimerInfo &timer, DWORD timeout) { int timerId = timer.timerId; if (timer.handler) timer.handler->handle(*this, timer, GUI_TIMER); if (timer.managedHandler) timer.managedHandler->handle(*this, timer, GUI_TIMER); else if (timer.callBack) timer.callBack(this, GUI_TIMER, &timer); for (auto it = timers.begin(); it != timers.end(); ++it) { if (it->getId() == timerId) { timers.erase(it); break; } } //timers.erase(remove_if(timers.begin(), timers.end(), [timerId](TimerInfo &x) {return x.getId() == timerId; }), timers.end()); } void gdioutput::removeHandler(GuiHandler *h) { for (auto &it : timers) { if (it.handler == h) it.handler = 0; } for (auto &it : BI) { if (it.handler == h) it.handler = 0; } for (auto &it : II) { if (it.handler == h) it.handler = 0; } for (auto &it : TL) { if (it.handler == h) it.handler = 0; } for (auto &it : LBI) { if (it.handler == h) it.handler = 0; } } void gdioutput::removeTimeoutMilli(const string &id) { for (list::iterator it = timers.begin(); it != timers.end(); ++it) { if (it->id == id) { timers.erase(it); return; } } } TimerInfo &gdioutput::addTimeoutMilli(int timeOut, const string &id, GUICALLBACK cb) { removeTimeoutMilli(id); timers.emplace_back(this, cb); timers.back().id = id; SetTimer(hWndTarget, (UINT_PTR)&timers.back(), timeOut, gdiTimerProc); timers.back().setWnd = hWndTarget; return timers.back(); } TimerInfo:: ~TimerInfo() { handler = 0; callBack = 0; if (setWnd) KillTimer(setWnd, (UINT_PTR)this); } TextInfo& gdioutput::addImage(const string& id, int yp, int xp, int format, const wstring& imageId, int width, int height, GUICALLBACK cb) { bool skipBBCalc = (format & skipBoundingBox) == skipBoundingBox; format &= ~skipBoundingBox; int oldYP = TL.empty() ? -1 : TL.back().yp; TL.emplace_back(); TextInfo& TI = TL.back(); itTL = TL.begin(); imageReferences.push_back(&TI); TI.id = id; TI.format = format | textImage; TI.xp = xp; TI.yp = yp; TI.text = L"L" + imageId; TI.callBack = cb; if (width == 0 || height == 0) { uint64_t imgId = _wcstoui64(imageId.c_str(), nullptr, 10); width = image.getWidth(imgId); height = image.getHeight(imgId); } //if (skipBBCalc) { TI.textRect.left = xp; TI.textRect.top = yp; TI.textRect.right = xp + width; TI.textRect.bottom = yp + height; TI.realWidth = width; FlowDirection oldDir = flowDirection; if (format & imageNoUpdatePos) flowDirection = FlowDirection::None; updatePos(TI.xp, TI.yp, width + scaleLength(10), height + scaleLength(2)); flowDirection = oldDir; if (oldYP > TI.yp) renderOptimize = false; return TL.back(); } TextInfo &gdioutput::addStringUT(int yp, int xp, int format, const string &text, int xlimit, GUICALLBACK cb, const wchar_t *fontFace) { return addStringUT(yp, xp, format, widen(text), xlimit, cb, fontFace); } int gdioutput::getFontHeight(int format, const wstring &fontFace) const { format = format & 0xFF; auto res = fontHeightCache.find(make_pair(format, fontFace)); if (res != fontHeightCache.end()) return res->second; TextInfo TI; TI.format = format; TI.xp = 0; TI.yp = 0; TI.text = L"M1y|"; TI.xlimit = 100; TI.callBack = 0; TI.font = fontFace; calcStringSize(TI); int h = TI.textRect.bottom - TI.textRect.top; fontHeightCache.emplace(make_pair(format, fontFace), h); return h; } TextInfo& gdioutput::addStringUT(int yp, int xp, int format, const wstring& text, int xlimit, GUICALLBACK cb, const wchar_t* fontFace) { bool skipBBCalc = (format & skipBoundingBox) == skipBoundingBox; format &= ~skipBoundingBox; int oldYP = TL.empty() ? -1 : TL.back().yp; TL.emplace_back(); TextInfo& TI = TL.back(); itTL = TL.begin(); if ((format & 0xFF) == textImage) imageReferences.push_back(&TI); TI.format = format; TI.xp = xp; TI.yp = yp; TI.text = text; TI.xlimit = xlimit; TI.callBack = cb; if (fontFace) TI.font = fontFace; if (!skipTextRender(format)) { if (skipBBCalc) { assert(xlimit > 0); int h = getFontHeight(format, fontFace); TI.textRect.left = xp; TI.textRect.top = yp; TI.textRect.right = xp + xlimit; TI.textRect.bottom = yp + h; TI.realWidth = xlimit; updatePos(TI.xp, TI.yp, TI.realWidth + scaleLength(10), TI.textRect.bottom - TI.textRect.top + scaleLength(2)); maxTextBlockHeight = max(maxTextBlockHeight, h + 1); } else { HDC hDC = GetDC(hWndTarget); if (hWndTarget && !manualUpdate) RenderString(TI, hDC); else calcStringSize(TI, hDC); if (xlimit == 0 || (format & (textRight | textCenter)) == 0) { updatePosTight(TI.textRect.left, TI.yp, TI.realWidth, TI.textRect.bottom - TI.textRect.top, scaleLength(10), scaleLength(2)); } else { updatePosTight(TI.xp, TI.yp, TI.realWidth, TI.textRect.bottom - TI.textRect.top, scaleLength(10), scaleLength(2)); } ReleaseDC(hWndTarget, hDC); maxTextBlockHeight = max(maxTextBlockHeight, 1 + TI.textRect.bottom - TI.textRect.top); } if (oldYP > TI.yp) renderOptimize = false; } else { TI.textRect.left = xp; TI.textRect.right = xp; TI.textRect.bottom = yp; TI.textRect.top = yp; } return TL.back(); } TextInfo &gdioutput::addString(const char *id, int yp, int xp, int format, const string &text, int xlimit, GUICALLBACK cb, const wchar_t *fontFace) { return addString(id, yp, xp, format, widen(text), xlimit, cb, fontFace); } TextInfo& gdioutput::addString(const char* id, int yp, int xp, int format, const wstring& text, int xlimit, GUICALLBACK cb, const wchar_t* fontFace) { int oldYP = TL.empty() ? -1 : TL.back().yp; TL.emplace_back(); itTL = TL.begin(); TextInfo& TI = TL.back(); if ((format & 0xFF) == textImage) imageReferences.push_back(&TI); TI.format = format; TI.xp = xp; TI.yp = yp; if ((format & 0xFF) != textImage) { TI.text = lang.tl(text); if ((format & Capitalize) == Capitalize && lang.capitalizeWords()) capitalizeWords(TI.text); } else { TI.text = text; } TI.id = id; TI.xlimit = xlimit; TI.callBack = cb; if (fontFace) TI.font = fontFace; if (!skipTextRender(format)) { HDC hDC = GetDC(hWndTarget); if (hWndTarget && !manualUpdate) RenderString(TI, hDC); else calcStringSize(TI, hDC); if (xlimit == 0 || (format & (textRight | textCenter)) == 0) { updatePos(TI.textRect.right + OffsetX, yp, scaleLength(10), TI.textRect.bottom - TI.textRect.top + scaleLength(2)); } else { updatePos(TI.xp, TI.yp, TI.realWidth + scaleLength(10), TI.textRect.bottom - TI.textRect.top + scaleLength(2)); } ReleaseDC(hWndTarget, hDC); maxTextBlockHeight = max(maxTextBlockHeight, TI.textRect.bottom - TI.textRect.top + 1); if (oldYP > TI.yp) renderOptimize = false; } else { TI.textRect.left = xp; TI.textRect.right = xp; TI.textRect.bottom = yp; TI.textRect.top = yp; } return TL.back(); } TextInfo &gdioutput::addString(const string &id, int format, const string &text, GUICALLBACK cb) { return addString(id.c_str(), CurrentY, CurrentX, format, text, 0, cb); } TextInfo &gdioutput::addString(const string &id, int format, const wstring &text, GUICALLBACK cb) { return addString(id.c_str(), CurrentY, CurrentX, format, text, 0, cb); } TextInfo &gdioutput::addString(const string &id, int yp, int xp, int format, const string &text, int xlimit, GUICALLBACK cb, const wchar_t *fontFace) { return addString(id.c_str(), yp, xp, format, text, xlimit, cb, fontFace); } TextInfo &gdioutput::addString(const string &id, int yp, int xp, int format, const wstring &text, int xlimit, GUICALLBACK cb, const wchar_t *fontFace) { return addString(id.c_str(), yp, xp, format, text, xlimit, cb, fontFace); } TextInfo &gdioutput::addString(const char *id, int format, const string &text, GUICALLBACK cb) { return addString(id, CurrentY, CurrentX, format, text, 0, cb); } TextInfo &gdioutput::addString(const char *id, int format, const wstring &text, GUICALLBACK cb) { return addString(id, CurrentY, CurrentX, format, text, 0, cb); } TextInfo &gdioutput::addStringUT(int format, const string &text, GUICALLBACK cb) { return addStringUT(CurrentY, CurrentX, format, text, 0, cb); } TextInfo &gdioutput::addStringUT(int format, const wstring &text, GUICALLBACK cb) { return addStringUT(CurrentY, CurrentX, format, text, 0, cb); } ButtonInfo &gdioutput::addButton(const string &id, const string &text, GUICALLBACK cb, const string &tooltip) { return addButton(CurrentX, CurrentY, id, text, cb, tooltip); } ButtonInfo &gdioutput::addButton(const string &id, const wstring &text, GUICALLBACK cb, const wstring &tooltip) { return addButton(CurrentX, CurrentY, id, text, cb, tooltip); } ButtonInfo &gdioutput::addButton(int x, int y, const string &id, const string &text, GUICALLBACK cb, const string &tooltip) { return addButton(x,y, id, widen(text), cb, widen(tooltip)); } ButtonInfo &gdioutput::addButton(int x, int y, const string &id, const wstring &text, GUICALLBACK cb, const wstring &tooltip) { HANDLE bm = 0; int width = 0; if (text[0] == '@') { HINSTANCE hInst = GetModuleHandle(0); int ir = _wtoi(text.c_str() + 1); bm = LoadBitmap(hInst, MAKEINTRESOURCE(ir)); SIZE size; size.cx = 24; width = size.cx+4; } else { SIZE size; HDC hDC = GetDC(hWndTarget); SelectObject(hDC, getGUIFont()); wstring ttext = lang.tl(text); int tts = ttext.size(); if (tts > 2 && ttext[0] == '<' && ttext[1] == '<') { ttext = L"◀" + ttext.substr(2); } else if (tts > 2 && ttext[tts-1] == '>' && ttext[tts-2] == '>') { ttext = ttext.substr(0, tts-2) + L"▶"; } if (lang.capitalizeWords()) capitalizeWords(ttext); GetTextExtentPoint32(hDC, ttext.c_str(), ttext.length(), &size); ReleaseDC(hWndTarget, hDC); width = size.cx + scaleLength(30); if (text != L"...") width = max(width, scaleLength(75)); } ButtonInfo &bi=addButton(x, y, width, id, text, cb, tooltip, false, false); if (bm != 0) { SendMessage(bi.hWnd, BM_SETIMAGE, IMAGE_BITMAP, LPARAM(bm)); } return bi; } ButtonInfo &ButtonInfo::setDefault() { flags |= 1; storedFlags |= 1; //SetWindowLong(hWnd, i, GetWindowLong(hWnd, i)|BS_DEFPUSHBUTTON); return *this; } void ButtonInfo::moveButton(gdioutput &gdi, int nxp, int nyp) { xp = nxp; yp = nyp; int w, h; getDimension(gdi, w, h); MoveWindow(hWnd, xp, yp, w, h, true); gdi.updatePos(xp, yp, w, h); } void ButtonInfo::getDimension(const gdioutput &gdi, int &w, int &h) const { RECT rc; GetWindowRect(hWnd, &rc); w = rc.right - rc.left + gdi.scaleLength(GDI_BUTTON_SPACING); h = rc.bottom - rc.top; } ButtonInfo &gdioutput::addButton(int x, int y, int w, const string &id, const string &text, GUICALLBACK cb, const string &tooltip, bool AbsPos, bool hasState) { return addButton(x, y, w, id, widen(text), cb, widen(tooltip), AbsPos, hasState); } ButtonInfo& gdioutput::addButton(int x, int y, int w, const string& id, const wstring& text, GUICALLBACK cb, const wstring& toolTip, bool absPos, bool hasState) { return addButton(x, y, w, getButtonHeight(), id, text, gdiFonts::normalText, cb, toolTip, absPos, hasState); } ButtonInfo& gdioutput::addButton(int x, int y, int width, int height, const string& id, const wstring& text, gdiFonts font, GUICALLBACK cb, const wstring& tooltip, bool absPos, bool hasState) { int style = hasState ? BS_CHECKBOX | BS_PUSHLIKE : BS_PUSHBUTTON; if (text[0] == '@') style |= BS_BITMAP; ButtonInfo bi; wstring ttext = lang.tl(text); int tts = ttext.size(); if (tts > 2 && ttext[0] == '<' && ttext[1] == '<') { ttext = L"◀" + ttext.substr(2); } else if (tts > 2 && ttext[tts - 1] == '>' && ttext[tts - 2] == '>') { ttext = ttext.substr(0, tts - 2) + L"▶"; } if (lang.capitalizeWords()) capitalizeWords(ttext); if (absPos) { if (ttext.find_first_of('\n') != string::npos) { style |= BS_MULTILINE; height *= 2; } bi.hWnd = CreateWindow(L"BUTTON", ttext.c_str(), WS_TABSTOP | WS_VISIBLE | WS_CHILD | WS_CLIPSIBLINGS | style | BS_NOTIFY, x - OffsetX, y, width, height, hWndTarget, NULL, (HINSTANCE)GetWindowLongPtr(hWndTarget, GWLP_HINSTANCE), NULL); } else { bi.hWnd = CreateWindow(L"BUTTON", ttext.c_str(), WS_TABSTOP | WS_VISIBLE | WS_CHILD | WS_CLIPSIBLINGS | style | BS_NOTIFY, x - OffsetX, y - OffsetY - 1, width, height, hWndTarget, NULL, (HINSTANCE)GetWindowLongPtr(hWndTarget, GWLP_HINSTANCE), NULL); } if (font == gdiFonts::normalText) SendMessage(bi.hWnd, WM_SETFONT, (WPARAM)getGUIFont(), 0); else SendMessage(bi.hWnd, WM_SETFONT, (WPARAM)getCurrentFont().getFont(font), 0); if (!absPos) updatePos(x, y, width + scaleLength(GDI_BUTTON_SPACING), height + 5); bi.xp = x; bi.yp = y - 1; bi.width = width; bi.text = ttext; bi.id = id; bi.callBack = cb; bi.AbsPos = absPos; if (tooltip.length() > 0) addToolTip(id, tooltip, bi.hWnd); BI.push_back(bi); biByHwnd[bi.hWnd] = &BI.back(); FocusList.push_back(bi.hWnd); return BI.back(); } static int checkBoxCallback(gdioutput *gdi, GuiEventType type, BaseInfo *data) { if (type == GUI_LINK) { TextInfo *ti = (TextInfo *)data; string cid = ti->id.substr(1); gdi->check(cid, !gdi->isChecked(cid), true); ButtonInfo &bi = ((ButtonInfo &)gdi->getBaseInfo(cid.c_str())); if (bi.callBack || bi.hasEventHandler()) gdi->sendCtrlMessage(cid); //gdi->getBaseInfo(cid); } return 0; } void gdioutput::enableCheckBoxLink(TextInfo &ti, bool enable) { bool needRefresh = false; if (enable) { needRefresh = ti.callBack == 0; ti.callBack = checkBoxCallback; ti.setColor(colorDefault); } else { needRefresh = ti.callBack != 0; ti.callBack = 0; DWORD c = GetSysColor(COLOR_GRAYTEXT); ti.setColor(GDICOLOR(c)); } if (needRefresh) InvalidateRect(hWndTarget, &ti.textRect, true); } ButtonInfo &gdioutput::addCheckbox(const string &id, const string &text, GUICALLBACK cb, bool Checked, const string &tooltip) { return addCheckbox(CurrentX, CurrentY, id, text, cb, Checked, tooltip); } ButtonInfo &gdioutput::addCheckbox(const string &id, const wstring &text, GUICALLBACK cb, bool Checked, const wstring &tooltip) { return addCheckbox(CurrentX, CurrentY, id, text, cb, Checked, tooltip); } ButtonInfo &gdioutput::addCheckbox(int x, int y, const string &id, const string &text, GUICALLBACK cb, bool Checked, const string &tooltip, bool AbsPos) { return addCheckbox(x,y,id, widen(text), cb, Checked, widen(tooltip), AbsPos); } ButtonInfo& gdioutput::addCheckbox(int x, int y, const string& id, const wstring& text, GUICALLBACK cb, bool Checked, const wstring& tooltip, bool AbsPos) { ButtonInfo bi; SIZE size; wstring ttext = lang.tl(text); HDC hDC = GetDC(hWndTarget); SelectObject(hDC, GetStockObject(DEFAULT_GUI_FONT)); GetTextExtentPoint32(hDC, L"M", 1, &size); int ox = OffsetX; int oy = OffsetY; if (AbsPos) { ox = 0; oy = 0; } int h = size.cy; SelectObject(hDC, getGUIFont()); GetTextExtentPoint32(hDC, ttext.c_str(), ttext.length(), &size); ReleaseDC(hWndTarget, hDC); int cbY = y + (size.cy - h) / 2; bi.hWnd = CreateWindowEx(0, L"BUTTON", L"", WS_TABSTOP | WS_VISIBLE | WS_CHILD | WS_CLIPSIBLINGS | BS_AUTOCHECKBOX | BS_NOTIFY, x - ox, cbY - oy, h, h, hWndTarget, NULL, (HINSTANCE)GetWindowLongPtr(hWndTarget, GWLP_HINSTANCE), NULL); TextInfo& desc = addStringUT(y, x + (3 * h) / 2, 0, ttext, 0, checkBoxCallback); desc.id = "T" + id; SendMessage(bi.hWnd, WM_SETFONT, (WPARAM)getGUIFont(), 0); if (Checked) SendMessage(bi.hWnd, BM_SETCHECK, BST_CHECKED, 0); bi.checked = Checked; if (!AbsPos) { if (ttext.empty()) updatePos(x, y, size.cx + int(30 * scale), size.cy + int(scale * 12) + 3); else updatePos(x, y, size.cx + int(30 * scale), desc.textRect.bottom - desc.textRect.top + scaleLength(4)); } if (tooltip.length() > 0) { addToolTip(id, tooltip, bi.hWnd); addToolTip(desc.id, tooltip, 0, &desc.textRect); } bi.isCheckbox = true; bi.xp = x; bi.yp = cbY; bi.width = desc.textRect.right - (x - ox); bi.text = ttext; bi.id = id; bi.callBack = cb; bi.AbsPos = AbsPos; bi.originalState = Checked; bi.isEdit(true); BI.push_back(bi); biByHwnd[bi.hWnd] = &BI.back(); FocusList.push_back(bi.hWnd); return BI.back(); } bool gdioutput::isChecked(const string &id) { list::iterator it; for(it=BI.begin(); it != BI.end(); ++it) if (it->id==id) return SendMessage(it->hWnd, BM_GETCHECK, 0, 0)==BST_CHECKED; return false; } void gdioutput::check(const string &id, bool state, bool keepOriginalState){ list::iterator it; for(it=BI.begin(); it != BI.end(); ++it) { if (it->id==id){ SendMessage(it->hWnd, BM_SETCHECK, state ? BST_CHECKED:BST_UNCHECKED, 0); it->checked = state; it->synchData(); if (!keepOriginalState) it->originalState = state; return; } } #ifdef _DEBUG string err = string("Internal Error, identifier not found: X#") + id; throw std::exception(err.c_str()); #endif } InputInfo &gdioutput::addInput(const string &id, const wstring &text, int length, GUICALLBACK cb, const wstring &explanation, const wstring &help) { return addInput(CurrentX, CurrentY, id, text, length, cb, explanation, help); } HFONT gdioutput::getGUIFont() const { if (scale==1) return (HFONT)GetStockObject(DEFAULT_GUI_FONT); else return getCurrentFont().getGUIFont(); } pair gdioutput::getInputDimension(int length) const { if (!guiMeasure) { HDC hDC = GetDC(hWndTarget); SelectObject(hDC, getGUIFont()); SIZE size; GetTextExtentPoint32(hDC, L"M", 1, &size); SIZE sizeAvg; wstring avgText = L"123456789ABCDEFGHIJHKLMNOPQRSTUVXYZ abcdefghijklmnopqrstuvxyz"; GetTextExtentPoint32(hDC, avgText.c_str(), avgText.length(), &sizeAvg); ReleaseDC(hWndTarget, hDC); int dy = GetSystemMetrics(SM_CYEDGE); int dx = GetSystemMetrics(SM_CXEDGE); guiMeasure = make_shared(); guiMeasure->letterWidth = size.cx; guiMeasure->extraX = 2 * dx; guiMeasure->height = 4 + dy * 2 + size.cy; guiMeasure->avgCharWidth = float(sizeAvg.cx) / float(avgText.length()); } return make_pair(length * guiMeasure->letterWidth + guiMeasure->extraX, guiMeasure->height); } int gdioutput::getButtonHeight() const { return int(getInputDimension(0).second * 1.2);//int(scale * 24) + 0; } InputInfo &gdioutput::addInput(int x, int y, const string &id, const wstring &text, int length, GUICALLBACK cb, const wstring &explanation, const wstring &help) { if (explanation.length()>0) { addString(id + "_label", y, x, 0, explanation); y+=lineHeight; } InputInfo ii; auto dim = getInputDimension(length); int ox=OffsetX; int oy=OffsetY; ii.hWnd=CreateWindowEx(WS_EX_CLIENTEDGE, L"EDIT", text.c_str(), WS_TABSTOP|WS_VISIBLE | WS_CHILD | WS_CLIPSIBLINGS | ES_AUTOHSCROLL | WS_BORDER, x-ox, y-oy, dim.first, dim.second, hWndTarget, NULL, (HINSTANCE)GetWindowLongPtr(hWndTarget, GWLP_HINSTANCE), NULL); int mrg = scaleLength(4); updatePos(x, y, dim.first+mrg, dim.second+mrg); SendMessage(ii.hWnd, WM_SETFONT, (WPARAM) getGUIFont(), 0); ii.xp=x; ii.yp=y; ii.width = dim.first; ii.height = dim.second; ii.text = text; ii.original = text; ii.focusText = text; ii.id=id; ii.callBack=cb; II.push_back(ii); iiByHwnd[ii.hWnd] = &II.back(); if (help.length() > 0) addToolTip(id, help, ii.hWnd); FocusList.push_back(ii.hWnd); if (II.size() == 1) { SetFocus(ii.hWnd); currentFocus = ii.hWnd; } return II.back(); } InputInfo &gdioutput::addInputBox(const string &id, int width, int height, const wstring &text, GUICALLBACK cb, const wstring &explanation) { return addInputBox(id, CurrentX, CurrentY, width, height, text, cb, explanation); } InputInfo &gdioutput::addInputBox(const string &id, int x, int y, int widthIn, int heightIn, const wstring &text, GUICALLBACK cb, const wstring &explanation) { if (explanation.length()>0) { addString("", y, x, 0, explanation); y+=lineHeight; } int width = scaleLength(widthIn); int height = scaleLength(heightIn); InputInfo ii; int ox=OffsetX; int oy=OffsetY; ii.hWnd=CreateWindowEx(WS_EX_CLIENTEDGE, L"EDIT", text.c_str(), WS_HSCROLL|WS_VSCROLL| WS_TABSTOP|WS_VISIBLE|WS_CHILD | WS_CLIPSIBLINGS |ES_AUTOHSCROLL|ES_MULTILINE|ES_AUTOVSCROLL|WS_BORDER, x-ox, y-oy, width, height, hWndTarget, NULL, (HINSTANCE)GetWindowLongPtr(hWndTarget, GWLP_HINSTANCE), NULL); updatePos(x, y, width, height + scaleLength(5)); SendMessage(ii.hWnd, WM_SETFONT, (WPARAM) getGUIFont(), 0); ii.xp=x; ii.yp=y; ii.width = width; ii.height = height; ii.text = text; ii.original = text; ii.focusText = text; ii.id=id; ii.callBack=cb; II.push_back(ii); iiByHwnd[ii.hWnd] = &II.back(); FocusList.push_back(ii.hWnd); return II.back(); } ListBoxInfo &gdioutput::addListBox(const string &id, int width, int height, GUICALLBACK cb, const wstring &explanation, const wstring &tooltip, bool multiple) { return addListBox(CurrentX, CurrentY, id, width, height, cb, explanation, tooltip, multiple); } LRESULT CALLBACK GetMsgProc(HWND hWnd, UINT iMsg, WPARAM wParam, LPARAM lParam) { ListBoxInfo *lbi = (ListBoxInfo *)(GetWindowLongPtr(hWnd, GWLP_USERDATA)); if (!lbi) { throw std::exception("Internal GDI error"); } LPARAM res = CallWindowProc(lbi->originalProc, hWnd, iMsg, wParam, lParam); if (iMsg == WM_VSCROLL || iMsg == WM_MOUSEWHEEL || iMsg == WM_KEYDOWN) { LRESULT topIndex = CallWindowProc(lbi->originalProc, hWnd, LB_GETTOPINDEX, 0, 0); if (lbi->lbiSync) { ListBoxInfo *other = lbi->lbiSync; CallWindowProc(other->originalProc, other->hWnd, LB_SETTOPINDEX, topIndex, 0); } } return res; } void gdioutput::synchronizeListScroll(const string &id1, const string &id2) { ListBoxInfo *a = 0, *b = 0; list::iterator it; for (it = LBI.begin(); it != LBI.end(); ++it) { if (it->id == id1) a = &*it; else if (it->id == id2) b = &*it; } if (!a || !b) throw std::exception("Not found"); a->lbiSync = b; b->lbiSync = a; SetWindowLongPtr(a->hWnd, GWLP_USERDATA, LONG_PTR(a)); SetWindowLongPtr(b->hWnd, GWLP_USERDATA, LONG_PTR(b)); a->originalProc = WNDPROC(GetWindowLongPtr(a->hWnd, GWLP_WNDPROC)); b->originalProc = WNDPROC(GetWindowLongPtr(b->hWnd, GWLP_WNDPROC)); SetWindowLongPtr(a->hWnd, GWLP_WNDPROC, LONG_PTR(GetMsgProc)); SetWindowLongPtr(b->hWnd, GWLP_WNDPROC, LONG_PTR(GetMsgProc)); } ListBoxInfo &gdioutput::addListBox(int x, int y, const string &id, int width, int height, GUICALLBACK cb, const wstring &explanation, const wstring &tooltip, bool multiple) { if (explanation.length()>0) { addString(id+"_label", y, x, 0, explanation); y+=lineHeight; } ListBoxInfo lbi; int ox=OffsetX; int oy=OffsetY; DWORD style=WS_TABSTOP|WS_VISIBLE|WS_CHILD | WS_CLIPSIBLINGS |WS_BORDER|LBS_USETABSTOPS|LBS_NOTIFY|WS_VSCROLL; if (multiple) style|=LBS_MULTIPLESEL; lbi.hWnd=CreateWindowEx(WS_EX_CLIENTEDGE, L"LISTBOX", L"", style, x-ox, y-oy, int(width*scale), int(height*scale), hWndTarget, NULL, (HINSTANCE)GetWindowLongPtr(hWndTarget, GWLP_HINSTANCE), NULL); updatePos(x, y, int(scale*(width+5)), int(scale * (height+2))); SendMessage(lbi.hWnd, WM_SETFONT, (WPARAM) getGUIFont(), 0); lbi.IsCombo=false; lbi.multipleSelection = multiple; lbi.xp=x; lbi.yp=y; lbi.width = scale*width; lbi.height = scale*height; lbi.id=id; lbi.callBack=cb; LBI.push_back(lbi); lbiByHwnd[lbi.hWnd] = &LBI.back(); if (tooltip.length() > 0) addToolTip(id, tooltip, lbi.hWnd); FocusList.push_back(lbi.hWnd); return LBI.back(); } void gdioutput::setSelection(const string &id, const set &selection) { list::iterator it; for(it=LBI.begin(); it != LBI.end(); ++it){ if (it->id==id && !it->IsCombo) { list::const_iterator cit; if (selection.count(-1)==1) SendMessage(it->hWnd, LB_SETSEL, 1, -1); else { LRESULT count=SendMessage(it->hWnd, LB_GETCOUNT, 0,0); SendMessage(it->hWnd, LB_SETSEL, 0, -1); for(int i=0;ihWnd, LB_GETITEMDATA, i, 0); if (selection.count(int(d))==1) SendMessage(it->hWnd, LB_SETSEL, 1, i); } return; } } } } void gdioutput::getSelection(const string &id, set &selection) { list::iterator it; for(it=LBI.begin(); it != LBI.end(); ++it){ if (it->id==id && !it->IsCombo) { selection.clear(); LRESULT count=SendMessage(it->hWnd, LB_GETCOUNT, 0,0); for(int i=0;ihWnd, LB_GETSEL, i, 0); if (s) { LRESULT d=SendMessage(it->hWnd, LB_GETITEMDATA, i, 0); selection.insert(int(d)); } } return; } } #ifdef _DEBUG string err = string("Internal Error, identifier not found: X#") + id; throw std::exception(err.c_str()); #endif } ListBoxInfo &gdioutput::addSelection(const string &id, int width, int height, GUICALLBACK cb, const wstring &explanation, const wstring &tooltip) { return addSelection(CurrentX, CurrentY, id, width, height, cb, explanation, tooltip); } ListBoxInfo &gdioutput::addSelection(int x, int y, const string &id, int width, int height, GUICALLBACK cb, const wstring &explanation, const wstring &tooltip) { if (explanation.length()>0) { addString(id + "_label", y, x, 0, explanation); y+=lineHeight; } ListBoxInfo lbi; int ox = OffsetX; int oy = OffsetY; lbi.hWnd=CreateWindowEx(WS_EX_CLIENTEDGE, L"COMBOBOX", L"", WS_TABSTOP|WS_VISIBLE | WS_CHILD | WS_CLIPSIBLINGS |WS_BORDER|CBS_DROPDOWNLIST|WS_VSCROLL , x-ox, y-oy, int(scale*width), int(scale*height), hWndTarget, NULL, (HINSTANCE)GetWindowLongPtr(hWndTarget, GWLP_HINSTANCE), NULL); updatePos(x, y, int(scale*(width+5)), int(scale*30)); SendMessage(lbi.hWnd, WM_SETFONT, (WPARAM) getGUIFont(), 0); lbi.IsCombo=true; lbi.xp=x; lbi.yp=y; lbi.width = scale*width; lbi.height = scale*30; lbi.id=id; lbi.callBack=cb; LBI.push_back(lbi); lbiByHwnd[lbi.hWnd] = &LBI.back(); if (tooltip.length() > 0) addToolTip(id, tooltip, lbi.hWnd); FocusList.push_back(lbi.hWnd); return LBI.back(); } ListBoxInfo &gdioutput::addCombo(const string &id, int width, int height, GUICALLBACK cb, const wstring &explanation, const wstring &tooltip) { return addCombo(CurrentX, CurrentY, id, width, height, cb, explanation, tooltip); } ListBoxInfo &gdioutput::addCombo(int x, int y, const string &id, int width, int height, GUICALLBACK cb, const wstring &explanation, const wstring &tooltip) { if (explanation.length()>0) { addString(id + "_label", y, x, 0, explanation); y+=lineHeight; } ListBoxInfo lbi; int ox=OffsetX; int oy=OffsetY; lbi.hWnd=CreateWindowEx(WS_EX_CLIENTEDGE, L"COMBOBOX", L"", WS_TABSTOP|WS_VISIBLE | WS_CHILD | WS_CLIPSIBLINGS |WS_BORDER|CBS_DROPDOWN |CBS_AUTOHSCROLL, x-ox, y-oy, int(scale*width), int(scale*height), hWndTarget, NULL, (HINSTANCE)GetWindowLongPtr(hWndTarget, GWLP_HINSTANCE), NULL); updatePos(x, y, int(scale * (width+5)), getButtonHeight()+scaleLength(5)); SendMessage(lbi.hWnd, WM_SETFONT, (WPARAM) getGUIFont(), 0); lbi.IsCombo=true; lbi.xp=x; lbi.yp=y; lbi.width = scale*width; lbi.height = scale*height; lbi.id=id; lbi.callBack=cb; LBI.push_back(lbi); lbiByHwnd[lbi.hWnd] = &LBI.back(); if (tooltip.length() > 0) addToolTip(id, tooltip, lbi.hWnd); FocusList.push_back(lbi.hWnd); return LBI.back(); } bool gdioutput::addItem(const string &id, const wstring &text, size_t data) { list::reverse_iterator it; for (it=LBI.rbegin(); it != LBI.rend(); ++it) { if (it->id==id) { if (it->IsCombo) { LRESULT index=SendMessage(it->hWnd, CB_ADDSTRING, 0, LPARAM(text.c_str())); SendMessage(it->hWnd, CB_SETITEMDATA, index, data); it->data2Index[data] = int(index); it->computed_hash = 0; } else { LRESULT index=SendMessage(it->hWnd, LB_INSERTSTRING, -1, LPARAM(text.c_str())); SendMessage(it->hWnd, LB_SETITEMDATA, index, data); it->data2Index[data] = int(index); it->computed_hash = 0; } return true; } } return false; } bool gdioutput::modifyItemDescription(const string& id, size_t itemData, const wstring &description) { for (auto it = LBI.rbegin(); it != LBI.rend(); ++it) { if (it->id == id) { int ix = it->data2Index[itemData]; // It is intentioal that the hash is not modified. This method allows "customization" of // some description without reloading a complete listbox if (it->IsCombo) { SendMessage(it->hWnd, CB_DELETESTRING, ix, 0); SendMessage(it->hWnd, CB_INSERTSTRING, ix, LPARAM(description.c_str())); SendMessage(it->hWnd, CB_SETITEMDATA, ix, itemData); } else { SendMessage(it->hWnd, LB_DELETESTRING, ix, 0); SendMessage(it->hWnd, LB_INSERTSTRING, ix, LPARAM(description.c_str())); SendMessage(it->hWnd, LB_SETITEMDATA, ix, itemData); } return true; } } return false; } bool gdioutput::setItems(const string& id, const vector>& items) { auto hash = ListBoxInfo::computeItemHash(items); for (auto it = LBI.rbegin(); it != LBI.rend(); ++it) { if (it->id == id) { if (it->IsCombo) { if (it->computed_hash == 0 || it->computed_hash != hash) { SendMessage(it->hWnd, CB_RESETCONTENT, 0, 0); SendMessage(it->hWnd, CB_INITSTORAGE, items.size(), 48); SendMessage(it->hWnd, WM_SETREDRAW, FALSE, 0); it->data2Index.clear(); for (size_t k = 0; k < items.size(); k++) { LRESULT index = SendMessage(it->hWnd, CB_ADDSTRING, 0, LPARAM(items[k].first.c_str())); SendMessage(it->hWnd, CB_SETITEMDATA, index, items[k].second); it->data2Index[items[k].second] = int(index); } SendMessage(it->hWnd, WM_SETREDRAW, TRUE, 0); RedrawWindow(it->hWnd, NULL, NULL, RDW_ERASE | RDW_FRAME | RDW_INVALIDATE | RDW_ALLCHILDREN); it->computed_hash = hash; } } else { if (it->computed_hash == 0 || it->computed_hash != hash) { SendMessage(it->hWnd, LB_RESETCONTENT, 0, 0); SendMessage(it->hWnd, LB_INITSTORAGE, items.size(), 48); SendMessage(it->hWnd, WM_SETREDRAW, FALSE, 0); it->data2Index.clear(); for (size_t k = 0; k < items.size(); k++) { LRESULT index = SendMessage(it->hWnd, LB_INSERTSTRING, -1, LPARAM(items[k].first.c_str())); SendMessage(it->hWnd, LB_SETITEMDATA, index, items[k].second); it->data2Index[items[k].second] = int(index); } SendMessage(it->hWnd, WM_SETREDRAW, TRUE, 0); RedrawWindow(it->hWnd, NULL, NULL, RDW_ERASE | RDW_FRAME | RDW_INVALIDATE | RDW_ALLCHILDREN); it->computed_hash = hash; } } return true; } } return false; } void gdioutput::filterOnData(const string &id, const unordered_set &filter) { list::iterator it; for (it=LBI.begin(); it != LBI.end(); ++it) { if (it->id==id) { if (it->IsCombo) { } else { it->computed_hash = 0; const HWND &hWnd = it->hWnd; LRESULT count = SendMessage(hWnd, LB_GETCOUNT, 0, 0); for (intptr_t ix = count - 1; ix>=0; ix--) { LRESULT ret = SendMessage(hWnd, LB_GETITEMDATA, ix, 0); if (ret != LB_ERR && filter.count(int(ret)) == 0) SendMessage(hWnd, LB_DELETESTRING, ix, 0); } return; } } } assert(false); } bool gdioutput::clearList(const string& id) { for (auto it = LBI.begin(); it != LBI.end(); ++it) { if (it->id == id) { it->original = L""; it->originalIdx = -1; it->computed_hash = 0; it->data2Index.clear(); if (it->IsCombo) SendMessage(it->hWnd, CB_RESETCONTENT, 0, 0); else SendMessage(it->hWnd, LB_RESETCONTENT, 0, 0); return true; } } return false; } bool gdioutput::getSelectedItem(const string &id, ListBoxInfo &lbi) { lbi = ListBoxInfo(); list::iterator it; for (it=LBI.begin(); it != LBI.end(); ++it) { if (it->id==id) { bool ret = getSelectedItem(*it); it->copyUserData(lbi); return ret; } } return false; } pair gdioutput::getSelectedItem(const string &id) { ListBoxInfo lbi; bool ret = getSelectedItem(id, lbi); return make_pair(lbi.getDataInt(), ret); } pair gdioutput::getSelectedItem(const char *id) { string ids = id; return getSelectedItem(ids); } void ListBoxInfo::copyUserData(ListBoxInfo &dest) const { dest.data = data; dest.text = text; dest.id = id; dest.extra = extra; dest.index = index; dest.IsCombo = IsCombo; } uint64_t ListBoxInfo::computeItemHash(const vector>& items) { uint64_t res = 1; for (auto& it : items) { res = res * 997 + it.second; for (auto ch : it.first) res = res * 2003 + ch; } return res; } bool gdioutput::getSelectedItem(ListBoxInfo &lbi) { if (lbi.IsCombo) { LRESULT index=SendMessage(lbi.hWnd, CB_GETCURSEL, 0, 0); if (index == CB_ERR) { wchar_t bf[256]; GetWindowText(lbi.hWnd, bf, 256); lbi.text=bf; lbi.data=-1; lbi.index=int(index); return false; } lbi.data=SendMessage(lbi.hWnd, CB_GETITEMDATA, index, 0); wchar_t bf[1024]; if (SendMessage(lbi.hWnd, CB_GETLBTEXT, index, LPARAM(bf)) != CB_ERR) lbi.text=bf; } else { LRESULT index=SendMessage(lbi.hWnd, LB_GETCURSEL, 0, 0); if (index==LB_ERR) return false; lbi.data=SendMessage(lbi.hWnd, LB_GETITEMDATA, index, 0); lbi.index=int(index); TCHAR bf[1024]; if (SendMessage(lbi.hWnd, LB_GETTEXT, index, LPARAM(bf))!=LB_ERR) lbi.text=bf; } return true; } int gdioutput::getNumItems(const char *id) { for (auto &lbi : LBI) { if (lbi.id == id) { if (lbi.IsCombo) { return (int)SendMessage(lbi.hWnd, CB_GETCOUNT, 0, 0); } else { return (int)SendMessage(lbi.hWnd, LB_GETCOUNT, 0, 0); } } } #ifdef _DEBUG string err = string("Internal Error, identifier not found: X#") + id; throw std::exception(err.c_str()); #endif return 0; } int gdioutput::getItemDataByName(const char *id, const char *name) const{ wstring wname = recodeToWide(name); list::const_iterator it; for(it = LBI.begin(); it != LBI.end(); ++it){ if (it->id==id) { if (it->IsCombo) { LRESULT ix = SendMessage(it->hWnd, CB_FINDSTRING, -1, LPARAM(wname.c_str())); if (ix >= 0) { return (int)SendMessage(it->hWnd, CB_GETITEMDATA, ix, 0); } return -1; } else { LRESULT ix = SendMessage(it->hWnd, LB_FINDSTRING, -1, LPARAM(wname.c_str())); if (ix >= 0) { return (int)SendMessage(it->hWnd, LB_GETITEMDATA, ix, 0); } return -1; } } } return -1; } bool gdioutput::selectItemByData(const char *id, int data) { list::iterator it; for(it=LBI.begin(); it != LBI.end(); ++it){ if (it->id==id) { if (it->IsCombo) { if (data==-1) { SendMessage(it->hWnd, CB_SETCURSEL, -1, 0); it->data = 0; it->text = L""; it->original = L""; it->originalIdx = -1; return true; } else { LRESULT count = SendMessage(it->hWnd, CB_GETCOUNT, 0, 0); for (int m = 0; m < count; m++) { LRESULT ret = SendMessage(it->hWnd, CB_GETITEMDATA, m, 0); if (ret == data) { SendMessage(it->hWnd, CB_SETCURSEL, m, 0); it->data = data; it->originalIdx = data; TCHAR bf[1024]; if (SendMessage(it->hWnd, CB_GETLBTEXT, m, LPARAM(bf))!=CB_ERR) { it->text = bf; it->original = bf; } return true; } } } return false; } else { if (data==-1) { SendMessage(it->hWnd, LB_SETCURSEL, -1, 0); it->data=0; it->text = L""; it->original = L""; it->originalIdx = -1; return true; } else { LRESULT count = SendMessage(it->hWnd, LB_GETCOUNT, 0, 0); for (int m = 0; m < count; m++) { LRESULT ret = SendMessage(it->hWnd, LB_GETITEMDATA, m, 0); if (ret == data) { SendMessage(it->hWnd, LB_SETCURSEL, m, 0); it->data = data; it->originalIdx = data; TCHAR bf[1024]; if (SendMessage(it->hWnd, LB_GETTEXT, m, LPARAM(bf)) != LB_ERR) { it->text = bf; it->original = bf; } return true; } } } return false; } } } return false; } bool gdioutput::selectItemByIndex(const char *id, int index) { for (auto it = LBI.begin(); it != LBI.end(); ++it) { if (it->id == id) { if (it->IsCombo) { if (index == -1) { SendMessage(it->hWnd, CB_SETCURSEL, -1, 0); it->data = 0; it->text = L""; it->original = L""; it->originalIdx = -1; return true; } else { SendMessage(it->hWnd, CB_SETCURSEL, index, 0); LRESULT data = SendMessage(it->hWnd, CB_GETITEMDATA, index, 0); it->data = data; it->originalIdx = data; TCHAR bf[1024]; if (SendMessage(it->hWnd, CB_GETLBTEXT, index, LPARAM(bf)) != CB_ERR) { it->text = bf; it->original = bf; } return true; } return false; } else { if (index == -1) { SendMessage(it->hWnd, LB_SETCURSEL, -1, 0); it->data = 0; it->text = L""; it->original = L""; it->originalIdx = -1; return true; } else { SendMessage(it->hWnd, LB_SETCURSEL, index, 0); LRESULT data = SendMessage(it->hWnd, LB_GETITEMDATA, index, 0); it->data = data; it->originalIdx = data; TCHAR bf[1024]; if (SendMessage(it->hWnd, LB_GETTEXT, index, LPARAM(bf)) != LB_ERR) { it->text = bf; it->original = bf; } return true; } return false; } } } return false; } bool gdioutput::autoGrow(const char *id) { list::iterator it; int size = 0; TextInfo TI; TI.format=0; TI.xp=0; TI.yp=0; TI.id=""; TI.xlimit=0; TI.callBack=0; HDC hDC=GetDC(hWndTarget); for(it=LBI.begin(); it != LBI.end(); ++it){ if (it->id==id) { if (it->IsCombo) { LRESULT count = SendMessage(it->hWnd, CB_GETCOUNT, 0, 0); for (int m = 0; m < count; m++) { wchar_t bf[1024]; if (SendMessage(it->hWnd, CB_GETLBTEXT, m, LPARAM(bf))!=CB_ERR) { TI.text = bf; calcStringSize(TI, hDC); size = max(size, TI.textRect.right - TI.textRect.left); } } ReleaseDC(hWndTarget, hDC); size += scaleLength(30); if (size > it->width) { it->width = size; SetWindowPos(it->hWnd, 0, 0, 0, (int)it->width, (int)it->height, SWP_NOZORDER|SWP_NOCOPYBITS|SWP_NOMOVE); updatePos(it->xp, it->yp, (int)it->width + int(scale*5), (int)it->height); return true; } return false; } else { LRESULT count = SendMessage(it->hWnd, LB_GETCOUNT, 0, 0); for (int m = 0; m < count; m++) { wchar_t bf[1024]; LRESULT len = SendMessage(it->hWnd, LB_GETTEXT, m, LPARAM(bf)); if (len!=LB_ERR) { if (it->lastTabStop == 0) TI.text = bf; else { auto pos = len; while(pos > 0) { if (bf[pos-1] == '\t') { break; } pos--; } TI.text = &bf[pos]; } calcStringSize(TI, hDC); size = max(size, TI.realWidth + it->lastTabStop); } } ReleaseDC(hWndTarget, hDC); size += scaleLength(30); if (size > it->width) { it->width = size; SetWindowPos(it->hWnd, 0, 0, 0, (int)it->width, (int)it->height, SWP_NOZORDER|SWP_NOCOPYBITS|SWP_NOMOVE); updatePos(it->xp, it->yp, (int)it->width+int(scale*5), (int)it->height); return true; } return false; } } } ReleaseDC(hWndTarget, hDC); return false; } void gdioutput::removeSelected(const char *id) { } LRESULT gdioutput::ProcessMsg(UINT iMessage, LPARAM lParam, WPARAM wParam) { wstring msg; try { return ProcessMsgWrp(iMessage, lParam, wParam); } catch (const meosCancel&) { return false; } catch (meosException & ex) { msg = ex.wwhat(); } catch(std::exception &ex) { msg=widen(ex.what()); if (msg.empty()) msg=L"Ett okänt fel inträffade."; } catch(...) { msg=L"Ett okänt fel inträffade."; } if (!msg.empty()) { alert(msg); setWaitCursor(false); } return 0; } void gdioutput::processButtonMessage(ButtonInfo &bi, WPARAM wParam) { WORD hwParam = HIWORD(wParam); switch (hwParam) { case BN_CLICKED: { string cmd; if (getRecorder().recording()) { if (bi.isExtraString()) { cmd = "press(\"" + bi.id + "\", \"" + narrow(bi.getExtra()) + "\"); //" + toUTF8(bi.text); } else { int arg = int((size_t)bi.extra); if (arg > 1000000 || arg < -1000000 || arg == 0) cmd = "press(\"" + bi.id + "\"); //" + toUTF8(bi.text); else cmd = "press(\"" + bi.id + "\", " + itos(bi.getExtraInt()) + "); //" + toUTF8(bi.text); } } if (bi.isCheckbox) bi.checked = SendMessage(bi.hWnd, BM_GETCHECK, 0, 0)==BST_CHECKED; bi.synchData(); if (bi.callBack || bi.hasEventHandler()) { setWaitCursor(true); if (!bi.handleEvent(*this, GUI_BUTTON) && bi.callBack) bi.callBack(this, GUI_BUTTON, &bi); //it may be destroyed here... setWaitCursor(false); } getRecorder().record(cmd); break; } case BN_SETFOCUS: if (currentFocus.hWnd != bi.hWnd) { // if (currentF ocus.wasTabbed) // Button_SetState(currentFocus.hWnd, false); currentFocus = bi.hWnd; } break; case BN_KILLFOCUS: if (currentFocus.hWnd == bi.hWnd) { // if (currentFocus.wasTabbed) // Button_SetState(currentFocus.hWnd, false); } break; } } void gdioutput::processEditMessage(InputInfo &bi, WPARAM wParam) { WORD hwParam = HIWORD(wParam); switch (hwParam) { case EN_CHANGE: if (bi.writeLock) return; getWindowText(bi.hWnd, bi.text); if (bi.handler) bi.handler->handle(*this, bi, GUI_INPUTCHANGE); else if (bi.managedHandler) bi.managedHandler->handle(*this, bi, GUI_INPUTCHANGE); else if (bi.callBack) bi.callBack(this, GUI_INPUTCHANGE, &bi); //it may be destroyed here... break; case EN_KILLFOCUS: { autoCompleteInfo.reset(); wstring old = bi.focusText; getWindowText(bi.hWnd, bi.text); bi.synchData(); bool equal = old == bi.text; string cmd = "input(\"" + bi.id + "\", \"" + toUTF8(bi.text) + "\");"; if (bi.handler) bi.handler->handle(*this, bi, GUI_INPUT); else if (bi.managedHandler) bi.managedHandler->handle(*this, bi, GUI_INPUT); else if (bi.callBack) bi.callBack(this, GUI_INPUT, &bi); if (!equal) getRecorder().record(cmd); break; } case EN_SETFOCUS: currentFocus = bi.hWnd; getWindowText(bi.hWnd, bi.text); bi.synchData(); bi.focusText = bi.text; if (bi.handler) bi.handler->handle(*this, bi, GUI_FOCUS); else if (bi.managedHandler) bi.managedHandler->handle(*this, bi, GUI_FOCUS); else if (bi.callBack) bi.callBack(this, GUI_FOCUS, &bi); break; } } void gdioutput::processComboMessage(ListBoxInfo &bi, WPARAM wParam) { WORD hwParam = HIWORD(wParam); LRESULT index; switch (hwParam) { case CBN_SETFOCUS: currentFocus = bi.hWnd; lockUpDown = true; break; case CBN_KILLFOCUS: { if (autoCompleteInfo && !autoCompleteInfo->locked()) autoCompleteInfo.reset(); lockUpDown = false; TCHAR bf[1024]; index=SendMessage(bi.hWnd, CB_GETCURSEL, 0, 0); if (index != CB_ERR) { if (SendMessage(bi.hWnd, CB_GETLBTEXT, index, LPARAM(bf)) != CB_ERR) { bi.text = bf; bi.data=SendMessage(bi.hWnd, CB_GETITEMDATA, index, 0); if (bi.handler) bi.handler->handle(*this, bi, GUI_COMBO); else if (bi.callBack) bi.callBack(this, GUI_COMBO, &bi); //it may be destroyed here... } } else { GetWindowText(bi.hWnd, bf, sizeof(bf)-1); bi.data = -1; bi.text = bf; string cmd = "input(\"" + bi.id + "\", \"" + toUTF8(bi.text) + "\");"; if (bi.handler) bi.handler->handle(*this, bi, GUI_COMBO); else if (bi.callBack) bi.callBack(this, GUI_COMBO, &bi); //it may be destroyed here... getRecorder().record(cmd); } } break; case CBN_EDITCHANGE: { if (bi.writeLock) return; getWindowText(bi.hWnd, bi.text); if (bi.handler) bi.handler->handle(*this, bi, GUI_COMBOCHANGE); else if (bi.callBack) bi.callBack(this, GUI_COMBOCHANGE, &bi); //it may be destroyed here... break; } case CBN_SELCHANGE: index=SendMessage(bi.hWnd, CB_GETCURSEL, 0, 0); if (index != CB_ERR) { bi.data=SendMessage(bi.hWnd, CB_GETITEMDATA, index, 0); TCHAR bf[1024]; if (SendMessage(bi.hWnd, CB_GETLBTEXT, index, LPARAM(bf)) != CB_ERR) bi.text=bf; string cmd = "select(\"" + bi.id + "\", " + itos(bi.data) + ");"; internalSelect(bi); getRecorder().record(cmd); } break; } } void gdioutput::keyCommand(KeyCommandCode code) { if (hasCommandLock()) return; if (code == KC_SLOWDOWN) autoSpeed *= 0.9; else if (code == KC_SPEEDUP) autoSpeed *= 1.0/0.9; wstring msg; try { list::iterator tit; if (useTables) { for (tit=Tables.begin(); tit!=Tables.end(); ++tit) if (tit->table->keyCommand(*this, code)) return; } for (list::iterator it = Events.begin(); it != Events.end(); ++it) { if (it->getKeyCommand() == code) { it->setData("", 0); it->setExtra(0); if (!it->handleEvent(*this, GUI_EVENT) && it->callBack) { it->callBack(this, GUI_EVENT, &*it); //it may be destroyed here... } return; } } } catch (const meosCancel&) { return; } catch (meosException & ex) { msg = ex.wwhat(); } catch(std::exception &ex) { msg = widen(ex.what()); if (msg.empty()) msg = L"Ett okänt fel inträffade."; } catch(...) { msg = L"Ett okänt fel inträffade."; } if (!msg.empty()) alert(msg); } void gdioutput::processListMessage(ListBoxInfo &bi, WPARAM wParam) { WORD hwParam = HIWORD(wParam); LRESULT index; switch (hwParam) { case LBN_SETFOCUS: currentFocus = bi.hWnd; lockUpDown = true; break; case LBN_KILLFOCUS: autoCompleteInfo.reset(); lockUpDown = false; break; case LBN_SELCHANGE: case LBN_DBLCLK: index=SendMessage(bi.hWnd, LB_GETCURSEL, 0, 0); if (index!=LB_ERR) { bi.data = SendMessage(bi.hWnd, LB_GETITEMDATA, index, 0); TCHAR bf[1024]; if (SendMessage(bi.hWnd, LB_GETTEXT, index, LPARAM(bf)) != LB_ERR) bi.text = bf; string cmd; if (hwParam == LBN_SELCHANGE) cmd = "select(\"" + bi.id + "\", " + itos(bi.data) + ");"; else cmd = "dblclick(\"" + bi.id + "\", " + itos(bi.data) + ");"; if (bi.callBack || bi.handler) { setWaitCursor(true); if (hwParam == LBN_SELCHANGE) { if (bi.handler) bi.handler->handle(*this, bi, GUI_LISTBOX); else bi.callBack(this, GUI_LISTBOX, &bi); //it may be destroyed here... } else { if (bi.handler) bi.handler->handle(*this, bi, GUI_LISTBOXSELECT); else bi.callBack(this, GUI_LISTBOXSELECT, &bi); //it may be destroyed here... } setWaitCursor(false); } getRecorder().record(cmd); } break; } } LRESULT gdioutput::ProcessMsgWrp(UINT iMessage, LPARAM lParam, WPARAM wParam) { if (iMessage == WM_COMMAND) { WORD hwParam = HIWORD(wParam); HWND hWnd = (HWND)lParam; if (hwParam == EN_CHANGE) { list::iterator tit; if (useTables) for (tit = Tables.begin(); tit != Tables.end(); ++tit) if (tit->table->inputChange(*this, hWnd)) return 0; } { //list::iterator it; //for (it=BI.begin(); it != BI.end(); ++it) { unordered_map::iterator it = biByHwnd.find(HWND(lParam)); // if (it->hWnd==hWnd) { if (it != biByHwnd.end()) { ButtonInfo &bi = *it->second; processButtonMessage(bi, wParam); return 0; } //} } { unordered_map::iterator it = iiByHwnd.find(HWND(lParam)); if (it != iiByHwnd.end()) { InputInfo &ii = *it->second; processEditMessage(ii, wParam); return 0; } //list::iterator it; /*for (it=II.begin(); it != II.end(); ++it) { if (it->hWnd==hWnd) { processEditMessage(*it, wParam); return 0; } }*/ } { //list::iterator it; //for(it=LBI.begin(); it != LBI.end(); ++it) { unordered_map::iterator it = lbiByHwnd.find(HWND(lParam)); if (it != lbiByHwnd.end()) { ListBoxInfo &lbi = *it->second; if (lbi.IsCombo) processComboMessage(lbi, wParam); else processListMessage(lbi, wParam); return 0; } } } else if (iMessage == WM_MOUSEMOVE) { POINT pt; pt.x = (signed short)LOWORD(lParam); pt.y = (signed short)HIWORD(lParam); list::iterator tit; bool GotCapture = false; if (useTables) for (tit = Tables.begin(); tit != Tables.end(); ++tit) GotCapture = tit->table->mouseMove(*this, pt.x, pt.y) || GotCapture; if (GotCapture) return 0; list::iterator it = IBox.begin(); while (it != IBox.end()) { if (PtInRect(&it->TextRect, pt) && (it->callBack || it->hasEventHandler())) { SetCursor(LoadCursor(NULL, IDC_HAND)); HDC hDC = GetDC(hWndTarget); drawBoxText(hDC, it->TextRect, *it, true); ReleaseDC(hWndTarget, hDC); SetCapture(hWndTarget); GotCapture = true; it->HasTCapture = true; } else { if (it->HasTCapture) { HDC hDC = GetDC(hWndTarget); drawBoxText(hDC, it->TextRect, *it, false); ReleaseDC(hWndTarget, hDC); if (!GotCapture) ReleaseCapture(); it->HasTCapture = false; } } if (it->HasCapture) { if (GetCapture() != hWndTarget) { HDC hDC = GetDC(hWndTarget); drawCloseBox(hDC, it->Close, false); ReleaseDC(hWndTarget, hDC); if (!GotCapture) ReleaseCapture(); it->HasCapture = false; } else if (!PtInRect(&it->Close, pt)) { HDC hDC = GetDC(hWndTarget); drawCloseBox(hDC, it->Close, false); ReleaseDC(hWndTarget, hDC); } else { HDC hDC = GetDC(hWndTarget); drawCloseBox(hDC, it->Close, true); ReleaseDC(hWndTarget, hDC); } } ++it; } for (size_t k = 0; k < shownStrings.size(); k++) { TextInfo &ti = *shownStrings[k]; if ((!ti.callBack && !ti.hasEventHandler()) || ti.hasTimer) continue; if (PtInRect(&ti.textRect, pt)) { if (!ti.highlight) { ti.highlight = true; InvalidateRect(hWndTarget, &ti.textRect, true); } SetCapture(hWndTarget); GotCapture = true; ti.hasCapture = true; SetCursor(LoadCursor(NULL, IDC_HAND)); } else { if (ti.highlight) { ti.highlight = false; InvalidateRect(hWndTarget, &ti.textRect, true); } if (ti.hasCapture) { if (!GotCapture) ReleaseCapture(); ti.hasCapture = false; } } } } else if (iMessage == WM_LBUTTONDOWN) { if (autoCompleteInfo) { autoCompleteInfo.reset(); return 0; } list::iterator it = IBox.begin(); POINT pt; pt.x = (signed short)LOWORD(lParam); pt.y = (signed short)HIWORD(lParam); list::iterator tit; if (useTables) { for (tit = Tables.begin(); tit != Tables.end(); ++tit) if (tit->table->mouseLeftDown(*this, pt.x, pt.y)) return 0; } while (it != IBox.end()) { if (PtInRect(&it->Close, pt)) { HDC hDC = GetDC(hWndTarget); drawCloseBox(hDC, it->Close, true); ReleaseDC(hWndTarget, hDC); SetCapture(hWndTarget); it->HasCapture = true; } ++it; } //Handle links for (size_t k = 0; k < shownStrings.size(); k++) { TextInfo &ti = *shownStrings[k]; if (!ti.callBack && !ti.hasEventHandler()) continue; if (ti.hasCapture) { HDC hDC = GetDC(hWndTarget); if (PtInRect(&ti.textRect, pt)) { ti.active = true; RenderString(ti, hDC); } ReleaseDC(hWndTarget, hDC); } } } else if (iMessage == WM_LBUTTONUP) { list::iterator tit; list::iterator it = IBox.begin(); POINT pt; pt.x = (signed short)LOWORD(lParam); pt.y = (signed short)HIWORD(lParam); if (useTables) { for (tit = Tables.begin(); tit != Tables.end(); ++tit) if (tit->table->mouseLeftUp(*this, pt.x, pt.y)) return 0; } while (it != IBox.end()) { if (it->HasCapture) { HDC hDC = GetDC(hWndTarget); drawCloseBox(hDC, it->Close, false); ReleaseDC(hWndTarget, hDC); ReleaseCapture(); it->HasCapture = false; if (PtInRect(&it->Close, pt)) { IBox.erase(it); refresh(); return 0; } } else if (it->HasTCapture) { ReleaseCapture(); it->HasTCapture = false; if (PtInRect(&it->TextRect, pt)) { if (!it->handleEvent(*this, GUI_INFOBOX) && it->callBack) it->callBack(this, GUI_INFOBOX, &*it); //it may be destroyed here... return 0; } } ++it; } //Handle links for (size_t k = 0; k < shownStrings.size(); k++) { TextInfo &ti = *shownStrings[k]; if (!ti.callBack && !ti.hasEventHandler()) continue; if (ti.hasCapture) { ReleaseCapture(); ti.hasCapture = false; if (PtInRect(&ti.textRect, pt)) { if (ti.active) { string cmd; if (ti.getExtraInt() != 0) cmd = "click(\"" + ti.id + "\", " + itos(ti.getExtraInt()) + "); //" + toUTF8(ti.text); else cmd = "click(\"" + ti.id + "\"); //" + toUTF8(ti.text); ti.active = false; RenderString(ti); if (!ti.handleEvent(*this, GUI_LINK)) ti.callBack(this, GUI_LINK, &ti); getRecorder().record(cmd); return 0; } } } else if (ti.active) { ti.active = false; RenderString(ti); } } } else if (iMessage == WM_LBUTTONDBLCLK) { list::iterator tit; POINT pt; pt.x = (signed short)LOWORD(lParam); pt.y = (signed short)HIWORD(lParam); if (useTables) for (tit = Tables.begin(); tit != Tables.end(); ++tit) if (tit->table->mouseLeftDblClick(*this, pt.x, pt.y)) return 0; } else if (iMessage == WM_RBUTTONDOWN) { POINT pt; pt.x = (signed short)LOWORD(lParam); pt.y = (signed short)HIWORD(lParam); if (useTables) { for (auto tit = Tables.begin(); tit != Tables.end(); ++tit) if (tit->table->mouseRightDown(*this, pt.x, pt.y)) return 0; } } else if (iMessage == WM_RBUTTONUP) { POINT pt; pt.x = (signed short)LOWORD(lParam); pt.y = (signed short)HIWORD(lParam); if (useTables) { for (auto tit = Tables.begin(); tit != Tables.end(); ++tit) if (tit->table->mouseRightUp(*this, pt.x, pt.y)) return 0; } } else if (iMessage == WM_MBUTTONDOWN) { POINT pt; pt.x = (signed short)LOWORD(lParam); pt.y = (signed short)HIWORD(lParam); if (useTables) { for (auto tit = Tables.begin(); tit != Tables.end(); ++tit) if (tit->table->mouseMidDown(*this, pt.x, pt.y)) return 0; } } else if (iMessage == WM_MBUTTONUP) { POINT pt; pt.x = (signed short)LOWORD(lParam); pt.y = (signed short)HIWORD(lParam); if (useTables) { for (auto tit = Tables.begin(); tit != Tables.end(); ++tit) if (tit->table->mouseMidUp(*this, pt.x, pt.y)) return 0; } } else if (iMessage == WM_CHAR) { /*list::iterator tit; if (useTables) for (tit=Tables.begin(); tit!=Tables.end(); ++tit) if (tit->table->character(*this, int(wParam), lParam & 0xFFFF)) return 0;*/ } else if (iMessage == WM_CTLCOLOREDIT) { unordered_map::iterator it = iiByHwnd.find(HWND(lParam)); if (it != iiByHwnd.end()) { InputInfo &ii = *it->second; if (ii.bgColor != colorDefault || ii.fgColor != colorDefault) { if (ii.bgColor != colorDefault) { SetDCBrushColor(HDC(wParam), ii.bgColor); SetBkColor(HDC(wParam), ii.bgColor); } else { SetDCBrushColor(HDC(wParam), GetSysColor(COLOR_WINDOW)); SetBkColor(HDC(wParam), GetSysColor(COLOR_WINDOW)); } if (ii.fgColor != colorDefault) SetTextColor(HDC(wParam), ii.fgColor); return LRESULT(GetStockObject(DC_BRUSH)); } } return 0; } else if (iMessage == WM_DESTROY) { canClear();// Ignore return value } return 0; } void gdioutput::TabFocus(int direction) { list::iterator tit; if (useTables) for (tit=Tables.begin(); tit!=Tables.end(); ++tit) if (tit->table->tabFocus(*this, direction)) return; if (FocusList.empty()) return; list::iterator it=FocusList.begin(); while(it!=FocusList.end() && *it != currentFocus.hWnd) ++it; //if (*it==CurrentFocus) if (it!=FocusList.end()) { if (direction==1){ ++it; if (it==FocusList.end()) it=FocusList.begin(); while(!IsWindowEnabled(*it) && *it != currentFocus.hWnd){ ++it; if (it==FocusList.end()) it=FocusList.begin(); } } else{ if (it==FocusList.begin()) it=FocusList.end(); it--; while(!IsWindowEnabled(*it) && *it != currentFocus.hWnd){ if (it==FocusList.begin()) it=FocusList.end(); it--; } } // if (currentFocus.wasTabbed) // Button_SetState(currentFocus.hWnd, false); HWND hWT = *it; //SetFocus(0); SetFocus(hWT); currentFocus = hWT; //currentFocus = *it; /*if (biByHwnd.find(currentFocus.hWnd) != biByHwnd.end()) { currentFocus.wasTabbed = true; Button_SetState(currentFocus.hWnd, true); }*/ } else{ SetFocus(currentFocus.hWnd); currentFocus=*FocusList.begin(); } } bool gdioutput::isInputChanged(const string &exclude) { for(list::iterator it=II.begin(); it != II.end(); ++it) { if (it->id!=exclude) { if (it->changed() && !it->ignoreCheck) return true; } } for (list::iterator it = LBI.begin(); it != LBI.end(); ++it) { getSelectedItem(*it); if (it->changed() && !it->ignoreCheck) return true; } for (list::iterator it = BI.begin(); it != BI.end(); ++it) { bool checked = SendMessage(it->hWnd, BM_GETCHECK, 0, 0)==BST_CHECKED; if (it->originalState != checked) return true; } return false; } InputInfo *gdioutput::replaceSelection(const char *id, const wstring &text) { for(list::iterator it=II.begin(); it != II.end(); ++it) if (it->id==id) { SendMessage(it->hWnd, EM_REPLACESEL, TRUE, LPARAM(text.c_str())); return &*it; } return 0; } BaseInfo *gdioutput::setInputFocus(const string &id, bool select) { for(list::iterator it=II.begin(); it != II.end(); ++it) if (it->id==id) { scrollTo(it->xp, it->yp); BaseInfo *bi = SetFocus(it->hWnd)!=NULL ? &*it: 0; if (bi) { if (select) PostMessage(it->hWnd, EM_SETSEL, it->text.length(), 0); } return bi; } for(list::iterator it=LBI.begin(); it!=LBI.end();++it) if (it->id==id) { scrollTo(it->xp, it->yp); return SetFocus(it->hWnd)!=NULL ? &*it: 0; } for(list::iterator it=BI.begin(); it!=BI.end();++it) if (it->id==id) { scrollTo(it->xp, it->yp); return SetFocus(it->hWnd)!=NULL ? &*it: 0; } return 0; } InputInfo *gdioutput::getInputFocus() { HWND hF=GetFocus(); if (hF) { list::iterator it; for(it=II.begin(); it != II.end(); ++it) if (it->hWnd==hF) return &*it; } return 0; } void gdioutput::enter() { if (hasCommandLock()) return; wstring msg; try { doEnter(); } catch (const meosCancel&) { return; } catch (meosException & ex) { msg = ex.wwhat(); } catch(std::exception &ex) { msg = widen(ex.what()); if (msg.empty()) msg = L"Ett okänt fel inträffade."; } catch(...) { msg = L"Ett okänt fel inträffade."; } if (!msg.empty()) alert(msg); } void gdioutput::doEnter() { if (autoCompleteInfo) { autoCompleteInfo->enter(); return; } list::iterator tit; if (useTables) for (tit=Tables.begin(); tit!=Tables.end(); ++tit) if (tit->table->enter(*this)) return; HWND hWnd=GetFocus(); for (list::iterator it=BI.begin(); it!=BI.end(); ++it) if (it->isDefaultButton()) { if (!it->handleEvent(*this, GUI_BUTTON) && it->callBack) it->callBack(this, GUI_BUTTON, &*it); return; } list::iterator it; for(it=II.begin(); it != II.end(); ++it) if (it->hWnd==hWnd && (it->hasEventHandler() || it->callBack)){ TCHAR bf[1024]; GetWindowText(hWnd, bf, 1024); it->text = bf; if (!it->handleEvent(*this, GUI_INPUT)) it->callBack(this, GUI_INPUT, &*it); return; } } bool gdioutput::upDown(int direction) { wstring msg; try { return doUpDown(direction); } catch (const meosCancel&) { return false; } catch (meosException & ex) { msg = ex.wwhat(); } catch(std::exception &ex) { msg = widen(ex.what()); if (msg.empty()) msg = L"Ett okänt fel inträffade."; } catch(...) { msg = L"Ett okänt fel inträffade."; } if (!msg.empty()) alert(msg); return false; } bool gdioutput::doUpDown(int direction) { if (autoCompleteInfo) { autoCompleteInfo->upDown(direction); return true; } list::iterator tit; if (useTables) for (tit=Tables.begin(); tit!=Tables.end(); ++tit) if (tit->table->upDown(*this, direction)) return true; return false; } void gdioutput::escape() { if (hasCommandLock()) return; wstring msg; try { doEscape(); } catch (const meosCancel&) { return; } catch (meosException & ex) { msg = ex.wwhat(); } catch(std::exception &ex) { msg = widen(ex.what()); if (msg.empty()) msg = L"Ett okänt fel inträffade."; } catch(...) { msg = L"Ett okänt fel inträffade."; } if (!msg.empty()) alert(msg); } void gdioutput::doEscape() { if (fullScreen) { PostMessage(hWndTarget, WM_CLOSE, 0,0); } if (autoCompleteInfo) { autoCompleteInfo.reset(); return; } list::iterator tit; if (useTables) for (tit=Tables.begin(); tit!=Tables.end(); ++tit) tit->table->escape(*this); for (list::iterator it=BI.begin(); it!=BI.end(); ++it) { if (it->isCancelButton() && (it->callBack || it->hasEventHandler()) ) { if (!it->handleEvent(*this, GUI_BUTTON)) it->callBack(this, GUI_BUTTON, &*it); return; } } } void gdioutput::clearPage(bool autoRefresh, bool keepToolbar) { maxTextBlockHeight = getLineHeight(); animationData.reset(); lockUpDown = false; hasAnyTimer = false; enableTables(); #ifndef MEOSDB if (toolbar && !keepToolbar) toolbar->hide(); #endif while (!timers.empty()) { KillTimer(hWndTarget, (UINT_PTR)&timers.back()); timers.back().setWnd = 0; timers.back().parent = 0; timers.pop_back(); } restorePoints.clear(); shownStrings.clear(); onClear = 0; FocusList.clear(); currentFocus = 0; TL.clear(); itTL = TL.end(); updateImageReferences(); listDescription.clear(); if (hWndTarget && autoRefresh) InvalidateRect(hWndTarget, NULL, true); fillDown(); hasCleared = true; for (ToolList::iterator it = toolTips.begin(); it != toolTips.end(); ++it) { if (hWndToolTip) { SendMessage(hWndToolTip, TTM_DELTOOL, 0, (LPARAM)&it->ti); } } toolTips.clear(); { list::iterator it; for (it = BI.begin(); it != BI.end(); ++it) { it->callBack = 0; it->setHandler(0); DestroyWindow(it->hWnd); } biByHwnd.clear(); BI.clear(); } { list::iterator it; for (it = II.begin(); it != II.end(); ++it) { it->callBack = 0; it->setHandler(0); DestroyWindow(it->hWnd); } iiByHwnd.clear(); II.clear(); } { list::iterator it; for (it = LBI.begin(); it != LBI.end(); ++it) { it->callBack = 0; it->setHandler(0); DestroyWindow(it->hWnd); if (it->writeLock) hasCleared = true; } lbiByHwnd.clear(); LBI.clear(); } while (!Tables.empty()) { auto t = Tables.front().table; Tables.pop_front(); t->hide(*this); } DataInfo.clear(); FocusList.clear(); Events.clear(); Rectangles.clear(); MaxX = scaleLength(60); MaxY = scaleLength(100); CurrentX = scaleLength(40); CurrentY = scaleLength(START_YP); SX = CurrentX; SY = CurrentY; OffsetX = 0; OffsetY = 0; renderOptimize = true; backgroundColor1 = -1; backgroundColor2 = -1; foregroundColor = -1; backgroundImage = -1; setRestorePoint(); if (autoRefresh) updateScrollbars(); try { if (postClear) postClear->makeEvent(*this, GUI_POSTCLEAR); } catch (const meosCancel&) { } catch (meosException & ex) { if (isTestMode) throw ex; wstring msg = ex.wwhat(); alert(msg); } catch (const std::exception &ex) { if (isTestMode) throw ex; string msg(ex.what()); alert(msg); } postClear = nullptr; manualUpdate = !autoRefresh; } void gdioutput::updateImageReferences() { if (imageReferences.size() > 0) { imageReferences.clear(); for (auto& ti : TL) { if ((ti.format & 0xFF) == textImage) { imageReferences.push_back(&ti); } } } } void gdioutput::getWindowText(HWND hWnd, wstring &text) { TCHAR bf[1024]; TCHAR *bptr=bf; int len=GetWindowTextLength(hWnd); if (len>1023) bptr=new TCHAR[len+1]; GetWindowText(hWnd, bptr, len+1); text=bptr; if (len>1023) delete[] bptr; } BaseInfo& gdioutput::getBaseInfo(const char* id, int requireExtraMatch) const { for (auto& ii : II) { if (ii.id == id && ii.matchExtra(requireExtraMatch)) { return const_cast(ii); } } for (auto& lbi : LBI) { if (lbi.id == id && lbi.matchExtra(requireExtraMatch)) { return const_cast(lbi); } } for (auto& bi : BI) { if (bi.id == id && bi.matchExtra(requireExtraMatch)) { return const_cast(bi); } } for (auto& tl : TL) { if (tl.id == id && tl.matchExtra(requireExtraMatch)) { return const_cast(tl); } } string err = string("Internal Error, identifier not found: X#") + id; throw std::exception(err.c_str()); } const wstring &gdioutput::getText(const char *id, bool acceptMissing, int requireExtraMatch) const { TCHAR bf[1024]; TCHAR *bptr=bf; for(list::const_iterator it=II.begin(); it != II.end(); ++it){ if (it->id==id && it->matchExtra(requireExtraMatch)){ int len=GetWindowTextLength(it->hWnd); if (len>1023) bptr=new TCHAR[len+1]; GetWindowText(it->hWnd, bptr, len+1); const_cast(it->text)=bptr; if (len>1023) delete[] bptr; return it->text; } } for(list::const_iterator it=LBI.begin(); it != LBI.end(); ++it){ if (it->id==id && it->IsCombo && it->matchExtra(requireExtraMatch)){ if (!it->writeLock) { GetWindowText(it->hWnd, bf, 1024); const_cast(it->text)=bf; } return it->text; } } for(list::const_iterator it=TL.begin(); it != TL.end(); ++it){ if (it->id==id && it->matchExtra(requireExtraMatch)) { return it->text; } } #ifdef _DEBUG if (!acceptMissing) { string err = string("Internal Error, identifier not found: X#") + id; throw std::exception(err.c_str()); } #endif return _EmptyWString; } bool gdioutput::hasWidget(const string &id) const { for(list::const_iterator it=II.begin(); it != II.end(); ++it){ if (it->id==id) return true; } for(list::const_iterator it=LBI.begin(); it != LBI.end(); ++it){ if (it->id==id) return true; } for(list::const_iterator it=BI.begin(); it != BI.end(); ++it){ if (it->id==id) return true; } for (auto &tl : TL) { if (tl.id == id) return true; } return false; } int gdioutput::getTextNo(const char *id, bool acceptMissing) const { const wstring &t = getText(id, acceptMissing); return _wtoi(t.c_str()); } BaseInfo *gdioutput::setTextTranslate(const char *id, const wstring &text, bool update) { return setText(id, lang.tl(text), update); } BaseInfo *gdioutput::setTextTranslate(const string &id, const wstring &text, bool update) { return setText(id, lang.tl(text), update); } BaseInfo *gdioutput::setTextTranslate(const char *id, const wchar_t *text, bool update) { return setText(id, lang.tl(text), update); } BaseInfo *gdioutput::setText(const char *id, int number, bool Update) { return setText(id, itow(number), Update); } BaseInfo *gdioutput::setTextZeroBlank(const char *id, int number, bool Update) { if (number!=0) return setText(id, number, Update); else return setText(id, L"", Update); } BaseInfo *gdioutput::setText(const char *id, const wstring &text, bool update, int requireExtraMatch, bool updateOriginal) { for (auto it = II.begin(); it != II.end(); ++it) { if (it->id == id && it->matchExtra(requireExtraMatch)) { bool oldWR = it->writeLock; it->writeLock = true; SetWindowText(it->hWnd, text.c_str()); it->writeLock = oldWR; it->text = text; it->synchData(); if (updateOriginal) it->original = text; it->focusText = text; return &*it; } } for (auto it = LBI.begin(); it != LBI.end(); ++it) { if (it->id == id && it->IsCombo && it->matchExtra(requireExtraMatch)) { SetWindowText(it->hWnd, text.c_str()); it->text = text; if (updateOriginal) it->original = text; return &*it; } } for (auto it = BI.begin(); it != BI.end(); ++it) { if (it->id == id && it->matchExtra(requireExtraMatch)) { SetWindowText(it->hWnd, text.c_str()); it->text = text; return &*it; } } for (auto it = TL.begin(); it != TL.end(); ++it) { if (it->id == id && it->matchExtra(requireExtraMatch)) { RECT rc = it->textRect; it->text = text; calcStringSize(*it); rc.right = max(it->textRect.right, rc.right); rc.bottom = max(it->textRect.bottom, rc.bottom); bool changed = updatePos(0, 0, it->textRect.right, it->textRect.bottom); if (update && hWndTarget) { if (changed) InvalidateRect(hWndTarget, 0, true); else InvalidateRect(hWndTarget, &rc, true); } return &*it; } } return nullptr; } bool gdioutput::insertText(const string &id, const wstring &text) { for (list::iterator it = II.begin(); it != II.end(); ++it) { if (it->id == id) { SetWindowText(it->hWnd, text.c_str()); it->text = text; if (it->hasEventHandler()) it->handleEvent(*this, GUI_INPUT); else if (it->callBack) it->callBack(this, GUI_INPUT, &*it); return true; } } return false; } void gdioutput::setData(const string &id, DWORD data) { void *pd = (void *)(size_t(data)); setData(id, pd); } void gdioutput::setData(const string &id, void *data) { list::iterator it; for(it=DataInfo.begin(); it != DataInfo.end(); ++it){ if (it->id==id){ it->data = data; return; } } DataStore ds; ds.id=id; ds.data=data; DataInfo.push_front(ds); return; } bool gdioutput::getData(const string &id, DWORD &data) const { list::const_iterator it; for(it=DataInfo.begin(); it != DataInfo.end(); ++it){ if (it->id==id){ data=DWORD(size_t(it->data)); return true; } } data=0; return false; } void gdioutput::setData(const string &id, const string &data) { for (auto &it : DataInfo) { if (it.id == id) { it.sdata = data; return; } } DataStore ds; ds.id = id; ds.sdata = data; DataInfo.push_front(ds); return; } bool gdioutput::getData(const string &id, string &out) const { for (auto &it : DataInfo) { if (it.id == id) { out = it.sdata; return true; } } out.clear(); return false; } void *gdioutput::getData(const string &id) const { list::const_iterator it; for (it = DataInfo.begin(); it != DataInfo.end(); ++it){ if (it->id == id){ return it->data; } } throw meosException("Data X not found#" + id); } bool gdioutput::hasData(const char *id) const { DWORD dummy; return getData(id, dummy); } bool gdioutput::updatePosTight(int x, int y, int width, int height, int marginx, int marginy) { int ox = MaxX; int oy = MaxY; MaxX = max(x + width, MaxX); MaxY = max(y + height, MaxY); bool changed = (ox != MaxX || oy != MaxY); if (changed && hWndTarget && !manualUpdate) { RECT rc; if (ox == MaxX) { rc.top = oy - CurrentY - 5; rc.bottom = MaxY - CurrentY + scaleLength(50); rc.right = 10000; rc.left = 0; InvalidateRect(hWndTarget, &rc, true); } else { InvalidateRect(hWndTarget, 0, true); } GetClientRect(hWndTarget, &rc); if (MaxX > rc.right || MaxY > rc.bottom) //Update scrollbars SendMessage(hWndTarget, WM_SIZE, 0, MAKELONG(rc.right, rc.bottom)); } if (flowDirection == FlowDirection::Down) { CurrentY = max(y + height + marginy, CurrentY); } else if (flowDirection == FlowDirection::Right) { CurrentX = max(x + width + marginx, CurrentX); } return changed; } bool gdioutput::updatePos(int x, int y, int width, int height) { return updatePosTight(x, y, width, height, 0, 0); } void gdioutput::adjustDimension(int width, int height) { int ox = MaxX; int oy = MaxY; MaxX = width; MaxY = height; if ((ox!=MaxX || oy!=MaxY) && hWndTarget && !manualUpdate) { RECT rc; if (ox == MaxX) { rc.top = oy - CurrentY - 5; rc.bottom = MaxY - CurrentY + scaleLength(50); rc.right = 10000; rc.left = 0; InvalidateRect(hWndTarget, &rc, true); } else { InvalidateRect(hWndTarget, 0, true); } GetClientRect(hWndTarget, &rc); if (MaxX>rc.right || MaxY>rc.bottom) //Update scrollbars SendMessage(hWndTarget, WM_SIZE, 0, MAKELONG(rc.right, rc.bottom)); } } // Alert from main thread (via callback) void gdioutput::delayAlert(const wstring& msg) { if (!delayedAlert.empty()) delayedAlert += L", "; if (delayedAlert.length() > 1000) delayedAlert = L""; delayedAlert += lang.tl(msg); PostMessage(hWndAppMain, WM_USER + 6, 0, LPARAM(this)); } wstring gdioutput::getDelayedAlert() { wstring out = L"#" + delayedAlert; delayedAlert.clear(); return out; } void gdioutput::alert(const string &msg) const { alert(widen(msg)); } void gdioutput::alert(const wstring &msg) const { if (isTestMode) { if (!cmdAnswers.empty()) { string ans = cmdAnswers.front(); cmdAnswers.pop_front(); if (ans == "ok") return; } throw meosException(msg + L"-- ok"); } HWND hFlt = getToolbarWindow(); if (hasToolbar()) { EnableWindow(hFlt, false); } refreshFast(); SetForegroundWindow(hWndAppMain); setCommandLock(); try { MessageBoxW(hWndAppMain, lang.tl(msg).c_str(), L"MeOS", MB_OK|MB_ICONINFORMATION); if (hasToolbar()) { EnableWindow(hFlt, true); } liftCommandLock(); } catch (...) { liftCommandLock(); throw; } } bool gdioutput::ask(const wstring &s) { if (isTestMode) { if (!cmdAnswers.empty()) { string ans = cmdAnswers.front(); cmdAnswers.pop_front(); if (ans == "yes") return true; else if (ans == "no") return false; } throw meosException(s + L"--yes/no"); } setCommandLock(); SetForegroundWindow(hWndAppMain); bool yes; try { yes = MessageBox(hWndAppMain, lang.tl(s).c_str(), L"MeOS", MB_YESNO|MB_ICONQUESTION)==IDYES; liftCommandLock(); } catch (...) { liftCommandLock(); throw; } return yes; } gdioutput::AskAnswer gdioutput::askCancel(const wstring &s) { if (isTestMode) { if (!cmdAnswers.empty()) { string ans = cmdAnswers.front(); cmdAnswers.pop_front(); if (ans == "cancel") return AskAnswer::AnswerCancel; else if (ans == "yes") return AskAnswer::AnswerYes; else if (ans == "no") return AskAnswer::AnswerNo; } throw meosException(s + L"--yes/no/cancel"); } setCommandLock(); SetForegroundWindow(hWndAppMain); int a = MessageBox(hWndAppMain, lang.tl(s).c_str(), L"MeOS", MB_YESNOCANCEL|MB_ICONQUESTION); liftCommandLock(); if (a == IDYES) return AskAnswer::AnswerYes; else if (a == IDNO) return AskAnswer::AnswerNo; else return AskAnswer::AnswerCancel; } gdioutput::AskAnswer gdioutput::askOkCancel(const wstring& s) { if (isTestMode) { if (!cmdAnswers.empty()) { string ans = cmdAnswers.front(); cmdAnswers.pop_front(); if (ans == "cancel") return AskAnswer::AnswerCancel; else if (ans == "ok") return AskAnswer::AnswerOK; } throw meosException(s + L"--ok/cancel"); } setCommandLock(); SetForegroundWindow(hWndAppMain); int a = MessageBox(hWndAppMain, lang.tl(s).c_str(), L"MeOS", MB_OKCANCEL | MB_ICONINFORMATION); liftCommandLock(); if (a == IDOK) return AskAnswer::AnswerOK; else return AskAnswer::AnswerCancel; } void gdioutput::setTabStops(const string& name, int t1, int t2) { getInputDimension(0); double relTextScale = scale / guiMeasure->avgCharWidth; DWORD ptr[2]; int n = 1; //LONG bu=GetDialogBaseUnits(); //int baseunitX=LOWORD(bu); //array[0]=int(t1 * 4.2 * scale) / baseunitX ; //array[1]=int(t2 * 4.2 * scale) / baseunitX ; ptr[0] = int(t1 * relTextScale * 6.4 * 4.2 / 8.0); ptr[1] = int(t2 * relTextScale * 6.4 * 4.2 / 8.0); int lastTabStop = 0; if (t2 > 0) { n = 2; lastTabStop = t2; } else { lastTabStop = t1; } list::iterator it; for (it = LBI.begin(); it != LBI.end(); ++it) { if (it->id == name) { if (!it->IsCombo) { SendMessage(it->hWnd, LB_SETTABSTOPS, n, LPARAM(ptr)); it->lastTabStop = lastTabStop; } return; } } } void gdioutput::setInputStatus(const char *id, bool status, bool acceptMissing, int matchExtra) { bool hit = false; for(list::iterator it=II.begin(); it != II.end(); ++it) if (it->id==id && (matchExtra == -1 || it->getExtraInt() == matchExtra)) { EnableWindow(it->hWnd, status); hit = true; } for(list::iterator it=LBI.begin(); it != LBI.end(); ++it) if (it->id==id && (matchExtra == -1 || it->getExtraInt() == matchExtra)) { EnableWindow(it->hWnd, status); hit = true; } for(list::iterator it=BI.begin(); it != BI.end(); ++it) if (it->id==id && (matchExtra == -1 || it->getExtraInt() == matchExtra)) { EnableWindow(it->hWnd, status); if (it->isCheckbox) { string tid = "T" + it->id; for(list::iterator tit=TL.begin(); tit != TL.end(); ++tit){ if (tit->id == tid) { enableCheckBoxLink(*tit, status); break; } } } hit = true; if (status==false) { it->storedFlags |= it->flags; it->flags = 0; //Remove default status etc. } else { // Restore flags it->flags |= it->storedFlags; } } if (acceptMissing) return; #ifdef _DEBUG if (!hit) { string err = string("Internal Error, identifier not found: X#") + id; throw std::exception(err.c_str()); } #endif } void gdioutput::refresh() const { #ifdef DEBUGRENDER OutputDebugString("### Full refresh\n"); #endif if (hWndTarget) { updateScrollbars(); InvalidateRect(hWndTarget, NULL, true); UpdateWindow(hWndTarget); } screenXYToString.clear(); stringToScreenXY.clear(); } void gdioutput::refreshFast() const { #ifdef DEBUGRENDER OutputDebugString("Fast refresh\n"); #endif if (hWndTarget) { InvalidateRect(hWndTarget, NULL, true); UpdateWindow(hWndTarget); } screenXYToString.clear(); stringToScreenXY.clear(); } void gdioutput::takeShownStringsSnapshot() { #ifdef DEBUGRENDER OutputDebugString("** Take snapshot\n"); #endif screenXYToString.clear(); stringToScreenXY.clear(); snapshotMaxXY.first = MaxX; snapshotMaxXY.second = MaxY - OffsetY; #ifdef DEBUGRENDER OutputDebugString(("ymax:" + itos(MaxY-OffsetY) + "\n").c_str()); #endif for (size_t k = 0; k < shownStrings.size(); k++) { if (shownStrings[k]->hasTimer) continue; //Ignore int x = shownStrings[k]->xp - OffsetX; int y = shownStrings[k]->yp - OffsetY; const wstring &str = shownStrings[k]->text; #ifdef DEBUGRENDER //OutputDebugString((itos(k) + ":" + itos(shownStrings[k]->xp) + "," + itos(shownStrings[k]->yp) + "," + str + "\n").c_str()); #endif screenXYToString.insert(make_pair(make_pair(x, y), ScreenStringInfo(shownStrings[k]->textRect, str))); if (stringToScreenXY.count(str) == 0) stringToScreenXY.insert(make_pair(str,make_pair(x, y))); } RECT rc; GetClientRect(hWndTarget, &rc); int BoundYup = OffsetY; int BoundYdown = OffsetY+rc.bottom; for (list::iterator it = Rectangles.begin(); it != Rectangles.end(); ++it) { if (it->rc.top <= BoundYdown && it->rc.bottom >= BoundYup) { wstring r = L"[R]"; RECT rect_rc = it->rc; OffsetRect(&rect_rc, -OffsetX, -OffsetY); screenXYToString.insert(make_pair(make_pair(rect_rc.left, rect_rc.top), ScreenStringInfo(rect_rc, r))); } } } void updateScrollInfo(HWND hWnd, gdioutput &gdi, int nHeight, int nWidth); void gdioutput::refreshSmartFromSnapshot(bool allowMoveOffset) { #ifdef DEBUGRENDER OutputDebugString("Smart refresh\n"); #endif RECT clientRC; GetClientRect(hWndTarget, &clientRC); updateStringPosCache(); vector changedStrings; bool updateScroll = false; if (allowMoveOffset) { map< pair, int> offsetCount; int misses = 0, hits = 0; for (size_t k = 0; k < shownStrings.size(); k++) { if (shownStrings[k]->hasTimer) continue; //Ignore int x = shownStrings[k]->xp - OffsetX; int y = shownStrings[k]->yp - OffsetY; const wstring &str = shownStrings[k]->text; map >::const_iterator found = stringToScreenXY.find(str); if (found != stringToScreenXY.end()) { hits++; int ox = found->second.first - x; int oy = found->second.second - y; ++offsetCount[make_pair(ox, oy)]; if (hits > 30) break; } else { misses++; if (misses > 20) break; } } // Choose dominating offset, if dominating enough pair offset(0,0); int maxVal = 10; // Require at least 10 hits for(map< pair, int>::iterator it = offsetCount.begin(); it != offsetCount.end(); ++it) { if (it->second > maxVal) { maxVal = it->second; offset = it->first; } } int maxOffsetY=max(getPageY()-clientRC.bottom, 0); int maxOffsetX=max(getPageX()-clientRC.right, 0); int noy = OffsetY - offset.second; int nox = OffsetX - offset.first; if ((offset.first != 0 && nox>0 && nox0 && noyhasTimer) continue; //Ignore int x = shownStrings[k]->xp - OffsetX; int y = shownStrings[k]->yp - OffsetY; const wstring &str = shownStrings[k]->text; #ifdef DEBUGRENDER //OutputDebugString((itos(k) + ":" + itos(shownStrings[k]->xp) + "," + itos(shownStrings[k]->yp) + "," + str + "\n").c_str()); #endif map, ScreenStringInfo>::iterator res = screenXYToString.find(make_pair(x,y)); if (res != screenXYToString.end()) { res->second.reached = true; if (str != res->second.str) { if (res->second.rc.bottom >= 0 && res->second.rc.top <= clientRC.bottom) { changedStrings.push_back(k); invalidRect.top = min(invalidRect.top, res->second.rc.top); invalidRect.bottom = max(invalidRect.bottom, res->second.rc.bottom); invalidRect.left = min(invalidRect.left, res->second.rc.left); invalidRect.right = max(invalidRect.right, res->second.rc.right); } } } else changedStrings.push_back(k); } RECT rc; GetClientRect(hWndTarget, &rc); int BoundYup = OffsetY; int BoundYdown = OffsetY+rc.bottom; for (list::iterator it = Rectangles.begin(); it != Rectangles.end(); ++it) { if (it->rc.top <= BoundYdown && it->rc.bottom >= BoundYup) { RECT rect_rc = it->rc; OffsetRect(&rect_rc, -OffsetX, -OffsetY); map, ScreenStringInfo>::iterator res = screenXYToString.find(make_pair(rect_rc.left, rect_rc.top)); bool add = false; if (res != screenXYToString.end()) { res->second.reached = true; if (!EqualRect(&rect_rc, &res->second.rc)) { add = true; invalidRect.top = min(invalidRect.top, res->second.rc.top); invalidRect.bottom = max(invalidRect.bottom, res->second.rc.bottom); invalidRect.left = min(invalidRect.left, res->second.rc.left); invalidRect.right = max(invalidRect.right, res->second.rc.right); } } else add = true; if (add) { invalid = true; invalidRect.top = min(invalidRect.top, rect_rc.top); invalidRect.bottom = max(invalidRect.bottom, rect_rc.bottom); invalidRect.left = min(invalidRect.left, rect_rc.left); invalidRect.right = max(invalidRect.right, rect_rc.right); } } } for (map, ScreenStringInfo>::iterator it = screenXYToString.begin(); it != screenXYToString.end(); ++it) { if (!it->second.reached) { invalid = true; invalidRect.top = min(invalidRect.top, it->second.rc.top); invalidRect.bottom = max(invalidRect.bottom, it->second.rc.bottom); invalidRect.left = min(invalidRect.left, it->second.rc.left); invalidRect.right = max(invalidRect.right, it->second.rc.right); } } screenXYToString.clear(); stringToScreenXY.clear(); if (snapshotMaxXY.second != MaxY - OffsetY) { // We added (or removed) a row. Add result to list is typical case. int currentMaxP = (MaxY - OffsetY); int oldMaxP = snapshotMaxXY.second; bool bottomVisible = ((currentMaxP < clientRC.bottom + 15) && currentMaxP > -15) || ((oldMaxP < clientRC.bottom + 15) && oldMaxP > -15); if (bottomVisible && !highContrast && oldMaxP != currentMaxP) { invalid = true; invalidRect.top = min(invalidRect.top, oldMaxP-15); invalidRect.top = min(invalidRect.top, currentMaxP-15); invalidRect.bottom = max(invalidRect.bottom, oldMaxP+15); invalidRect.bottom = max(invalidRect.bottom, currentMaxP+15); invalidRect.left = 0; invalidRect.right = clientRC.right; #ifdef DEBUGRENDER OutputDebugString("Extend Y\n"); #endif } updateScroll = true; } if (snapshotMaxXY.first != MaxX) { // This almost never happens invalidRect = clientRC; invalid = true; updateScroll; } if (updateScroll) { bool hc = highContrast; highContrast = false; updateScrollInfo(hWndTarget, *this, clientRC.bottom, clientRC.right); // No throw highContrast = hc; } if (changedStrings.empty() && !invalid) { #ifdef DEBUGRENDER //breakRender = true; OutputDebugString("*** NO CHANGE\n"); #endif return; } for (size_t k = 0; k< changedStrings.size(); k++) { TextInfo &ti = *shownStrings[changedStrings[k]]; invalidRect.top = min(invalidRect.top, ti.textRect.top); invalidRect.bottom = max(invalidRect.bottom, ti.textRect.bottom); invalidRect.left = min(invalidRect.left, ti.textRect.left); invalidRect.right = max(invalidRect.right, ti.textRect.right); } if (invalidRect.bottom<0 || invalidRect.right < 0 || invalidRect.top > clientRC.bottom || invalidRect.left > clientRC.right) { #ifdef DEBUGRENDER //breakRender = true; OutputDebugString("*** EMPTY CHANGE\n"); #endif return; } if (hWndTarget) { //InvalidateRect(hWndTarget, &invalidRect, true); //UpdateWindow(hWndTarget); HDC hDC = GetDC(hWndTarget); IntersectClipRect(hDC, invalidRect.left, invalidRect.top, invalidRect.right, invalidRect.bottom); //debugDrawColor = RGB((30*counterRender)%256,0,0); draw(hDC, clientRC, invalidRect); //debugDrawColor = 0; ReleaseDC(hWndTarget, hDC); } } void gdioutput::removeString(string id) { int cnt = 0; for (auto it = TL.begin(); it != TL.end(); ++it, ++cnt) { if (it->id == id) { InvalidateRect(hWndTarget, &it->textRect, true); TL.erase(it); itTL = TL.end(); shownStrings.clear(); updateImageReferences(); // Update restorepoints for (auto& rp : restorePoints) { if (rp.second.nTL > cnt) rp.second.nTL--; } return; } } } bool gdioutput::selectFirstItem(const string& id) { for (auto it = LBI.begin(); it != LBI.end(); ++it) if (it->id == id) { bool ret; if (it->IsCombo) ret = SendMessage(it->hWnd, CB_SETCURSEL, 0, 0) >= 0; else ret = SendMessage(it->hWnd, LB_SETCURSEL, 0, 0) >= 0; getSelectedItem(*it); it->original = it->text; it->originalIdx = it->data; } return false; } void gdioutput::setWindowTitle(const wstring &title) { if (title.length()>0) { wstring titlew = title + makeDash(L" - MeOS"); SetWindowText(hWndAppMain, titlew.c_str()); } else SetWindowText(hWndAppMain, L"MeOS"); } void gdioutput::setWaitCursor(bool wait) { if (wait) SetCursor(LoadCursor(NULL, IDC_WAIT)); else SetCursor(LoadCursor(NULL, IDC_ARROW)); } struct FadeInfo { TextInfo ti; DWORD Start; DWORD End; HWND hWnd; COLORREF StartC; COLORREF EndC; }; void TextFader(void *f) { FadeInfo *fi=(FadeInfo *)f; HDC hDC=GetDC(fi->hWnd); SelectObject(hDC, GetStockObject(DEFAULT_GUI_FONT)); SetBkMode(hDC, TRANSPARENT); double p=0; double r1=GetRValue(fi->StartC); double g1=GetGValue(fi->StartC); double b1=GetBValue(fi->StartC); double r2=GetRValue(fi->EndC); double g2=GetGValue(fi->EndC); double b2=GetBValue(fi->EndC); while(p<1) { p=double(GetTickCount()-fi->Start)/double(fi->End-fi->Start); if (p>1) p=1; p=1-(p-1)*(p-1); int red=int((1-p)*r1+(p)*r2); int green=int((1-p)*g1+(p)*g2); int blue=int((1-p)*b1+(p)*b2); //int green=int((p-1)*GetGValue(fi->StartC)+(p)*GetGValue(fi->EndC)); //int blue=int((p-1)*GetBValue(fi->StartC)+(p)*GetBValue(fi->EndC)); SetTextColor(hDC, RGB(red, green, blue)); TextOut(hDC, fi->ti.xp, fi->ti.yp, fi->ti.text.c_str(), fi->ti.text.length()); Sleep(30); //char bf[10]; //fi->ti.text=fi->ti.text+itoa(red, bf, 16); } ReleaseDC(fi->hWnd, hDC); delete fi; } void gdioutput::fadeOut(string Id, int ms) { list::iterator it; for(it=TL.begin(); it != TL.end(); ++it){ if (it->id==Id){ FadeInfo *fi=new FadeInfo; fi->Start=GetTickCount(); fi->End=fi->Start+ms; fi->ti=*it; fi->StartC=RGB(0, 0, 0); fi->EndC=GetSysColor(COLOR_WINDOW); fi->hWnd=hWndTarget; _beginthread(TextFader, 0, fi); TL.erase(it); return; } } } void gdioutput::RenderString(TextInfo &ti, HDC hDC) { if (skipTextRender(ti.format)) return; if (ti.hasTimer && ti.xp == 0) return; HDC hThis=0; if (!hDC){ assert(hWndTarget!=0); hDC=hThis=GetDC(hWndTarget); } RECT rc; if ((ti.format & absolutePosition) == 0) { rc.left = ti.xp - OffsetX; rc.top = ti.yp - OffsetY; } else { rc.left = ti.xp; rc.top = ti.yp; } rc.right = rc.left; rc.bottom = rc.top; formatString(ti, hDC); int format=ti.format&0xFF; if (format == textImage) { // Image int id = _wtoi(ti.text.c_str()); bool fixedRect = false; int h = 16, w = 16; if (id > 0) { image.loadImage(id, Image::ImageMethod::Default); w = image.getWidth(id); h = image.getHeight(id); image.drawImage(id, Image::ImageMethod::Default, hDC, rc.left, rc.top, w, h); } else if (ti.text.size()>1) { if (ti.text[0] == 'S') { // Icon w = _wtoi(ti.text.c_str() + 1); h = getLineHeight(); } else if (ti.text[0] == 'L') { fixedRect = true; uint64_t imgId = _wcstoui64(ti.text.c_str() + 1, nullptr, 10); w = ti.textRect.right - ti.textRect.left; h = ti.textRect.bottom - ti.textRect.top; image.drawImage(imgId, Image::ImageMethod::Default, hDC, rc.left, rc.top, w, h); } } if (!fixedRect) { ti.textRect.left = rc.left; ti.textRect.right = rc.left + w + 5; ti.textRect.top = rc.top; ti.textRect.bottom = rc.bottom + h + 5; } } else if (format != 10 && (breakLines&ti.format) == 0) { if (ti.xlimit == 0) { if (ti.format&textRight) { DrawText(hDC, ti.text.c_str(), ti.text.length(), &rc, DT_CALCRECT | DT_NOPREFIX); int dx = rc.right - rc.left; ti.realWidth = dx; rc.right -= dx; rc.left -= dx; ti.textRect = rc; DrawText(hDC, ti.text.c_str(), ti.text.length(), &rc, DT_RIGHT | DT_NOCLIP | DT_NOPREFIX); } else if (ti.format&textCenter) { DrawText(hDC, ti.text.c_str(), ti.text.length(), &rc, DT_CENTER | DT_CALCRECT | DT_NOPREFIX); int dx = rc.right - rc.left; ti.realWidth = dx; rc.right -= dx / 2; rc.left -= dx / 2; ti.textRect = rc; DrawText(hDC, ti.text.c_str(), ti.text.length(), &rc, DT_CENTER | DT_NOCLIP | DT_NOPREFIX); } else { DrawText(hDC, ti.text.c_str(), ti.text.length(), &rc, DT_LEFT | DT_CALCRECT | DT_NOPREFIX); ti.textRect = rc; ti.realWidth = rc.right - rc.left; DrawText(hDC, ti.text.c_str(), ti.text.length(), &rc, DT_LEFT | DT_NOCLIP | DT_NOPREFIX); } } else { int flags = DT_NOPREFIX; if (ti.format & textLimitEllipsis) flags = DT_END_ELLIPSIS; DrawText(hDC, ti.text.c_str(), ti.text.length(), &rc, DT_CALCRECT | flags); ti.realWidth = rc.right - rc.left; if (ti.format&textRight) { rc.right = rc.left + ti.xlimit - (rc.bottom - rc.top) / 2; DrawText(hDC, ti.text.c_str(), ti.text.length(), &rc, DT_RIGHT | flags); } else if (ti.format&textCenter) { rc.right = rc.left + ti.xlimit - (rc.bottom - rc.top) / 2; DrawText(hDC, ti.text.c_str(), ti.text.length(), &rc, DT_CENTER | flags); } else { rc.right = rc.left + ti.xlimit; DrawText(hDC, ti.text.c_str(), -1, &rc, DT_LEFT | flags); } ti.textRect = rc; } } else { memset(&rc, 0, sizeof(rc)); int width = scaleLength( (breakLines&ti.format) ? ti.xlimit : 450 ); rc.right = width; int dx = format != 10 ? 0 : scaleLength(20); ti.realWidth = width + dx; DrawText(hDC, ti.text.c_str(), ti.text.length(), &rc, DT_CALCRECT|DT_LEFT|DT_NOPREFIX|DT_WORDBREAK); ti.textRect=rc; ti.textRect.right+=ti.xp+dx; ti.textRect.left+=ti.xp; ti.textRect.top+=ti.yp; ti.textRect.bottom+=ti.yp+dx; if (format == 10) { DWORD c = colorLightYellow;// GetSysColor(COLOR_INFOBK); double red=GetRValue(c); double green=GetGValue(c); double blue=GetBValue(c); double blue1=min(255., blue*1.05); double green1=min(255., green*1.05); double red1=min(255., red*1.05); TRIVERTEX vert[2]; vert [0] .x = ti.xp-OffsetX; vert [0] .y = ti.yp-OffsetY; vert [0] .Red = 0xff00&DWORD(red1*256); vert [0] .Green = 0xff00&DWORD(green1*256); vert [0] .Blue = 0xff00&DWORD(blue1*256); vert [0] .Alpha = 0x0000; vert [1] .x = ti.xp+rc.right+dx-OffsetX; vert [1] .y = ti.yp+rc.bottom+dx-OffsetY; vert [1] .Red = 0xff00&DWORD(red*256); vert [1] .Green = 0xff00&DWORD(green*256); vert [1] .Blue = 0xff00&DWORD(blue*256); vert [1] .Alpha = 0x0000; GRADIENT_RECT gr[1]; gr[0].UpperLeft=0; gr[0].LowerRight=1; GradientFill(hDC,vert, 2, gr, 1,GRADIENT_FILL_RECT_H); SelectObject(hDC, GetStockObject(NULL_BRUSH)); SelectObject(hDC, GetStockObject(DC_PEN)); SetDCPenColor(hDC, RGB(DWORD(red*0.5), DWORD(green*0.5), DWORD(blue*0.5))); Rectangle(hDC, vert[0].x, vert[0].y, vert[1].x, vert[1].y); SetDCPenColor(hDC, RGB(DWORD(min(255., red*1.1)), DWORD(min(255., green*1.2)), DWORD(min(255., blue)))); POINT pt; MoveToEx(hDC, vert[0].x-1, vert[1].y, &pt); LineTo(hDC, vert[0].x-1, vert[0].y-1); LineTo(hDC, vert[1].x, vert[0].y-1); SetDCPenColor(hDC, RGB(DWORD(min(255., red*0.4)), DWORD(min(255., green*0.4)), DWORD(min(255., blue*0.4)))); MoveToEx(hDC, vert[1].x+0, vert[0].y, &pt); LineTo(hDC, vert[1].x+0, vert[1].y+0); LineTo(hDC, vert[0].x, vert[1].y+0); } dx/=2; rc.top=ti.yp+dx-OffsetY; rc.left=ti.xp+dx-OffsetX; rc.bottom+=ti.yp+dx-OffsetY; rc.right=ti.xp+dx+width-OffsetX; SetTextColor(hDC, 0); DrawText(hDC, ti.text.c_str(), ti.text.length(), &rc, DT_LEFT|DT_NOPREFIX|DT_WORDBREAK); } if (hThis) ReleaseDC(hWndTarget, hDC); } void gdioutput::RenderString(TextInfo &ti, const wstring &text, HDC hDC) { if (skipTextRender(ti.format)) return; RECT rc; if ((ti.format & absolutePosition) == 0) { rc.left = ti.xp - OffsetX; rc.top = ti.yp - OffsetY; } else { rc.left = ti.xp; rc.top = ti.yp; } rc.right = rc.left; rc.bottom = rc.top; int format=ti.format&0xFF; assert(format!=10); formatString(ti, hDC); if (ti.xlimit==0){ if (ti.format&textRight) { DrawText(hDC, text.c_str(), text.length(), &rc, DT_CALCRECT|DT_NOPREFIX); int dx=rc.right-rc.left; rc.right-=dx; rc.left-=dx; ti.textRect=rc; DrawText(hDC, text.c_str(), text.length(), &rc, DT_RIGHT|DT_NOCLIP|DT_NOPREFIX); } else if (ti.format&textCenter) { DrawText(hDC, text.c_str(), text.length(), &rc, DT_CENTER|DT_CALCRECT|DT_NOPREFIX); int dx=rc.right-rc.left; rc.right-=dx/2; rc.left-=dx/2; ti.textRect=rc; DrawText(hDC, text.c_str(), text.length(), &rc, DT_CENTER|DT_NOCLIP|DT_NOPREFIX); } else{ DrawText(hDC, text.c_str(), text.length(), &rc, DT_LEFT|DT_CALCRECT|DT_NOPREFIX); ti.textRect=rc; DrawText(hDC, text.c_str(), text.length(), &rc, DT_LEFT|DT_NOCLIP|DT_NOPREFIX); } } else{ if (ti.format&textRight) { DrawText(hDC, text.c_str(), text.length(), &rc, DT_LEFT|DT_CALCRECT|DT_NOPREFIX); rc.right = rc.left + ti.xlimit; ti.textRect = rc; DrawText(hDC, text.c_str(), text.length(), &rc, DT_RIGHT|DT_NOPREFIX); } else { DrawText(hDC, text.c_str(), text.length(), &rc, DT_LEFT|DT_CALCRECT|DT_NOPREFIX); rc.right=rc.left+ti.xlimit; DrawText(hDC, text.c_str(), text.length(), &rc, DT_LEFT|DT_NOPREFIX); ti.textRect=rc; } } } void gdioutput::resetLast() const { lastFormet = -1; lastActive = false; lastHighlight = false; lastColor = -1; lastFont.clear(); } void gdioutput::getFontInfo(const TextInfo &ti, FontInfo &fi) const { if (ti.font.empty()) { fi.name = 0; fi.bold = fi.normal = fi.italic = 0; } else { fi.name = &ti.font; getFont(ti.font).getInfo(fi); } } void gdioutput::formatString(const TextInfo &ti, HDC hDC) const { int format=ti.format&0xFF; if (lastFormet == format && lastActive == ti.active && lastHighlight == ti.highlight && lastColor == ti.color && ti.font == lastFont) return; if (ti.font.empty()) { getCurrentFont().selectFont(hDC, format); lastFont.clear(); } else { getFont(ti.font).selectFont(hDC, format); lastFont = ti.font; } SetBkMode(hDC, TRANSPARENT); if (ti.active) SetTextColor(hDC, RGB(255,0,0)); else if (ti.highlight) SetTextColor(hDC, RGB(64,64,128)); else if (ti.color == 0 && foregroundColor != -1) SetTextColor(hDC, foregroundColor); else SetTextColor(hDC, ti.color); } void gdioutput::calcStringSize(TextInfo &ti, HDC hDC_in) const { RECT rc; rc.left=ti.xp-OffsetX; rc.top=ti.yp-OffsetY; rc.right = rc.left; rc.bottom = rc.top; if ((ti.format & 0xFF) == textImage) { // Image int id = _wtoi(ti.text.c_str()); int w = 16, h = 16; if (id > 0) { w = image.getWidth(id); h = image.getHeight(id); } else if (ti.text.size()>1 && ti.text[0] == 'S') { // Icon w = _wtoi(ti.text.c_str() + 1); h = getLineHeight(); } else if (ti.text[0] == 'L') { return; } ti.textRect.left = rc.left; ti.textRect.right = rc.left + w + 5; ti.textRect.top = rc.top; ti.textRect.bottom = rc.bottom + h + 5; return; } HDC hDC = hDC_in; if (!hDC) { // assert(hWndTarget!=0); hDC = GetDC(hWndTarget); } resetLast(); formatString(ti, hDC); int format=ti.format&0xFF; if (format != 10 && (breakLines&ti.format) == 0) { if (ti.xlimit==0){ if (ti.format&textRight) { DrawText(hDC, ti.text.c_str(), ti.text.length(), &rc, DT_CALCRECT|DT_NOPREFIX); int dx=rc.right-rc.left; ti.realWidth = dx; rc.right-=dx; rc.left-=dx; ti.textRect=rc; } else if (ti.format&textCenter) { DrawText(hDC, ti.text.c_str(), ti.text.length(), &rc, DT_CENTER|DT_CALCRECT|DT_NOPREFIX); int dx=rc.right-rc.left; ti.realWidth = dx; rc.right-=dx/2; rc.left-=dx/2; ti.textRect=rc; } else{ DrawText(hDC, ti.text.c_str(), ti.text.length(), &rc, DT_LEFT|DT_CALCRECT|DT_NOPREFIX); ti.realWidth = rc.right - rc.left; ti.textRect=rc; } } else { DrawText(hDC, ti.text.c_str(), ti.text.length(), &rc, DT_LEFT|DT_CALCRECT|DT_NOPREFIX); ti.realWidth = rc.right - rc.left; rc.right=rc.left+ti.xlimit; ti.textRect=rc; } } else { memset(&rc, 0, sizeof(rc)); rc.right = scaleLength( (breakLines&ti.format) ? ti.xlimit : 450 ); int dx = format != 10 ? 0 : scaleLength(20); ti.realWidth = rc.right + dx; DrawText(hDC, ti.text.c_str(), ti.text.length(), &rc, DT_CALCRECT|DT_LEFT|DT_NOPREFIX|DT_WORDBREAK); ti.textRect=rc; ti.textRect.right+=ti.xp+dx; ti.textRect.left+=ti.xp; ti.textRect.top+=ti.yp; ti.textRect.bottom+=ti.yp+dx; } if (!hDC_in) ReleaseDC(hWndTarget, hDC); } void gdioutput::updateScrollbars() const { RECT rc; GetClientRect(hWndTarget, &rc); SendMessage(hWndTarget, WM_SIZE, 0, MAKELONG(rc.right, rc.bottom)); } void gdioutput::updateObjectPositions() { { list::iterator it; for(it=BI.begin(); it != BI.end(); ++it) { //MoveWindow(it->hWnd, it-> if (!it->AbsPos) SetWindowPos(it->hWnd, 0, it->xp-OffsetX, it->yp-OffsetY, 0,0, SWP_NOSIZE|SWP_NOZORDER|SWP_NOCOPYBITS); } } { list::iterator it; for(it=II.begin(); it != II.end(); ++it) SetWindowPos(it->hWnd, 0, it->xp-OffsetX, it->yp-OffsetY, 0,0, SWP_NOSIZE|SWP_NOZORDER); } { list::iterator it; for(it=LBI.begin(); it != LBI.end(); ++it) SetWindowPos(it->hWnd, 0, it->xp-OffsetX, it->yp-OffsetY, 0,0, SWP_NOSIZE|SWP_NOZORDER); } } void gdioutput::addInfoBox(string id, wstring text, int TimeOut, GUICALLBACK cb) { InfoBox Box; Box.id=id; Box.callBack=cb; Box.text=lang.tl(text); if (TimeOut>0) Box.TimeOut=GetTickCount()+TimeOut; IBox.push_back(Box); refresh(); } void gdioutput::drawBox(HDC hDC, InfoBox &Box, RECT &pos) { SelectObject(hDC, GetStockObject(DEFAULT_GUI_FONT)); SetBkMode(hDC, TRANSPARENT); //Calculate size. RECT testrect; memset(&testrect, 0, sizeof(RECT)); DrawText(hDC, Box.text.c_str(), Box.text.length(), &testrect, DT_CALCRECT|DT_LEFT|DT_NOPREFIX|DT_SINGLELINE); if (testrect.right>250 || Box.text.find_first_of('\n')!=string::npos) { testrect.right=250; DrawText(hDC, Box.text.c_str(), Box.text.length(), &testrect, DT_CALCRECT|DT_LEFT|DT_NOPREFIX|DT_WORDBREAK); } else if (testrect.right<80) testrect.right=80; pos.left=pos.right-(testrect.right+22); pos.top=pos.bottom-(testrect.bottom+20); DWORD c=GetSysColor(COLOR_INFOBK); double red=GetRValue(c); double green=GetGValue(c); double blue=GetBValue(c); double blue1=min(255., blue*1.1); double green1=min(255., green*1.1); double red1=min(255., red*1.1); TRIVERTEX vert[2]; vert [0] .x = pos.left; vert [0] .y = pos.top; vert [0] .Red = 0xff00&DWORD(red*256); vert [0] .Green = 0xff00&DWORD(green*256); vert [0] .Blue = 0xff00&DWORD(blue*256); vert [0] .Alpha = 0x0000; vert [1] .x = pos.right; vert [1] .y = pos.bottom; vert [1] .Red = 0xff00&DWORD(red1*256); vert [1] .Green = 0xff00&DWORD(green1*256); vert [1] .Blue = 0xff00&DWORD(blue1*256); vert [1] .Alpha = 0x0000; GRADIENT_RECT gr[1]; gr[0].UpperLeft=0; gr[0].LowerRight=1; //if (MaxY>500) GradientFill(hDC,vert, 2, gr, 1,GRADIENT_FILL_RECT_V); SelectObject(hDC, GetStockObject(NULL_BRUSH)); SelectObject(hDC, GetStockObject(BLACK_PEN)); Rectangle(hDC, pos.left, pos.top, pos.right, pos.bottom); Box.BoundingBox=pos; //Close Box RECT Close; Close.top=pos.top+3; Close.bottom=Close.top+11; Close.right=pos.right-3; Close.left=Close.right-11; Box.Close=Close; drawCloseBox(hDC, Close, false); RECT tr=pos; tr.left+=10; tr.right-=10; tr.top+=15; tr.bottom-=5; drawBoxText(hDC, tr, Box, false); Box.TextRect=tr; } void gdioutput::drawBoxes(HDC hDC, RECT &rc) { RECT pos; pos.right=rc.right; pos.bottom=rc.bottom; list::iterator it=IBox.begin(); int maxNumBox = 10; while(it!=IBox.end() && --maxNumBox > 0) { drawBox(hDC, *it, pos); pos.bottom=pos.top; ++it; } } void gdioutput::drawCloseBox(HDC hDC, RECT &Close, bool pressed) { if (!pressed) { SelectObject(hDC, GetStockObject(WHITE_BRUSH)); SelectObject(hDC, GetStockObject(BLACK_PEN)); } else { SelectObject(hDC, GetStockObject(LTGRAY_BRUSH)); SelectObject(hDC, GetStockObject(BLACK_PEN)); } //Close Box Rectangle(hDC, Close.left, Close.top, Close.right, Close.bottom); MoveToEx(hDC, Close.left+2, Close.top+2, 0); LineTo(hDC, Close.right-2, Close.bottom-2); MoveToEx(hDC, Close.right-2, Close.top+2, 0); LineTo(hDC, Close.left+2, Close.bottom-2); } void gdioutput::drawBoxText(HDC hDC, RECT &tr, InfoBox &Box, bool highligh) { SelectObject(hDC, GetStockObject(DEFAULT_GUI_FONT)); SetBkMode(hDC, TRANSPARENT); if (highligh) { SetTextColor(hDC, 0x005050FF); } else { SetTextColor(hDC, GetSysColor(COLOR_INFOTEXT)); } DrawText(hDC, Box.text.c_str(), Box.text.length(), &tr, DT_LEFT|DT_NOPREFIX|DT_WORDBREAK); } bool gdioutput::RemoveFirstInfoBox(const string &id) { list::iterator it=IBox.begin(); while(it!=IBox.end()) { if (it->id==id) { IBox.erase(it); return true; } ++it; } return false; } wstring gdioutput::getTimerText(int zeroTime, int format, bool timeInSeconds) { TextInfo temp; temp.zeroTime=0; //memset(&temp, 0, sizeof(TextInfo)); temp.format=format; if (timeInSeconds) return getTimerText(&temp, 1000*zeroTime); else return getTimerText(&temp, (1000/timeUnitsPerSecond) * zeroTime); } wstring gdioutput::getTimerText(TextInfo *tit, DWORD T) { int rt = (int(T) - int(tit->zeroTime)) / 1000; int tenth = (abs(int(T) - int(tit->zeroTime)) / 100) % 10; wstring text; int t=abs(rt); wchar_t bf[16]; if ((tit->format & time24HourClock) != 0 && t > 0) t = t % (24 * timeConstSecPerHour); if (tit->format & timeSeconds) { if (tit->format & timeWithTenth) swprintf_s(bf, 16, L"%d.%d", t, tenth); else swprintf_s(bf, 16, L"%d", t); } else if ((tit->format & timeWithTenth) && rt < timeConstSecPerHour) { swprintf_s(bf, 16, L"%02d:%02d.%d", t/ timeConstSecPerMin, t%timeConstSecPerMin, tenth); } else if (rt>=timeConstSecPerHour || (tit->format&fullTimeHMS)) swprintf_s(bf, 16, L"%02d:%02d:%02d", t/ timeConstSecPerHour, (t/ timeConstSecPerMin)% timeConstSecPerMin, t%timeConstSecPerMin); else swprintf_s(bf, 16, L"%d:%02d", (t/ timeConstMinPerHour), t%timeConstMinPerHour); if (rt>0 || ((tit->format&fullTimeHMS) && rt>=0) ) if (tit->format&timerCanBeNegative) text = wstring(L"+") + bf; else text = bf; else if (rt<0) if (tit->format&timerCanBeNegative) text = wstring(L"-")+bf; else if (tit->format&timerIgnoreSign) text = bf; else text = L"-"; return text; } void gdioutput::CheckInterfaceTimeouts(DWORD T) { list::iterator it=IBox.begin(); while (it!=IBox.end()) { if (it->TimeOut && it->TimeOutHasCapture || it->HasTCapture) ReleaseCapture(); InvalidateRect(hWndTarget, &(it->BoundingBox), true); IBox.erase(it); it=IBox.begin(); } else ++it; } list::iterator tit = TL.begin(); vector timeout; if (hasAnyTimer) { bool anyChange = false; while(tit!=TL.end()){ if (tit->hasTimer){ wstring text = tit->xp > 0 ? getTimerText(&*tit, T) : L""; if (tit->timeOut && T>DWORD(tit->timeOut)){ tit->timeOut=0; if (tit->callBack || tit->hasEventHandler()) timeout.push_back(*tit); } if (text != tit->text) { RECT rc=tit->textRect; tit->text=text; calcStringSize(*tit); rc.right=max(tit->textRect.right, rc.right); rc.bottom=max(tit->textRect.bottom, rc.bottom); anyChange = true; //InvalidateRecthWndTarget, &rc, true); } } ++tit; } if (anyChange) { int w, h; getTargetDimension(w, h); HDC hDC = GetDC(hWndTarget); HBITMAP btm = CreateCompatibleBitmap(hDC, w, h); HDC memDC = CreateCompatibleDC (hDC); HGDIOBJ hOld = SelectObject(memDC, btm); RECT rc; rc.top = 0; rc.left = 0; rc.bottom = h; rc.right = w; RECT area = rc; drawBackground(memDC, rc); draw(memDC, rc, area); BitBlt(hDC, 0, 0, w, h, memDC, 0,0, SRCCOPY); SelectObject(memDC, hOld); DeleteObject(btm); DeleteDC(memDC); ReleaseDC(hWndTarget, hDC); } } for (size_t k = 0; k < timeout.size(); k++) { if (!timeout[k].handleEvent(*this, GUI_TIMEOUT)) timeout[k].callBack(this, GUI_TIMEOUT, &timeout[k]); } } bool gdioutput::removeWidget(const string &id) { { auto it = BI.begin(); int cnt = 0; while (it != BI.end()) { if (it->id == id) { DestroyWindow(it->hWnd); biByHwnd.erase(it->hWnd); if (it->isCheckbox) removeString("T" + id); BI.erase(it); // Update restorepoints for (auto& rp : restorePoints) { if (rp.second.nBI > cnt) rp.second.nBI--; } return true; } ++it; ++cnt; } } { auto lit = LBI.begin(); int cnt = 0; while (lit != LBI.end()) { if (lit->id == id) { DestroyWindow(lit->hWnd); lbiByHwnd.erase(lit->hWnd); removeString(id + "_label"); if (lit->writeLock) hasCleared = true; LBI.erase(lit); // Update restorepoints for (auto& rp : restorePoints) { if (rp.second.nLBI > cnt) rp.second.nLBI--; } return true; } ++lit; cnt++; } } { auto iit = II.begin(); int cnt = 0; while (iit != II.end()) { if (iit->id == id) { DestroyWindow(iit->hWnd); iiByHwnd.erase(iit->hWnd); II.erase(iit); removeString(id + "_label"); // Update restorepoints for (auto& rp : restorePoints) { if (rp.second.nII > cnt) rp.second.nII--; } return true; } ++iit; cnt++; } } removeString(id); return false; } bool gdioutput::hideWidget(const string &id, bool hide) { list::iterator it=BI.begin(); while (it!=BI.end()) { if (it->id == id) { ShowWindow(it->hWnd, hide ? SW_HIDE : SW_SHOW); if (it->isCheckbox) { hideWidget("T" + id, hide); } return true; } ++it; } list::iterator lit=LBI.begin(); while (lit!=LBI.end()) { if (lit->id==id) { ShowWindow(lit->hWnd, hide ? SW_HIDE : SW_SHOW); return true; } ++lit; } list::iterator iit=II.begin(); while (iit!=II.end()) { if (iit->id==id) { ShowWindow(iit->hWnd, hide ? SW_HIDE : SW_SHOW); return true; } ++iit; } for (auto &ti : TL) { if (ti.id == id) { if (hide) ti.format |= hiddenText; else ti.format &= ~hiddenText; return true; } } return false; } void gdioutput::setRestorePoint() { setRestorePoint(""); } void gdioutput::setRestorePoint(const string &id) { RestoreInfo ri; ri.id = id; ri.nLBI = LBI.size(); ri.nBI = BI.size(); ri.nII = II.size(); ri.nTL = TL.size(); ri.nRect = Rectangles.size(); ri.nTooltip = toolTips.size(); ri.nTables = Tables.size(); ri.nHWND = FocusList.size(); ri.nData = DataInfo.size(); for (auto& rp : restorePoints) ri.restorePoints.insert(rp.first); ri.sCX=CurrentX; ri.sCY=CurrentY; ri.sMX=MaxX; ri.sMY=MaxY; ri.sOX=OffsetX; ri.sOY=OffsetY; ri.onClear = onClear; ri.postClear = postClear; restorePoints[id]=ri; } bool gdioutput::getWidgetRestorePoint(const string& id, string& restorePoint) const { int count; count = 0; for (auto& lbi : LBI) { if (lbi.id == id) { const RestoreInfo* bestRestoreInfo = nullptr; for (auto& rp : restorePoints) { if (count <= rp.second.nLBI && (bestRestoreInfo == nullptr || rp.second < *bestRestoreInfo)) { bestRestoreInfo = &rp.second; restorePoint = rp.first; } } return bestRestoreInfo != nullptr; } count++; } count = 0; for (auto& ii : II) { if (ii.id == id) { const RestoreInfo* bestRestoreInfo = nullptr; for (auto& rp : restorePoints) { if (count <= rp.second.nII && (bestRestoreInfo == nullptr || rp.second < *bestRestoreInfo)) { bestRestoreInfo = &rp.second; restorePoint = rp.first; } } return bestRestoreInfo != nullptr; } count++; } count = 0; for (auto& bi : BI) { if (bi.id == id) { const RestoreInfo* bestRestoreInfo = nullptr; for (auto& rp : restorePoints) { if (count <= rp.second.nBI && (bestRestoreInfo == nullptr || rp.second < *bestRestoreInfo)) { bestRestoreInfo = &rp.second; restorePoint = rp.first; } } return bestRestoreInfo != nullptr; } count++; } count = 0; for (auto& ti : TL) { if (ti.id == id) { const RestoreInfo* bestRestoreInfo = nullptr; for (auto& rp : restorePoints) { if (count <= rp.second.nTL && (bestRestoreInfo == nullptr || rp.second < *bestRestoreInfo)) { bestRestoreInfo = &rp.second; restorePoint = rp.first; } } return bestRestoreInfo != nullptr; } count++; } return false; } void gdioutput::setWidgetRestorePoint(const string& id, const string& restorePoint) { for (auto it = LBI.begin(); it != LBI.end(); ++it) { if (it->id == id) { auto& rpTarget = restorePoints[restorePoint]; auto itNew = LBI.begin(); int numNew = rpTarget.nLBI; advance(itNew, numNew); LBI.splice(itNew, LBI, it); for (auto& rp : restorePoints) { if (rp.second.nLBI >= numNew) { rp.second.nLBI++; } } return; } } for (auto it = II.begin(); it != II.end(); ++it) { if (it->id == id) { auto& rpTarget = restorePoints[restorePoint]; auto itNew = II.begin(); int numNew = rpTarget.nII; advance(itNew, numNew); II.splice(itNew, II, it); for (auto& rp : restorePoints) { if (rp.second.nII >= numNew) { rp.second.nII++; } } return; } } for (auto it = BI.begin(); it != BI.end(); ++it) { if (it->id == id) { auto& rpTarget = restorePoints[restorePoint]; auto itNew = BI.begin(); int numNew = rpTarget.nBI; advance(itNew, numNew); BI.splice(itNew, BI, it); for (auto& rp : restorePoints) { if (rp.second.nBI >= numNew) { rp.second.nBI++; } } return; } } for (auto it = TL.begin(); it != TL.end(); ++it) { if (it->id == id) { auto& rpTarget = restorePoints[restorePoint]; auto itNew = TL.begin(); int numNew = rpTarget.nTL; advance(itNew, numNew); TL.splice(itNew, TL, it); for (auto& rp : restorePoints) { if (rp.second.nTL >= numNew) { rp.second.nTL++; } } return; } } } void gdioutput::restoreInternal(const RestoreInfo &ri) { int toolRemove=toolTips.size()-ri.nTooltip; while (toolRemove>0 && toolTips.size()>0) { ToolInfo &info=toolTips.back(); if (hWndToolTip) { SendMessage(hWndToolTip, TTM_DELTOOL, 0, (LPARAM) &info.ti); } toolTips.pop_back(); toolRemove--; } int lbiRemove=LBI.size()-ri.nLBI; while (lbiRemove>0 && LBI.size()>0) { ListBoxInfo &lbi=LBI.back(); lbi.callBack = nullptr; // Avoid kill focus event here lbi.clearHandler(); DestroyWindow(lbi.hWnd); if (lbi.writeLock) hasCleared = true; lbiByHwnd.erase(lbi.hWnd); LBI.pop_back(); lbiRemove--; } int tlRemove=TL.size()-ri.nTL; while (tlRemove>0 && TL.size()>0) { TL.pop_back(); tlRemove--; } itTL=TL.begin(); updateImageReferences(); // Clear cache of shown strings shownStrings.clear(); int biRemove=BI.size()-ri.nBI; while (biRemove>0 && BI.size()>0) { ButtonInfo &bi=BI.back(); bi.callBack = nullptr; bi.clearHandler(); DestroyWindow(bi.hWnd); biByHwnd.erase(bi.hWnd); BI.pop_back(); biRemove--; } int iiRemove=II.size()-ri.nII; while (iiRemove>0 && II.size()>0) { InputInfo &ii=II.back(); ii.callBack = nullptr; // Avoid kill focus event here ii.clearHandler(); DestroyWindow(ii.hWnd); iiByHwnd.erase(ii.hWnd); II.pop_back(); iiRemove--; } int rectRemove=Rectangles.size()-ri.nRect; while (rectRemove>0 && Rectangles.size()>0) { Rectangles.pop_back(); rectRemove--; } int hwndRemove=FocusList.size()-ri.nHWND; while(hwndRemove>0 && FocusList.size()>0) { FocusList.pop_back(); hwndRemove--; } while(Tables.size() > unsigned(ri.nTables)){ auto t=Tables.back().table; Tables.pop_back(); t->hide(*this); } int dataRemove=DataInfo.size()-ri.nData; while(dataRemove>0 && DataInfo.size()>0) { DataInfo.pop_front(); dataRemove--; } CurrentX=ri.sCX; CurrentY=ri.sCY; onClear = ri.onClear; postClear = ri.postClear; for (auto it = restorePoints.begin(); it != restorePoints.end(); ) { if (!ri.restorePoints.count(it->first) && (& it->second != &ri)) it = restorePoints.erase(it); else ++it; } } void gdioutput::restore(const string &restorePointId, bool doRefresh) { auto rp = restorePoints.find(restorePointId); if (rp == restorePoints.end()) return; const RestoreInfo& ri = rp->second; restoreInternal(ri); MaxX=ri.sMX; MaxY=ri.sMY; if (doRefresh) refresh(); setOffset(ri.sOY, ri.sOY, false); } RECT gdioutput::getDimensionSince(const string& restorePointId) const { auto rp = restorePoints.find(restorePointId); if (rp == restorePoints.end()) throw meosException("Internal error: " + restorePointId); const RestoreInfo& ri = rp->second; RECT out = {numeric_limits::max(), numeric_limits::max(), 0, 0}; auto grow = [&out](int x, int y, int w, int h) { out.left = min(out.left, x); out.right = max(out.right, x + w); out.top = min(out.top, y); out.bottom = max(out.bottom, y + h); }; int lbiRemove = LBI.size() - ri.nLBI; for (auto it = LBI.rbegin(); lbiRemove > 0; lbiRemove--, ++it) { grow(it->getX(), it->getY(), it->getWidth(), it->getHeight()); } int tlRemove = TL.size() - ri.nTL; for (auto it = TL.rbegin(); tlRemove > 0; tlRemove--, ++it) { grow(it->getX(), it->getY(), it->getWidth(), it->getHeight()); } int biRemove = BI.size() - ri.nBI; for (auto it = BI.rbegin(); biRemove > 0; biRemove--, ++it) { int w, h; it->getDimension(*this, w, h); grow(it->getX(), it->getY(), w, h); } int iiRemove = II.size() - ri.nII; for (auto it = II.rbegin(); iiRemove > 0; iiRemove--, ++it) { grow(it->getX(), it->getY(), it->getWidth(), it->getHeight()); } int rectRemove = Rectangles.size() - ri.nRect; for (auto it = Rectangles.rbegin(); rectRemove > 0; rectRemove--, ++it) { auto& rc = it->getRect(); grow(rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top); } return out; } void gdioutput::restoreNoUpdate(const string &restorePointId) { auto rp = restorePoints.find(restorePointId); if (rp == restorePoints.end()) return; const RestoreInfo& ri = rp->second; MaxX=ri.sMX; MaxY=ri.sMY; restoreInternal(ri); } bool gdioutput::canClear() { if (!onClear) return true; try { return onClear->makeEvent(*this, GUI_CLEAR)!=0; } catch (const meosCancel&) { return false; } catch (meosException & ex) { if (isTestMode) throw ex; wstring msg = ex.wwhat(); alert(msg); } catch(const std::exception &ex) { if (isTestMode) throw ex; string msg(ex.what()); alert(msg); } return true; } int gdioutput::sendCtrlMessage(const string &id) { for (list::iterator it=BI.begin(); it != BI.end(); ++it) { if (id==it->id) { if (it->hasEventHandler()) return it->handleEvent(*this, GUI_BUTTON); else if (it->callBack) return it->callBack(this, GUI_BUTTON, &*it); //it may be destroyed here... } } for(list::iterator it=Events.begin(); it != Events.end(); ++it){ if (id==it->id) { if (it->hasEventHandler()) return it->handleEvent(*this, GUI_EVENT); else if (it->callBack) return it->callBack(this, GUI_EVENT, &*it); //it may be destroyed here... } } #ifdef _DEBUG throw meosException("Unknown command " +id); #endif return 0; } void gdioutput::unregisterEvent(const string &id) { list::iterator it; for (it = Events.begin(); it != Events.end(); ++it) { if ( id == it->id) { Events.erase(it); return; } } } EventInfo &gdioutput::registerEvent(const string &id, GUICALLBACK cb) { list::iterator it; for (it = Events.begin(); it != Events.end(); ++it) { if ( id == it->id) { Events.erase(it); break; } } EventInfo ei; ei.id=id; ei.callBack=cb; Events.push_front(ei); return Events.front(); } void flushEvent(const string &id, const string &origin, DWORD data, int extraData); DWORD gdioutput::makeEvent(const string &id, const string &origin, DWORD data, int extraData, bool doflush) { if (doflush) { #ifndef MEOSDB ::flushEvent(id, origin, data, extraData); #else throw std::exception("internal gdi/database error"); #endif } else { list::iterator it; for(it=Events.begin(); it != Events.end(); ++it){ if (id==it->id && (it->callBack || it->hasEventHandler()) ) { it->setData(origin, data); if (extraData) { it->setExtra(extraData); } if (it->handleEvent(*this, GUI_EVENT)) { return 1; } else return it->callBack(this, GUI_EVENT, &*it); //it may be destroyed here... } } } return -1; } RectangleInfo &RectangleInfo::changeDimension(gdioutput &gdi, int dx, int dy) { rc.right += dx; rc.bottom += dy; int ex = gdi.scaleLength(5); gdi.updatePos(rc.left, rc.top, rc.right-rc.left+ex, rc.bottom-rc.top+ex); return *this; } RectangleInfo &gdioutput::addRectangle(const RECT &rc, GDICOLOR color, bool drawBorder, bool addFirst) { RectangleInfo ri; ri.rc.left = min(rc.left, rc.right); ri.rc.right = max(rc.left, rc.right); ri.rc.top = min(rc.top, rc.bottom); ri.rc.bottom = max(rc.top, rc.bottom); if (color==colorDefault) ri.color = GetSysColor(COLOR_INFOBK); else if (color == colorWindowBar) { ri.color = GetSysColor(COLOR_3DFACE); } else ri.color = color; ri.color2 = ri.color; ri.drawBorder = drawBorder; if (hWndTarget && !manualUpdate) { HDC hDC=GetDC(hWndTarget); renderRectangle(hDC, 0, ri); ReleaseDC(hWndTarget, hDC); } int ex = scaleLength(5); updatePos(ri.rc.left, ri.rc.top, ri.rc.right-ri.rc.left+ex, ri.rc.bottom-ri.rc.top+ex); if (addFirst) { Rectangles.push_front(ri); return Rectangles.front(); } else { Rectangles.push_back(ri); return Rectangles.back(); } } RectangleInfo &gdioutput::getRectangle(const char *id) { for (list::iterator it = Rectangles.begin(); it != Rectangles.end(); ++it) { return *it; } string err = string("Internal Error, identifier not found: X#") + id; throw std::exception(err.c_str()); } void gdioutput::setOffset(int x, int y, bool update) { int h,w; getTargetDimension(w, h); int cdy = 0; int cdx = 0; if (y!=OffsetY) { int oldY = OffsetY; OffsetY = y; if (OffsetY < 0) OffsetY = 0; else if (OffsetY > MaxY) OffsetY = MaxY; //cdy=(oldY!=OffsetY); cdy = oldY - OffsetY; } if (x!=OffsetX) { int oldX = OffsetX; OffsetX = x; if (OffsetX < 0) OffsetX = 0; else if (OffsetX > MaxX) OffsetX = MaxX; //cdx=(oldX!=OffsetX); cdx = oldX - OffsetX; } if (cdx || cdy) { updateScrollbars(); updateObjectPositions(); if (cdy) { SCROLLINFO si; memset(&si, 0, sizeof(si)); si.nPos=OffsetY; si.fMask=SIF_POS; SetScrollInfo(hWndTarget, SB_VERT, &si, true); } if (cdx) { SCROLLINFO si; memset(&si, 0, sizeof(si)); si.nPos=OffsetX; si.fMask=SIF_POS; SetScrollInfo(hWndTarget, SB_HORZ, &si, true); } if (update) { //RECT ScrollArea, ClipArea; //GetClientRect(hWndTarget, &ScrollArea); //ClipArea = ScrollArea; /* ScrollArea.top=-gdi->getHeight()-100; ScrollArea.bottom+=gdi->getHeight(); ScrollArea.right=gdi->getWidth()-gdi->GetOffsetX()+15; ScrollArea.left = -2000; */ ScrollWindowEx(hWndTarget, -cdx, cdy, NULL, NULL, (HRGN) NULL, (LPRECT) NULL, 0/*SW_INVALIDATE|SW_SMOOTHSCROLL|(1000*65536 )*/); UpdateWindow(hWndTarget); } } } void gdioutput::scrollTo(int x, int y) { int cx=x-OffsetX; int cy=y-OffsetY; int h,w; getTargetDimension(w, h); bool cdy=false; bool cdx=false; if (cy<=(h/15) || cy>=(h-h/10)) { int oldY=OffsetY; OffsetY=y-h/2; if (OffsetY<0) OffsetY=0; else if (OffsetY>MaxY) OffsetY=MaxY; cdy=(oldY!=OffsetY); } if (cx<=(w/15) || cx>=(w-w/8)) { int oldX=OffsetX; OffsetX=x-w/2; if (OffsetX<0) OffsetX=0; else if (OffsetX>MaxX) OffsetX=MaxX; cdx=(oldX!=OffsetX); } if (cdx || cdy) { updateScrollbars(); updateObjectPositions(); if (cdy) { SCROLLINFO si; memset(&si, 0, sizeof(si)); si.nPos=OffsetY; si.fMask=SIF_POS; SetScrollInfo(hWndTarget, SB_VERT, &si, true); } if (cdx) { SCROLLINFO si; memset(&si, 0, sizeof(si)); si.nPos=OffsetX; si.fMask=SIF_POS; SetScrollInfo(hWndTarget, SB_HORZ, &si, true); } } } void gdioutput::scrollToBottom() { OffsetY=MaxY; SCROLLINFO si; memset(&si, 0, sizeof(si)); updateScrollbars(); updateObjectPositions(); si.nPos=OffsetY; si.fMask=SIF_POS; SetScrollInfo(hWndTarget, SB_VERT, &si, true); } bool gdioutput::clipOffset(int PageX, int PageY, int &MaxOffsetX, int &MaxOffsetY) { if (animationData) { MaxOffsetX = 0; MaxOffsetY = 0; return false; } if (highContrast) setHighContrastMaxWidth(); int oy=OffsetY; int ox=OffsetX; MaxOffsetY=max(getPageY()-PageY, 0); MaxOffsetX=max(getPageX()-PageX, 0); if (OffsetY<0) OffsetY=0; else if (OffsetY>MaxOffsetY) OffsetY=MaxOffsetY; if (OffsetX<0) OffsetX=0; else if (OffsetX>MaxOffsetX) OffsetX=MaxOffsetX; if (ox!=OffsetX || oy!=OffsetY){ updateObjectPositions(); return true; } return false; } //bool ::GetSaveFile(string &file, char *filter) wstring gdioutput::browseForSave(const vector< pair > &filter, const wstring &defext, int &filterIndex) { if (isTestMode) { if (!cmdAnswers.empty()) { string ans = cmdAnswers.front(); cmdAnswers.pop_front(); if (ans.substr(0, 1) == "*") return widen(ans.substr(1)); } throw meosException("Browse for file"); } InitCommonControls(); TCHAR FileName[260]; FileName[0]=0; OPENFILENAME of; wstring sFilter; for (size_t k = 0; k< filter.size(); k++) { sFilter.append(lang.tl(filter[k].first)).push_back(0); sFilter.append(filter[k].second).push_back(0); } sFilter.push_back(0); of.lStructSize = sizeof(of); of.hwndOwner = hWndTarget; of.hInstance = (HINSTANCE)GetWindowLongPtr(hWndTarget, GWLP_HINSTANCE); of.lpstrFilter = sFilter.c_str(); of.lpstrCustomFilter = NULL; of.nMaxCustFilter = 0; of.nFilterIndex = filterIndex; of.lpstrFile = FileName; of.nMaxFile = 260; of.lpstrFileTitle = NULL; of.nMaxFileTitle = 0; of.lpstrInitialDir = NULL; of.lpstrTitle = NULL; of.Flags = OFN_OVERWRITEPROMPT|OFN_HIDEREADONLY; of.lpstrDefExt = defext.c_str(); of.lpfnHook = NULL; bool res; setCommandLock(); try { res = GetSaveFileName(&of) != false; liftCommandLock(); } catch (...) { liftCommandLock(); throw; } if (res==false) return L""; filterIndex=of.nFilterIndex; return FileName; } wstring gdioutput::browseForOpen(const vector< pair > &filter, const wstring &defext) { if (isTestMode) { if (!cmdAnswers.empty()) { string ans = cmdAnswers.front(); cmdAnswers.pop_front(); if (ans.substr(0, 1) == "*") return widen(ans.substr(1)); } throw meosException("Browse for file"); } InitCommonControls(); wchar_t FileName[260]; FileName[0]=0; OPENFILENAME of; wstring sFilter; for (size_t k = 0; k< filter.size(); k++) { sFilter.append(lang.tl(filter[k].first)).push_back(0); sFilter.append(filter[k].second).push_back(0); } sFilter.push_back(0); of.lStructSize = sizeof(of); of.hwndOwner = hWndTarget; of.hInstance = (HINSTANCE)GetWindowLongPtr(hWndTarget, GWLP_HINSTANCE); of.lpstrFilter = sFilter.c_str(); of.lpstrCustomFilter = NULL; of.nMaxCustFilter = 0; of.nFilterIndex = 1; of.lpstrFile = FileName; of.nMaxFile = 260; of.lpstrFileTitle = NULL; of.nMaxFileTitle = 0; of.lpstrInitialDir = NULL; of.lpstrTitle = NULL; of.Flags = OFN_PATHMUSTEXIST|OFN_FILEMUSTEXIST|OFN_HIDEREADONLY; of.lpstrDefExt = defext.c_str(); of.lpfnHook = NULL; bool res; setCommandLock(); try { res = GetOpenFileName(&of) != false; liftCommandLock(); } catch (...) { liftCommandLock(); throw; } if (res == false) return L""; return FileName; } wstring gdioutput::browseForFolder(const wstring &folderStart, const wchar_t *descr) { if (isTestMode) { if (!cmdAnswers.empty()) { string ans = cmdAnswers.front(); cmdAnswers.pop_front(); if (ans.substr(0, 1) == "*") return widen(ans.substr(1)); } throw meosException("Browse for folder"); } CoInitializeEx(0, COINIT_APARTMENTTHREADED); BROWSEINFO bi; wchar_t InstPath[260]; wcscpy_s(InstPath, folderStart.c_str()); memset(&bi, 0, sizeof(bi) ); bi.hwndOwner=hWndAppMain; bi.pszDisplayName=InstPath; wstring title = descr ? lang.tl(descr) : L""; bi.lpszTitle = title.c_str(); bi.ulFlags=BIF_RETURNONLYFSDIRS|BIF_EDITBOX|BIF_NEWDIALOGSTYLE|BIF_EDITBOX; LPITEMIDLIST pidl_new; setCommandLock(); try { pidl_new = SHBrowseForFolder(&bi); liftCommandLock(); } catch (...) { liftCommandLock(); throw; } if (pidl_new==NULL) return L""; // Convert the item ID list's binary // representation into a file system path //char szPath[_MAX_PATH]; SHGetPathFromIDList(pidl_new, InstPath); // Allocate a pointer to an IMalloc interface LPMALLOC pMalloc; // Get the address of our task allocator's IMalloc interface SHGetMalloc(&pMalloc); // Free the item ID list allocated by SHGetSpecialFolderLocation pMalloc->Free(pidl_new); // Free our task allocator pMalloc->Release(); return InstPath; } bool gdioutput::openDoc(const wchar_t *doc) { return (intptr_t)ShellExecute(hWndTarget, L"open", doc, NULL, L"", SW_SHOWNORMAL ) >32; } void gdioutput::init(HWND hWnd, HWND hMain, HWND hTab) { setWindow(hWnd); hWndAppMain=hMain; hWndTab=hTab; InitCommonControls(); hWndToolTip = CreateWindow(TOOLTIPS_CLASS, (LPWSTR) NULL, TTS_ALWAYSTIP, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, (HMENU) NULL, (HINSTANCE)GetWindowLongPtr(hWndTarget, GWLP_HINSTANCE), NULL); } ToolInfo &gdioutput::addToolTip(const string &tipId, const wstring &tip, HWND hWnd, RECT *rc) { static ToolInfo dummy; if (!hWndToolTip) return dummy; toolTips.push_back(ToolInfo()); ToolInfo &info = toolTips.back(); TOOLINFOW &ti = info.ti; info.tip = lang.tl(tip); memset(&ti, 0, sizeof(ti)); ti.cbSize = sizeof(TOOLINFO); if (hWnd != 0) { ti.uFlags = TTF_IDISHWND; info.id = uintptr_t(hWnd); ti.uId = (UINT_PTR) hWnd; } else { ti.uFlags = TTF_SUBCLASS; info.id = toolTips.size(); ti.uId = info.id; } ti.hwnd = hWndTarget; ti.hinst = (HINSTANCE)GetWindowLongPtr(hWndTarget, GWLP_HINSTANCE); info.name = tipId; ti.lpszText = (LPWSTR)toolTips.back().tip.c_str(); if (rc != 0) ti.rect = *rc; SendMessage(hWndToolTip, TTM_ADDTOOLW, 0, (LPARAM) &ti); if (tip.find('\n') != string::npos || tip.length()>40) SendMessage(hWndToolTip, TTM_SETMAXTIPWIDTH, 0, 250); return info; } ToolInfo *gdioutput::getToolTip(const string &id) { for (ToolList::reverse_iterator it = toolTips.rbegin(); it != toolTips.rend(); ++it) { if (it->name == id) return &*it; } return 0; } ToolInfo &gdioutput::updateToolTip(const string &id, const wstring &tip) { for (ToolList::reverse_iterator it = toolTips.rbegin(); it != toolTips.rend(); ++it) { if (it->name == id && hWndToolTip) { it->tip = lang.tl(tip); it->ti.lpszText = (LPWSTR)it->tip.c_str(); SendMessage(hWndToolTip, TTM_UPDATETIPTEXTW, 0, (LPARAM) &it->ti); return *it; } } BaseInfo &bi = getBaseInfo(id.c_str()); return addToolTip(id, tip, bi.getControlWindow()); } void gdioutput::selectTab(int Id) { if (hWndTab) TabCtrl_SetCurSel(hWndTab, Id); } void gdioutput::getTargetDimension(int &x, int &y) const { if (hWndTarget){ RECT rc; GetClientRect(hWndTarget, &rc); x=rc.right; y=rc.bottom; } else { x=0; y=0; } } Table &gdioutput::getTable() const { if (Tables.empty()) throw std::exception("No table defined"); return *const_cast(Tables.back().table.get()); } static int gdiTableCB(gdioutput *gdi, GuiEventType type, BaseInfo *data) { if (type == GUI_BUTTON) { ButtonInfo bi = *static_cast(data); gdi->tableCB(bi, &gdi->getTable()); } return 0; } void gdioutput::tableCB(ButtonInfo &bu, Table *t) { if (bu.id=="tblPrint") { t->keyCommand(*this, KC_PRINT); } else if (bu.id=="tblColumns") { disableTables(); if (Tables.empty()) return; restore("tblRestore"); int ybase = Tables.back().yp; addString("", ybase, 20, boldLarge, "Välj kolumner"); ybase += scaleLength(30); addString("", ybase, 20, 0, L"Välj kolumner för tabellen X.#"+ t->getTableName()); ybase += getLineHeight()*2; addListBox(20, ybase, "tblColSel", 180, 450, 0, L"", L"", true); const int btnHeight = getButtonHeight()+scaleLength(5); vector cols = t->getColumns(); set sel; for (size_t k=0; kgetTableId()); addButton(xp, ybase+btnHeight*4, "tblOK", "OK", gdiTableCB).setExtra(t->getTableId()); addButton(xp, ybase+btnHeight*5, "tblCancel", "Avbryt", gdiTableCB); if (toolbar) toolbar->hide(); refresh(); } else if (bu.id=="tblAll") { set sel; sel.insert(-1); setSelection("tblColSel", sel); } else if (bu.id=="tblNone") { set sel; setSelection("tblColSel", sel); } else if (bu.id=="tblAuto") { restore("tblRestore", false); t->autoSelectColumns(); t->autoAdjust(*this); enableTables(); refresh(); } else if (bu.id=="tblOK") { set sel; getSelection("tblColSel", sel); restore("tblRestore", false); t->clearCellSelection(this); t->selectColumns(sel); t->autoAdjust(*this); enableTables(); refresh(); } else if (bu.id=="tblReset") { t->clearCellSelection(this); t->resetColumns(); t->autoAdjust(*this); t->updateDimension(*this); refresh(); } else if (bu.id == "tblMarkAll") { t->keyCommand(*this, KC_MARKALL); } else if (bu.id == "tblClearAll") { t->keyCommand(*this, KC_CLEARALL); } else if (bu.id=="tblUpdate") { t->keyCommand(*this, KC_REFRESH); } else if (bu.id=="tblCancel") { restore("tblRestore", true); enableTables(); refresh(); } else if (bu.id == "tblCopy") { t->keyCommand(*this, KC_COPY); } else if (bu.id == "tblPaste") { t->keyCommand(*this, KC_PASTE); } else if (bu.id == "tblRemove") { t->keyCommand(*this, KC_DELETE); } else if (bu.id == "tblInsert") { t->keyCommand(*this, KC_INSERT); } } void gdioutput::enableTables() { useTables=true; if (!Tables.empty()) { auto &t = Tables.front().table; if (toolbar == 0) toolbar = new Toolbar(*this); toolbar->setData(t); string tname = string("table") + itos(t->canDelete()) + itos(t->canInsert()) + itos(t->canPaste()); if (!toolbar->isLoaded(tname)) { toolbar->reset(); toolbar->addButton("tblColumns", 1, 2, "Välj vilka kolumner du vill visa"); toolbar->addButton("tblPrint", 0, STD_PRINT, "Skriv ut tabellen (X)#Ctrl+P"); toolbar->addButton("tblUpdate", 1, 0, "Uppdatera alla värden i tabellen (X)#F5"); toolbar->addButton("tblReset", 1, 4, "Återställ tabeldesignen och visa allt"); toolbar->addButton("tblMarkAll", 1, 5, "Markera allt (X)#Ctrl+A"); toolbar->addButton("tblClearAll", 1, 6, "Markera inget (X)#Ctrl+D"); toolbar->addButton("tblCopy", 0, STD_COPY, "Kopiera selektionen till urklipp (X)#Ctrl+C"); if (t->canPaste()) toolbar->addButton("tblPaste", 0, STD_PASTE, "Klistra in data från urklipp (X)#Ctrl+V"); if (t->canDelete()) toolbar->addButton("tblRemove", 1, 1, "Ta bort valda rader från tabellen (X)#Del"); if (t->canInsert()) toolbar->addButton("tblInsert", 1, 3, "Lägg till en ny rad i tabellen (X)#Ctrl+I"); toolbar->createToolbar(tname, L"Tabellverktyg"); } else { toolbar->show(); } } } void gdioutput::processToolbarMessage(const string &id, Table *tbl) { if (hasCommandLock()) return; wstring msg; string cmd; if (getRecorder().recording()) { cmd = "tableCmd(\"" + id + "\"); //" + toUTF8(tbl->getTableName()); } try { ButtonInfo bi; bi.id = id; tableCB(bi, tbl); getRecorder().record(cmd); } catch (const meosCancel&) { } catch (meosException &ex) { msg = ex.wwhat(); } catch(std::exception &ex) { msg = widen(ex.what()); if (msg.empty()) msg = L"Ett okänt fel inträffade."; } catch(...) { msg = L"Ett okänt fel inträffade."; } if (!msg.empty()) alert(msg); } HWND gdioutput::getToolbarWindow() const { if (!toolbar) return 0; return toolbar->getFloater(); } bool gdioutput::hasToolbar() const { if (!toolbar) return false; return toolbar->isVisible(); } void gdioutput::activateToolbar(bool active) { if (!toolbar) return; toolbar->activate(active); } void gdioutput::disableTables() { useTables=false; for(list::iterator bit=BI.begin(); bit != BI.end();) { if (bit->id.substr(0, 3)=="tbl" && bit->getExtra()!=0) { string id = bit->id; ++bit; removeWidget(id); } else ++bit; } } void gdioutput::addTable(const shared_ptr
&t, int x, int y) { TableInfo ti; ti.table = t; ti.xp = x; ti.yp = y; t->setPosition(x,y, MaxX, MaxY); if (t->hasAutoSelect()) t->autoSelectColumns(); t->autoAdjust(*this); Tables.push_back(ti); //updatePos(x, y, dx + TableXMargin, dy + TableYMargin); setRestorePoint("tblRestore"); enableTables(); updateScrollbars(); } void gdioutput::pasteText(const char *id) { list::iterator it; for (it=II.begin(); it != II.end(); ++it) { if (it->id==id) { SendMessage(it->hWnd, WM_PASTE, 0,0); return; } } } wchar_t *gdioutput::getExtra(const char *id) const { return getBaseInfo(id).getExtra(); } int gdioutput::getExtraInt(const char *id) const { return getBaseInfo(id).getExtraInt(); } bool gdioutput::hasEditControl() const { return !II.empty() || (Tables.size()>0 && Tables.front().table->hasEditControl()); } void gdioutput::enableEditControls(bool enable, bool processAll) { set TCheckControls; for (list::iterator it=BI.begin(); it != BI.end(); ++it) { if (it->isEditControl || processAll) { EnableWindow(it->hWnd, enable); if (it->isCheckbox) { TCheckControls.insert("T" + it->id); } } } for (list::iterator it=TL.begin(); it != TL.end(); ++it) { if (TCheckControls.count(it->id)) { enableCheckBoxLink(*it, enable); } } for (list::iterator it=II.begin(); it != II.end(); ++it) { if (it->isEditControl) EnableWindow(it->hWnd, enable); } for( list::iterator it=LBI.begin(); it != LBI.end(); ++it) { if (it->isEditControl) EnableWindow(it->hWnd, enable); } } void gdioutput::closeWindow() { PostMessage(hWndTarget, WM_CLOSE, 0, 0); } InputInfo &InputInfo::setPassword(bool pwd) { LONG style = GetWindowLong(hWnd, GWL_STYLE); if (pwd) style |= ES_PASSWORD; else style &= ~ES_PASSWORD; SetWindowLong(hWnd, GWL_STYLE, style); SendMessage(hWnd, EM_SETPASSWORDCHAR, 183, 0); return *this; } int gdioutput::setHighContrastMaxWidth() { RECT rc; GetClientRect(hWndTarget, &rc); if (lockRefresh) return rc.bottom; #ifdef DEBUGRENDER OutputDebugString("Set high contrast\n"); #endif double w = getPageX(); double s = rc.right / w; if (!highContrast || (fabs(s-1.0) > 1e-3 && (s * scale) >= 1.0) ) { lockRefresh = true; try { highContrast = true; scaleSize(s); refresh(); lockRefresh = false; } catch (...) { lockRefresh = false; throw; } } return rc.bottom; } double static acc = 0; void gdioutput::setAutoScroll(double speed) { if (autoSpeed == 0 && speed != 0) { SetTimer(hWndTarget, 1001, 20, 0); autoPos = OffsetY; } else if (speed == 0 && autoSpeed != 0) { KillTimer(hWndTarget, 1001); } if (speed == -1) autoSpeed = -autoSpeed; else autoSpeed = speed; autoCounter = - M_PI_2; acc = 0; } void gdioutput::getAutoScroll(double &speed, double &pos) const { RECT rc; GetClientRect(hWndTarget, &rc); double height = rc.bottom; double s = autoSpeed * (1 + height/1000 + sin(autoCounter)/max(1.0, 500/height)); autoCounter += M_PI/75.0; if (autoCounter > M_PI) autoCounter -= 2*M_PI; acc += 0.3/30; if (acc>0.8) acc = 0.8; speed = (lastSpeed * (1.0-acc) + s * acc); lastSpeed = speed; pos = autoPos; } void gdioutput::storeAutoPos(double pos) { autoPos = pos; } void gdioutput::setFullScreen(bool useFullScreen) { if (useFullScreen && !fullScreen) { SetWindowLong(hWndTarget, GWL_STYLE, WS_POPUP | WS_BORDER); ShowWindow(hWndTarget, SW_MAXIMIZE); UpdateWindow(hWndTarget); } else if (fullScreen) { SetWindowLong(hWndTarget, GWL_STYLE, WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN | WS_CLIPSIBLINGS); ShowWindow(hWndTarget, SW_NORMAL); UpdateWindow(hWndTarget); } fullScreen = useFullScreen; } void gdioutput::setColorMode(DWORD bgColor1, DWORD bgColor2, DWORD fgColor, const wstring &bgImage) { backgroundColor1 = bgColor1; backgroundColor2 = bgColor2; foregroundColor = fgColor; backgroundImage = bgImage; } DWORD gdioutput::getFGColor() const { return foregroundColor != -1 ? foregroundColor : 0; } DWORD gdioutput::getBGColor() const { return backgroundColor1 != -1 ? backgroundColor1 : RGB(255,255,255); } DWORD gdioutput::getBGColor2() const { return backgroundColor2; } const wstring &gdioutput::getBGImage() const { return backgroundImage; } bool gdioutput::hasCommandLock() const { if (commandLock) return true; if (commandUnlockTime > 0) { DWORD t = GetTickCount(); if (commandUnlockTime < (commandUnlockTime + 500) && t < (commandUnlockTime+500)) { commandUnlockTime = 0; return true; } } return false; } void gdioutput::setCommandLock() const { commandLock = true; } void gdioutput::liftCommandLock() const { commandUnlockTime = GetTickCount(); commandLock = false; } int gdioutput::getLineHeight(gdiFonts font, const wchar_t *face) const { int h; if (face == nullptr) h = getFontHeight(font, _EmptyWString); else h = getFontHeight(font, face); return (11*h)/10; } GDIImplFontSet::GDIImplFontSet() { Huge = 0; Large = 0; Medium = 0; Small = 0; pfLarge = 0; pfMedium = 0; pfMediumPlus = 0; pfSmall = 0; pfSmallItalic = 0; pfItalic = 0; pfItalicMediumPlus = 0; pfMono = 0; } GDIImplFontSet::~GDIImplFontSet() { deleteFonts(); } void GDIImplFontSet::deleteFonts() { if (Huge) DeleteObject(Huge); Huge = 0; if (Large) DeleteObject(Large); Large = 0; if (Medium) DeleteObject(Medium); Medium = 0; if (Small) DeleteObject(Small); Small = 0; if (pfLarge) DeleteObject(pfLarge); pfLarge = 0; if (pfMedium) DeleteObject(pfMedium); pfMedium = 0; if (pfMediumPlus) DeleteObject(pfMediumPlus); pfMediumPlus = 0; if (pfSmall) DeleteObject(pfSmall); pfSmall = 0; if (pfMono) DeleteObject(pfMono); pfMono = 0; if (pfSmallItalic) DeleteObject(pfSmallItalic); pfSmallItalic = 0; if (pfItalicMediumPlus) DeleteObject(pfItalicMediumPlus); pfItalicMediumPlus = 0; if (pfItalic) DeleteObject(pfItalic); pfItalic = 0; } float GDIImplFontSet::baseSize(int format, float scale) { format &= 0xFF; if (format==0 || format==10) { return 14 * scale; } else if (format==fontMedium){ return 14 * scale; } else if (format==1) { return 14.0001f * scale; //Bold } else if (format==boldLarge){ return 24.0001f * scale; } else if (format==boldHuge){ return 34.0001f * scale; } else if (format==boldSmall){ return 11.0001f * scale; } else if (format==fontLarge){ return 24 * scale; } else if (format==fontMediumPlus){ return 18 * scale; } else if (format==fontSmall){ return 11 * scale; } else if (format==italicSmall){ return 11 * scale; } else if (format==italicText){ return 14 * scale; } else if (format == monoText){ return 14 * scale; } else if (format==italicMediumPlus){ return 18 * scale; } else { return 10 * scale; } } void GDIImplFontSet::init(double scale, const wstring &font, const wstring &gdiName_) { int charSet = DEFAULT_CHARSET; deleteFonts(); gdiName = gdiName_; Huge=CreateFont(int(scale*34), 0, 0, 0, FW_BOLD, false, false, false, charSet, OUT_TT_ONLY_PRECIS, CLIP_DEFAULT_PRECIS, PROOF_QUALITY, DEFAULT_PITCH|FF_ROMAN, font.c_str()); Large=CreateFont(int(scale*24), 0, 0, 0, FW_BOLD, false, false, false, charSet, OUT_TT_ONLY_PRECIS, CLIP_DEFAULT_PRECIS, PROOF_QUALITY, DEFAULT_PITCH|FF_ROMAN, font.c_str()); Medium=CreateFont(int(scale*14), 0, 0, 0, FW_BOLD, false, false, false, charSet, OUT_TT_ONLY_PRECIS, CLIP_DEFAULT_PRECIS, PROOF_QUALITY, DEFAULT_PITCH|FF_ROMAN, font.c_str()); Small=CreateFont(int(scale*11), 0, 0, 0, FW_BOLD, false, false, false, charSet, OUT_TT_ONLY_PRECIS, CLIP_DEFAULT_PRECIS, PROOF_QUALITY, DEFAULT_PITCH|FF_ROMAN, font.c_str()); pfLarge=CreateFont(int(scale*24), 0, 0, 0, FW_NORMAL, false, false, false, charSet, OUT_TT_ONLY_PRECIS, CLIP_DEFAULT_PRECIS, PROOF_QUALITY, DEFAULT_PITCH|FF_ROMAN, font.c_str()); pfMedium=CreateFont(int(scale*14), 0, 0, 0, FW_NORMAL, false, false, false, charSet, OUT_TT_ONLY_PRECIS, CLIP_DEFAULT_PRECIS, PROOF_QUALITY, DEFAULT_PITCH|FF_ROMAN, font.c_str()); pfMediumPlus=CreateFont(int(scale*18), 0, 0, 0, FW_NORMAL, false, false, false, charSet, OUT_TT_ONLY_PRECIS, CLIP_DEFAULT_PRECIS, PROOF_QUALITY, DEFAULT_PITCH|FF_ROMAN, font.c_str()); pfSmall=CreateFont(int(scale*11), 0, 0, 0, FW_NORMAL, false, false, false, charSet, OUT_TT_ONLY_PRECIS, CLIP_DEFAULT_PRECIS, PROOF_QUALITY, DEFAULT_PITCH|FF_ROMAN, font.c_str()); pfSmallItalic = CreateFont(int(scale*11), 0, 0, 0, FW_NORMAL, true, false, false, charSet, OUT_TT_ONLY_PRECIS, CLIP_DEFAULT_PRECIS, PROOF_QUALITY, DEFAULT_PITCH|FF_ROMAN, font.c_str()); pfItalic = CreateFont(int(scale*14), 0, 0, 0, FW_NORMAL, true, false, false, charSet, OUT_TT_ONLY_PRECIS, CLIP_DEFAULT_PRECIS, PROOF_QUALITY, DEFAULT_PITCH|FF_ROMAN, font.c_str()); pfMono = CreateFont(int(scale*12), 0, 0, 0, FW_NORMAL, false, false, false, charSet, OUT_TT_ONLY_PRECIS, CLIP_DEFAULT_PRECIS, PROOF_QUALITY, DEFAULT_PITCH|FF_MODERN, L"Lucida Console"); pfItalicMediumPlus = CreateFont(int(scale*18), 0, 0, 0, FW_NORMAL, true, false, false, charSet, OUT_TT_ONLY_PRECIS, CLIP_DEFAULT_PRECIS, PROOF_QUALITY, DEFAULT_PITCH|FF_ROMAN, font.c_str()); } void GDIImplFontSet::getInfo(FontInfo &fi) const { fi.normal = pfMedium; fi.bold = Medium; fi.italic = pfItalic; } void GDIImplFontSet::selectFont(HDC hDC, int format) const { if (format==0 || format==10) { SelectObject(hDC, pfMedium); } else if (format==fontMedium){ SelectObject(hDC, pfMedium); } else if (format==1){ SelectObject(hDC, Medium); } else if (format==boldLarge){ SelectObject(hDC, Large); } else if (format==boldHuge){ SelectObject(hDC, Huge); } else if (format==boldSmall){ SelectObject(hDC, Small); } else if (format==fontLarge){ SelectObject(hDC, pfLarge); } else if (format==fontMediumPlus){ SelectObject(hDC, pfMediumPlus); } else if (format==fontSmall){ SelectObject(hDC, pfSmall); } else if (format==italicSmall){ SelectObject(hDC, pfSmallItalic); } else if (format==italicText){ SelectObject(hDC, pfItalic); } else if (format==italicMediumPlus){ SelectObject(hDC, pfItalicMediumPlus); } else if (format == monoText) { SelectObject(hDC, pfMono); } else { SelectObject(hDC, GetStockObject(DEFAULT_GUI_FONT)); } } HFONT GDIImplFontSet::getFont(int format) const { format = format & 31; if (format==0 || format==10) { return pfMedium; } else if (format==fontMedium){ return pfMedium; } else if (format==1){ return Medium; } else if (format==boldLarge){ return Large; } else if (format==boldHuge){ return Huge; } else if (format==boldSmall){ return Small; } else if (format==fontLarge){ return pfLarge; } else if (format==fontMediumPlus){ return pfMediumPlus; } else if (format==fontSmall){ return pfSmall; } else if (format==italicSmall){ return pfSmallItalic; } else if (format==italicText){ return pfItalic; } else if (format==italicMediumPlus){ return pfItalicMediumPlus; } else if (format == monoText) { return pfMono; } else { return (HFONT)GetStockObject(DEFAULT_GUI_FONT); } } const GDIImplFontSet &gdioutput::getCurrentFont() const { if (currentFontSet == 0) { map::const_iterator res = fonts.find(currentFont); if (res == fonts.end()) throw meosException(L"Font not defined: " + currentFont); currentFontSet = &res->second; } return *currentFontSet; } const GDIImplFontSet &gdioutput::getFont(const wstring &font) const { map::const_iterator res = fonts.find(font); if (res == fonts.end()) { return const_cast(this)->loadFont(font); throw meosException(L"Font not defined: " + currentFont); } return res->second; } int CALLBACK enumFontProc(const LOGFONT* logFont, const TEXTMETRIC *metric, DWORD id, LPARAM lParam) { if (logFont->lfFaceName[0] == '@') return 1; if (metric->tmAveCharWidth <= 0) return 1; vector &enumFonts = *(vector *)(lParam); /*string we = "we: " + itos(logFont->lfWeight); string wi = "wi: " + itos(metric->tmAveCharWidth); string he = "he: " + itos(metric->tmHeight); string info = string(logFont->lfFaceName) + ", " + we + ", " + wi + ", " + he;*/ enumFonts.push_back(GDIImplFontEnum()); GDIImplFontEnum &f = enumFonts.back(); f.face = logFont->lfFaceName; f.height = metric->tmHeight; f.width = metric->tmAveCharWidth; f.relScale = ((double(metric->tmHeight) / double(metric->tmAveCharWidth)) * 14.0/36.0); return 1; } void gdioutput::getEnumeratedFonts(vector< pair > &output) const { if (enumeratedFonts.empty()) { HDC hDC = GetDC(hWndTarget); // EnumFontFamilies(hDC, NULL, enumFontProc, LPARAM(&enumeratedFonts)); LOGFONT logFont; memset(&logFont, 0, sizeof(LOGFONT)); logFont.lfCharSet = DEFAULT_CHARSET; EnumFontFamiliesEx(hDC, &logFont, enumFontProc, LPARAM(&enumeratedFonts), 0); ReleaseDC(hWndTarget, hDC); } output.resize(enumeratedFonts.size()); for (size_t k = 0; k avgWidthCache.size()) throw meosException("Internal font error"); if (avgWidthCache[font] == 0) { TextInfo ti; ti.xp = 0; ti.yp = 0; ti.format = font; ti.text = L"Goliat Meze 1234:5678"; ti.font = gdiName; gdi.calcStringSize(ti); avgWidthCache[font] = double(ti.textRect.right) / double(ti.text.length()); } return avgWidthCache[font]; } const wstring &gdioutput::getFontName(int id) { return _EmptyWString; } GDIImplFontEnum::GDIImplFontEnum() { relScale = 1.0; } GDIImplFontEnum::~GDIImplFontEnum() { } /* FontEncoding interpetEncoding(const string &enc) { if (enc == "RUSSIAN") return Russian; else if (enc == "EASTEUROPE") return EastEurope; else if (enc == "HEBREW") return Hebrew; else return ANSI; }*/ const string &gdioutput::narrow(const wstring &input) { string &output = StringCache::getInstance().get(); output.clear(); output.insert(output.begin(), input.begin(), input.end()); return output; } const wstring &gdioutput::widen(const string &input) { wstring &output = StringCache::getInstance().wget(); int cp = 1252; if (input.empty()) { output = L""; return output; } output.reserve(input.size()+1); output.resize(input.size(), 0); MultiByteToWideChar(cp, MB_PRECOMPOSED, input.c_str(), input.size(), &output[0], output.size() * sizeof(wchar_t)); return output; } const wstring &gdioutput::recodeToWide(const string &input) { wstring &output = StringCache::getInstance().wget(); int cp = defaultCodePage; // if (defaultCodePage > 0) // cp = defaultCodePage; /*switch(getEncoding()) { case Russian: cp = 1251; break; case EastEurope: cp = 1250; break; case Hebrew: cp = 1255; break; }*/ if (input.empty()) { output = L""; return output; } output.reserve(input.size()+1); output.resize(input.size(), 0); MultiByteToWideChar(cp, MB_PRECOMPOSED, input.c_str(), input.size(), &output[0], output.size() * sizeof(wchar_t)); return output; } const string &gdioutput::recodeToNarrow(const wstring &input) { string &output = StringCache::getInstance().get(); int cp = defaultCodePage; // if (defaultCodePage > 0) // cp = defaultCodePage; /*switch(getEncoding()) { case Russian: cp = 1251; break; case EastEurope: cp = 1250; break; case Hebrew: cp = 1255; break; }*/ if (input.empty()) { output = ""; return output; } int res = input.size() * 3 + 2; output.reserve(res); output.resize(input.size(), 0); BOOL usedDef = false; int ok = WideCharToMultiByte(cp, 0, input.c_str(), input.size(), &output[0], res, "?", &usedDef); return output; } const wstring &gdioutput::fromUTF8(const string &input) { wstring &output = StringCache::getInstance().wget(); size_t alloc = input.length() + 1; output.resize(alloc); wchar_t *ptr = &output[0]; int wlen = MultiByteToWideChar(CP_UTF8, 0, input.c_str(), input.length(), ptr, alloc); ptr[wlen] = 0; output.resize(wlen); return output; } const string &gdioutput::toUTF8(const wstring &winput) { string &output = StringCache::getInstance().get(); size_t alloc = winput.length()*4+32; output.resize(alloc); WideCharToMultiByte(CP_UTF8, 0, winput.c_str(), winput.length()+1, (char *)output.c_str(), alloc, 0, 0); output.resize(strlen(output.c_str())); return output; } void gdioutput::setListDescription(const wstring &desc) { listDescription = desc; } InputInfo &InputInfo::setFont(gdioutput &gdi, gdiFonts font) { SendMessage(hWnd, WM_SETFONT, (WPARAM) gdi.getCurrentFont().getFont(font), 0); return *this; } void gdioutput::copyToClipboard(const string &html, const wstring &txt) const { if (OpenClipboard(getHWNDMain()) != false) { EmptyClipboard(); size_t len = html.length() + 1; const char *output = html.c_str(); const char cbd[]= "Version:0.9\n" "StartHTML:%08u\n" "EndHTML:%08u\n" "StartFragment:%08u\n" "EndFragment:%08u\n"; char head[256]; sprintf_s(head, cbd, 1,0,0,0); int offset=strlen(head); //Fill header with relevant information int ho_start = offset; int ho_end = offset + len; sprintf_s(head, cbd, offset,offset+len,ho_start,ho_end); HANDLE hMem=GlobalAlloc(GMEM_MOVEABLE|GMEM_DDESHARE, offset+len); LPVOID data=GlobalLock(hMem); memcpy(LPSTR(data), head, offset); memcpy(LPSTR(data)+offset, output, len); GlobalUnlock(hMem); // Text format //HANDLE hMemText = 0; HANDLE hMemTextWide = 0; if (txt.length() > 0) { size_t siz = txt.length(); hMemTextWide = GlobalAlloc(GMEM_MOVEABLE|GMEM_DDESHARE, siz * sizeof(wchar_t)); LPVOID dataText = GlobalLock(hMemTextWide); memcpy(LPSTR(dataText), txt.c_str(), siz * sizeof(wchar_t)); GlobalUnlock(hMemTextWide); /* hMemText = GlobalAlloc(GMEM_MOVEABLE|GMEM_DDESHARE, txt.length()+1); LPVOID dataText=GlobalLock(hMemText); memcpy(LPSTR(dataText), txt.c_str() , txt.length()+1); GlobalUnlock(hMemText);*/ } else { // HTML table to text std::ostringstream result; bool started = false; bool newline = false; bool dowrite = false; for (size_t k = 0; k + 3 < html.size(); k++) { if (html[k] == '<') { if (html[k+1] == 't') { if (html[k+2] == 'r') { newline = true; if (started) result << "\r\n"; } else if (html[k+2] == 'd') { if (!newline) result << "\t"; started = true; newline = false; dowrite = true; } } else if (html[k+1] == '/') { if (html[k+2] == 't' && html[k+3] == 'd') { dowrite = false; } } while (k < html.size() && html[k] != '>') k++; } else { if (dowrite) result << html[k]; } } string atext = decodeXML(result.str()); /* result.flush(); for (size_t k = 0; k < atext.size(); k++) { if (atext[k] == '&') { size_t m = 0; while ((k+m) < atext.size() && atext[k+m] != ';') m++; if ((k+m) < atext.size() && atext[k+m] == ';') { string cmd = atext.substr(k, m-k); if (cmd == "nbsp") result << " "; else if (cmd == "amp") result << " "; else if (cmd == "lt") result << "<"; else if (cmd == "gt") result << ">"; else if (cmd == "quot") result << "\""; k += m; } } else result << atext[k]; } atext = result.str(); */ if (atext.size() > 0) { wstring atextw; int osize = atext.size(); atextw.resize(osize + 1, 0); size_t siz = atextw.size(); MultiByteToWideChar(CP_UTF8, 0, atext.c_str(), -1, &atextw[0], siz * sizeof(wchar_t)); hMemTextWide = GlobalAlloc(GMEM_MOVEABLE|GMEM_DDESHARE, siz * sizeof(wchar_t)); LPVOID dataText = GlobalLock(hMemTextWide); memcpy(LPSTR(dataText), atextw.c_str(), siz * sizeof(wchar_t)); GlobalUnlock(hMemTextWide); } } UINT CF_HTML = RegisterClipboardFormat(L"HTML format"); SetClipboardData(CF_HTML, hMem); if (hMemTextWide != 0) { SetClipboardData(CF_UNICODETEXT, hMemTextWide); } CloseClipboard(); } } Recorder &gdioutput::getRecorder() { if (recorder.first == 0) { recorder.first = new Recorder(); recorder.second = true; } return *recorder.first; } void gdioutput::initRecorder(Recorder *rec) { if (recorder.second) delete recorder.first; recorder.first = rec; recorder.second = false; } string gdioutput::dbPress(const string &id, int extra) { bool notEnabled = false; for (list::iterator it=BI.begin(); it != BI.end(); ++it) { if (id==it->id && (extra == -65536 || extra == it->getExtraInt())) { if (!IsWindowEnabled(it->hWnd)) { notEnabled = true; continue; } if (it->isCheckbox) { check(id, !isChecked(id)); } else if(!it->callBack && !it->hasEventHandler()) throw meosException("Button " + id + " is not active."); wstring val = it->text; if (it->hasEventHandler()) it->handleEvent(*this, GUI_BUTTON); else if (it->callBack) it->callBack(this, GUI_BUTTON, &*it); //it may be destroyed here... return toUTF8(val); } } if (notEnabled) throw meosException("Button " + id + " is not active."); throw meosException("Unknown command " + id + "."); } string gdioutput::dbPress(const string &id, const char *extra) { wstring eid = widen(extra ? extra : ""); for (list::iterator it=BI.begin(); it != BI.end(); ++it) { if (id==it->id && (!extra || (it->isExtraString() && eid == it->getExtra()))) { if (!IsWindowEnabled(it->hWnd)) throw meosException("Button " + id + " is not active."); if (it->isCheckbox) { check(id, !isChecked(id)); } else if(!it->callBack && !it->hasEventHandler()) throw meosException("Button " + id + " is not active."); wstring val = it->text; if (it->hasEventHandler()) it->handleEvent(*this, GUI_BUTTON); else if (it->callBack) it->callBack(this, GUI_BUTTON, &*it); //it may be destroyed here... return toUTF8(val); } } throw meosException(L"Unknown command " + widen(id) + L"/" + eid + L"."); } string gdioutput::dbSelect(const string &id, int data) { for (list::iterator it = LBI.begin(); it != LBI.end(); ++it) { if (id==it->id) { if (!IsWindowEnabled(it->hWnd)) throw meosException("Selection " + id + " is not active."); if (it->multipleSelection) { auto res = it->data2Index.find(data); if (res != it->data2Index.end()) SendMessage(it->hWnd, LB_SETSEL, true, res->second); else throw meosException("List " + id + " does not contain value " + itos(data) + "."); } else { if (!selectItemByData(id, data)) throw meosException("List " + id + " does not contain value " + itos(data) + "."); } UpdateWindow(it->hWnd); wstring res = it->text; internalSelect(*it); return toUTF8(res); } } throw meosException("Unknown selection " + id + "."); } void gdioutput::internalSelect(ListBoxInfo &bi) { bi.syncData(); if (bi.callBack || bi.handler || bi.managedHandler) { setWaitCursor(true); hasCleared = false; try { bi.writeLock = true; if (bi.hasEventHandler()) bi.handleEvent(*this, GUI_LISTBOX); else bi.callBack(this, GUI_LISTBOX, &bi); //it may be destroyed here... Then hasCleared is set. } catch(...) { if (!hasCleared) bi.writeLock = false; setWaitCursor(false); throw; } if (!hasCleared) bi.writeLock = false; setWaitCursor(false); } } void gdioutput::dbInput(const string &id, const string &text) { for (list::iterator it = LBI.begin(); it != LBI.end(); ++it) { if (id==it->id) { if (!IsWindowEnabled(it->hWnd) || !it->IsCombo) throw meosException("Selection " + id + " is not active."); SendMessage(it->hWnd, CB_SETCURSEL, -1, 0); SetWindowText(it->hWnd, widen(text).c_str()); it->text = widen(text); it->data = -1; if (it->hasEventHandler()) it->handleEvent(*this, GUI_COMBO); else if (it->callBack) it->callBack(this, GUI_COMBO, &*it); //it may be destroyed here... return; } } for (list::iterator it = II.begin(); it != II.end(); ++it) { if (id == it->id) { if (!IsWindowEnabled(it->hWnd)) throw meosException("Input " + id + " is not active."); it->text = widen(text); SetWindowText(it->hWnd, widen(text).c_str()); if (it->hasEventHandler()) it->handleEvent(*this, GUI_INPUT); else if (it->callBack) it->callBack(this, GUI_INPUT, &*it); return; } } throw meosException("Unknown input " + id + "."); } void gdioutput::dbCheck(const string &id, bool state) { } string gdioutput::dbClick(const string &id, int extra) { for (list::iterator it = TL.begin(); it != TL.end(); ++it) { if (it->id == id && (extra == -65536 || it->getExtraInt() == extra)) { if (it->callBack || it->hasEventHandler()) { string res = toUTF8(it->text); if (!it->handleEvent(*this, GUI_LINK)) it->callBack(this, GUI_LINK, &*it); return res; } else throw meosException("Link " + id + " is not active."); } } throw meosException("Unknown link " + id + "."); } void gdioutput::dbDblClick(const string &id, int data) { for (list::iterator it = LBI.begin(); it != LBI.end(); ++it) { if (id==it->id) { if (!IsWindowEnabled(it->hWnd)) throw meosException("Selection " + id + " is not active."); selectItemByData(id, data); if (it->hasEventHandler()) it->handleEvent(*this, GUI_LISTBOXSELECT); else if (it->callBack) it->callBack(this, GUI_LISTBOXSELECT, &*it); //it may be destroyed here... return; } } throw meosException("Unknown selection " + id + "."); } // Add the next answer for a dialog popup void gdioutput::dbPushDialogAnswer(const string &answer) { cmdAnswers.push_back(answer); } void gdioutput::clearDialogAnswers(bool checkEmpty) { if (!cmdAnswers.empty()) { string front = cmdAnswers.front(); cmdAnswers.clear(); if (checkEmpty) throw meosException("Pending answer: X#" + front); } } int gdioutput::dbGetStringCount(const string &str, bool subString) const { int count = 0; wstring wstr = widen(str); for (list::const_iterator it = TL.begin(); it != TL.end(); ++it) { if (subString == false) { if (it->text == wstr) count++; } else { size_t off = 0; while(off < it->text.size()) { off = it->text.find(wstr, off); if (off != string::npos) { count++; off++; } else break; } } } return count; } void gdioutput::dbRegisterSubCommand(const SubCommand *cmd, const string &action) { if (cmd == 0) subCommands.clear(); else subCommands.push_back(make_pair(cmd, action)); } void gdioutput::runSubCommand() { if (!subCommands.empty()) { auto cmd = subCommands.back(); subCommands.pop_back(); cmd.first->subCommand(cmd.second); } } void gdioutput::getWindowsPosition(RECT &rc) const { WINDOWPLACEMENT wpl; memset(&wpl, 0, sizeof(WINDOWPLACEMENT)); wpl.length = sizeof(WINDOWPLACEMENT); GetWindowPlacement(hWndAppMain, &wpl); rc = wpl.rcNormalPosition; } void gdioutput::setWindowsPosition(const RECT &rc) { WINDOWPLACEMENT wpl; memset(&wpl, 0, sizeof(WINDOWPLACEMENT)); wpl.length = sizeof(WINDOWPLACEMENT); wpl.rcNormalPosition = rc; wpl.showCmd = SW_SHOWNORMAL; SetWindowPlacement(hWndAppMain, &wpl); } void gdioutput::getVirtualScreenSize(RECT &rc) { int px = GetSystemMetrics(SM_CXVIRTUALSCREEN); if (px < 10 || px > 100000) px = GetSystemMetrics(SM_CXSCREEN); int py = GetSystemMetrics(SM_CYVIRTUALSCREEN); if (py < 10 || py > 100000) py = GetSystemMetrics(SM_CYSCREEN); rc.left = 0; rc.right = px; rc.top = 0; rc.bottom = py; } DWORD gdioutput::selectColor(wstring &def, DWORD input) { CHOOSECOLOR cc; memset(&cc, 0, sizeof(cc)); cc.lStructSize = sizeof(cc); cc.hwndOwner = getHWNDMain(); cc.rgbResult = COLORREF(input); if (GDICOLOR(input) != colorDefault) cc.Flags |= CC_RGBINIT; COLORREF staticColor[16]; memset(staticColor, 0, 16 * sizeof(COLORREF)); const wchar_t *end = def.c_str() + def.length(); const wchar_t * pEnd = def.c_str(); int pix = 0; while (pEnd < end && pix < 16) { staticColor[pix++] = wcstol(pEnd, (wchar_t **)&pEnd, 16); } cc.lpCustColors = staticColor; int res = 0; setCommandLock(); try { res = ChooseColor(&cc); liftCommandLock(); } catch (...) { liftCommandLock(); throw; } if (res) { wstring co; for (int ix = 0; ix < 16; ix++) { wchar_t bf[16]; swprintf_s(bf, L"%x ", staticColor[ix]); co += bf; } swap(def,co); return cc.rgbResult; } return -1; } void gdioutput::setAnimationMode(const shared_ptr &data) { if (animationData && animationData->takeOver(data)) return; animationData = data; } AutoCompleteInfo &gdioutput::addAutoComplete(const string &key) { BaseInfo &bi = getBaseInfo(key.c_str()); RECT rc, rcMain; GetWindowRect(bi.getControlWindow(), &rc); GetWindowRect(hWndTarget, &rcMain); POINT pt; int height = scaleLength(200); pt.x = rc.right; //pt.y = min(rc.top, rcMain.bottom-height); pt.y = rc.bottom; if (pt.y + height > rcMain.bottom) pt.y = rc.top - height; ScreenToClient(hWndTarget, &pt); if (pt.y < 0) { //Fallback pt.x = rc.right; pt.y = min(rc.top, rcMain.bottom - height); ScreenToClient(hWndTarget, &pt); } if (autoCompleteInfo && autoCompleteInfo->matchKey(key)) { return *autoCompleteInfo; } autoCompleteInfo.reset(); HWND hWnd = CreateWindowEx(WS_EX_CLIENTEDGE, L"AUTOCOMPLETE", L"", WS_VISIBLE | WS_CHILD | WS_CLIPSIBLINGS| WS_BORDER , pt.x, pt.y, scaleLength(350), height, hWndTarget, NULL, (HINSTANCE)GetWindowLongPtr(hWndTarget, GWLP_HINSTANCE), NULL); autoCompleteInfo.reset(new AutoCompleteInfo(hWnd, key, *this)); //SendMessage(hWnd, WM_SETFONT, (WPARAM)getGUIFont(), 0); return *autoCompleteInfo; } void gdioutput::clearAutoComplete(const string &key) { autoCompleteInfo.reset(); } int gdioutput::getPageY() const { if (hideBG || backgroundColor1 != -1) return max(MaxY, 100); else return max(MaxY, 100) + scaleLength(60); } int gdioutput::getPageX() const { int xlimit = 100; for (auto &b : BI) xlimit = max(b.xp + b.width, xlimit); if (hideBG || backgroundColor1 != -1 || xlimit >= MaxX) return max(MaxX, xlimit); else return max(MaxX, xlimit) + scaleLength(60); } int gdioutput::popupMenu(int x, int y, const vector> &menuItems) const { POINT pt; pt.x = x; pt.y = y; ClientToScreen(getHWNDTarget(), &pt); HMENU hm = CreatePopupMenu(); for (auto &me : menuItems) { if (me.first.empty()) AppendMenu(hm, MF_SEPARATOR, me.second, L""); else AppendMenu(hm, MF_STRING, me.second, lang.tl(me.first).c_str()); } int res = TrackPopupMenuEx(hm, TPM_LEFTALIGN | TPM_TOPALIGN | TPM_RETURNCMD | TPM_NONOTIFY, pt.x, pt.y, getHWNDTarget(), nullptr); DestroyMenu(hm); return res; }