326 lines
23 KiB
C++
326 lines
23 KiB
C++
// Exercise the actual editor controls without capturing the desktop or changing the clipboard.
|
|
// Including the implementation lets this integration test drive private editor state directly.
|
|
#include "../src/screenshot.cpp"
|
|
#include <iostream>
|
|
int main()
|
|
{
|
|
CoInitializeEx(nullptr,COINIT_APARTMENTTHREADED);
|
|
try {
|
|
INITCOMMONCONTROLSEX controls{sizeof(controls),ICC_STANDARD_CLASSES}; InitCommonControlsEx(&controls);
|
|
ks::ScreenshotHost app{GetModuleHandleW(nullptr)};
|
|
app.window=CreateWindowW(L"STATIC",L"",0,0,0,0,0,HWND_MESSAGE,nullptr,app.instance,nullptr);
|
|
ks::Capture c(app);
|
|
c.image=std::make_unique<ks::Surface>(320,240);
|
|
PatBlt(c.image->dc,0,0,320,240,WHITENESS); c.start={0,0}; c.end={320,240};
|
|
WNDCLASSW cls{}; cls.hInstance=app.instance; cls.lpfnWndProc=ks::captureProc;
|
|
cls.lpszClassName=L"KSwitcher.Editor.Integration"; RegisterClassW(&cls);
|
|
auto window=CreateWindowW(cls.lpszClassName,L"Editor test",WS_POPUP,0,0,320,240,app.window,nullptr,app.instance,&c);
|
|
auto require=[](bool ok,const char *message){ if(!ok) throw std::runtime_error(message); };
|
|
require(window!=nullptr,"editor creation");
|
|
ks::edit(window,c); ShowWindow(window,SW_HIDE);
|
|
require(GetWindow(window,GW_OWNER)==nullptr && (GetWindowLongPtrW(window,GWL_EXSTYLE)&WS_EX_APPWINDOW),
|
|
"editor is an independent Alt+Tab application window");
|
|
require(GetDlgItem(window,50)!=nullptr && c.statusHeight>0,"status bar is created");
|
|
require(c.pencilCursor!=nullptr,"pencil drawing cursor loads");
|
|
for(int size:{32,48,64,96}) {
|
|
auto cursor=(HCURSOR)LoadImageW(app.instance,MAKEINTRESOURCEW(IDC_SCREENSHOT_PENCIL),IMAGE_CURSOR,size,size,0);
|
|
ICONINFO info{};
|
|
require(cursor && GetIconInfo(cursor,&info) && !info.fIcon,"pencil cursor supports standard and high DPI");
|
|
require(info.xHotspot<UINT(size/3) && info.yHotspot>UINT(size*2/3),"pencil cursor draws from its tip");
|
|
if(info.hbmMask) DeleteObject(info.hbmMask);
|
|
if(info.hbmColor) DeleteObject(info.hbmColor);
|
|
DestroyCursor(cursor);
|
|
}
|
|
wchar_t windowTitle[128]{}; GetWindowTextW(window,windowTitle,128);
|
|
require(std::wstring(windowTitle)==L"KSwitcher — Screenshot #1","title includes editor index without usage hints");
|
|
RECT client{}; GetClientRect(window,&client);
|
|
require(c.view.bottom<=client.bottom-c.statusHeight,"image does not overlap status bar");
|
|
ks::updateStatus(c);
|
|
require(c.statusText.find(L"Pointer")!=std::wstring::npos,"default tool hint is shown");
|
|
SendMessageW(GetDlgItem(window,13),WM_MOUSEMOVE,0,0);
|
|
require(c.statusText.find(L"Ctrl+C")!=std::wstring::npos,"button hover shows action shortcut");
|
|
SendMessageW(GetDlgItem(window,13),WM_MOUSELEAVE,0,0);
|
|
require(c.statusText.find(L"Pointer")!=std::wstring::npos,"button leave restores contextual hint");
|
|
for(int id:ks::ToolIds) require(GetDlgItem(window,id)!=nullptr,"all toolbar buttons created");
|
|
require(GetDlgItem(window,34)!=nullptr && c.toolIcons[0][14]!=nullptr,"font icon created and loaded");
|
|
ks::Mark text{{20,20},{20,20},0}; text.fontSize=18;
|
|
ks::beginInlineText(window,c,text,-1);
|
|
require(c.inlineText!=nullptr,"inline text control opens");
|
|
ks::updateStatus(c);
|
|
require(c.statusText.find(L"Ctrl+Enter")!=std::wstring::npos,"inline editing hint shows commit and cancel keys");
|
|
SendMessageW(c.inlineText,EM_REPLACESEL,TRUE,(LPARAM)L"First line\r\nДругий рядок");
|
|
require(c.textDraft.text==L"First line\r\nДругий рядок","inline text changes update draft");
|
|
SendMessageW(window,ks::FinishTextMessage,1,(LPARAM)c.inlineText);
|
|
require(!c.inlineText && c.marks.size()==1 && c.marks[0].text==text.text+L"First line\r\nДругий рядок","commit adds multiline text");
|
|
ks::beginInlineText(window,c,c.marks[0],0);
|
|
SendMessageW(c.inlineText,EM_REPLACESEL,TRUE,(LPARAM)L"Cancelled");
|
|
SendMessageW(window,ks::FinishTextMessage,2,(LPARAM)c.inlineText);
|
|
require(c.marks[0].text!=L"Cancelled","Escape cancels changes");
|
|
SendMessageW(window,WM_KEYDOWN,VK_F2,0);
|
|
require(c.inlineText!=nullptr,"F2 edits inline");
|
|
SendMessageW(c.inlineText,EM_REPLACESEL,TRUE,(LPARAM)L"Edited"); ks::finishInlineText(window,c,true);
|
|
require(c.marks[0].text==L"Edited","existing text edit commits");
|
|
SendMessageW(window,WM_COMMAND,12,0);
|
|
require(c.marks[0].text!=L"Edited","undo restores original text");
|
|
SendMessageW(window,WM_COMMAND,18,0);
|
|
require(c.marks[0].text==L"Edited","redo restores text edit");
|
|
ks::beginInlineText(window,c,c.marks[0],0);
|
|
SendMessageW(c.inlineText,EM_REPLACESEL,TRUE,(LPARAM)L""); ks::finishInlineText(window,c,true);
|
|
require(c.marks.empty(),"committing empty text deletes object");
|
|
c.marks.push_back({{30,150},{260,150},0}); c.selected=0;
|
|
SendMessageW(window,WM_COMMAND,35,0);
|
|
require(c.inlineText && c.editingLabel,"label button opens inline editor");
|
|
SendMessageW(c.inlineText,EM_REPLACESEL,TRUE,(LPARAM)L"Arrow caption");
|
|
ks::finishInlineText(window,c,true);
|
|
require(c.marks.size()==1 && c.marks[0].label==L"Arrow caption" && c.marks[0].isLine(),"label attaches to arrow");
|
|
SendMessageW(window,WM_KEYDOWN,VK_F2,0);
|
|
require(c.inlineText && c.editingLabel,"F2 edits arrow caption");
|
|
SendMessageW(c.inlineText,EM_REPLACESEL,TRUE,(LPARAM)L""); ks::finishInlineText(window,c,true);
|
|
require(c.marks.size()==1 && c.marks[0].label.empty(),"empty label removes caption without deleting arrow");
|
|
SendMessageW(window,WM_COMMAND,12,0);
|
|
require(c.marks[0].label==L"Arrow caption","undo restores arrow caption");
|
|
auto caption=ks::screenshotArrowLabel(c.marks[0]);
|
|
auto captionGrip=ks::screenshotTextRotationHandle(caption,double(c.unit*0.6)*c.image->width/(c.view.right-c.view.left));
|
|
c.selected=0;
|
|
require(ks::markAt(c,captionGrip).second==ks::MarkPart::Rotation,"caption rotation handle is interactive");
|
|
c.marks[0]=ks::dragScreenshotMark(c.marks[0],ks::MarkPart::Rotation,captionGrip,{captionGrip.x+40,captionGrip.y+20},320,240,true);
|
|
auto angle=c.marks[0].rotation;
|
|
SendMessageW(window,WM_KEYDOWN,VK_F2,0);
|
|
SendMessageW(c.inlineText,EM_REPLACESEL,TRUE,(LPARAM)L"Rotated caption"); ks::finishInlineText(window,c,true);
|
|
require(c.marks[0].rotation==angle && c.marks[0].label==L"Rotated caption","editing caption preserves its rotation");
|
|
auto rotating=text; rotating.text=L"Rotate"; c.marks.push_back(rotating); c.selected=1;
|
|
auto handle=ks::screenshotTextRotationHandle(rotating,double(c.unit*0.6)*c.image->width/(c.view.right-c.view.left));
|
|
require(ks::markAt(c,handle).second==ks::MarkPart::Rotation,"rotation handle is interactive");
|
|
c.hoveredPart=ks::MarkPart::Rotation; ks::updateStatus(c);
|
|
require(c.statusText.find(L"15-degree")!=std::wstring::npos,"rotation hint explains Shift snapping");
|
|
c.hoveredPart=ks::MarkPart::None;
|
|
// Drag the two badges in turn, including a click near the attached endpoint.
|
|
for(bool freehand : {false,true}) for(auto ends : {ks::LineEnds::End,ks::LineEnds::None}) {
|
|
c.marks={{{60,80},{60,80},1},{{230,80},{230,80},2},{{60,80},{230,80},0}};
|
|
auto &connector=c.marks[2]; connector.fromNumber=1; connector.toNumber=2;
|
|
connector.ends=ends; connector.freehand=freehand;
|
|
if(freehand) connector.points={{60,80},{140,80},{230,80}};
|
|
ks::resolveScreenshotLinks(c.marks); c.selected=-1; c.selectedLabel=false;
|
|
auto mouse=[&](UINT message,POINT p) {
|
|
LONG x=c.view.left+MulDiv(p.x,c.view.right-c.view.left,c.image->width);
|
|
LONG y=c.view.top+MulDiv(p.y,c.view.bottom-c.view.top,c.image->height);
|
|
SendMessageW(window,message,message==WM_LBUTTONUP?0:MK_LBUTTON,MAKELPARAM(x,y));
|
|
};
|
|
for(int badge : {0,1,0,1}) {
|
|
auto origin=c.marks[badge].from;
|
|
// The second badge's inner edge overlaps the connector endpoint hit area.
|
|
if(badge==1) origin.x-=12;
|
|
mouse(WM_LBUTTONDOWN,origin);
|
|
require(c.selected==badge && c.part==ks::MarkPart::Body,"click on linked badge selects badge instead of connector endpoint");
|
|
mouse(WM_MOUSEMOVE,{origin.x,origin.y+20});
|
|
mouse(WM_LBUTTONUP,{origin.x,origin.y+20});
|
|
require(c.marks[2].fromNumber==1 && c.marks[2].toNumber==2,"alternating badge drags preserve both connector links");
|
|
auto start=c.marks[0].from,end=c.marks[1].from;
|
|
require(std::abs(std::hypot(double(c.marks[2].from.x-start.x),double(c.marks[2].from.y-start.y))-15)<1 &&
|
|
std::abs(std::hypot(double(c.marks[2].to.x-end.x),double(c.marks[2].to.y-end.y))-15)<1,
|
|
"connector endpoints follow both badge rims");
|
|
}
|
|
// Selecting a linked connector (also the first click of a double-click)
|
|
// must not detach it before opening its caption editor.
|
|
for(int entry : {0,1,2}) {
|
|
c.selected=-1;
|
|
POINT body{110,c.marks[0].from.y};
|
|
if(freehand) body={(c.marks[2].points[0].x+c.marks[2].points[1].x)/2,
|
|
(c.marks[2].points[0].y+c.marks[2].points[1].y)/2};
|
|
mouse(WM_LBUTTONDOWN,body); mouse(WM_LBUTTONUP,body);
|
|
require(c.selected==2 && c.marks[2].fromNumber==1 && c.marks[2].toNumber==2,
|
|
"clicking a connector without moving preserves both links");
|
|
if(entry==0) { mouse(WM_LBUTTONDBLCLK,body); mouse(WM_LBUTTONUP,body); }
|
|
else if(entry==1) SendMessageW(window,WM_KEYDOWN,VK_F2,0);
|
|
else SendMessageW(window,WM_COMMAND,35,0);
|
|
require(c.inlineText && c.editingLabel,"linked connector caption opens through every entry point");
|
|
SendMessageW(c.inlineText,EM_REPLACESEL,TRUE,(LPARAM)L"Linked caption");
|
|
ks::finishInlineText(window,c,true);
|
|
require(c.marks[2].label==L"Linked caption" && c.marks[2].fromNumber==1 && c.marks[2].toNumber==2,
|
|
"adding or editing a caption preserves both connector links");
|
|
}
|
|
c.selected=2;
|
|
require(ks::markAt(c,c.marks[1].from).first==1,"selected connector cannot steal a badge click");
|
|
auto outside=c.marks[2].to; outside.x-=4;
|
|
require(ks::markAt(c,outside)==std::pair{2,ks::MarkPart::End},"endpoint remains draggable outside badge");
|
|
}
|
|
{
|
|
auto mouse=[&](UINT message,POINT p) {
|
|
LONG x=c.view.left+MulDiv(p.x,c.view.right-c.view.left,c.image->width);
|
|
LONG y=c.view.top+MulDiv(p.y,c.view.bottom-c.view.top,c.image->height);
|
|
SendMessageW(window,message,message==WM_LBUTTONUP?0:MK_LBUTTON,MAKELPARAM(x,y));
|
|
};
|
|
SendMessageW(window,WM_COMMAND,27,0); c.marks.clear();
|
|
mouse(WM_LBUTTONDOWN,{40,160}); mouse(WM_MOUSEMOVE,{100,190}); mouse(WM_LBUTTONUP,{100,190});
|
|
require(c.marks.empty() && !c.dragging && c.selected==-1,"pointer drag on empty canvas never adds an object");
|
|
for(int tool : {11,22}) for(int target : {0,1,2}) {
|
|
if(target==0) c.marks={{{55,160},{55,160},1},{{255,160},{255,160},2}};
|
|
else {
|
|
c.marks={{{30,140},{80,190},0},{{230,140},{280,190},0}};
|
|
if(target==1) for(auto &mark:c.marks) mark.shape=ks::ScreenshotShape::Rectangle;
|
|
else for(auto &mark:c.marks) mark.from.y=mark.to.y=160;
|
|
}
|
|
SendMessageW(window,WM_COMMAND,tool,0);
|
|
BYTE savedKeys[256]{},drawingKeys[256]{};
|
|
require(GetKeyboardState(savedKeys)!=FALSE,"read thread keyboard state");
|
|
std::copy(savedKeys,savedKeys+256,drawingKeys); drawingKeys[VK_CONTROL]=0x80;
|
|
require(SetKeyboardState(drawingKeys)!=FALSE,"set thread-local Ctrl for drawing test");
|
|
mouse(WM_LBUTTONDOWN,{55,160}); mouse(WM_MOUSEMOVE,{255,160}); mouse(WM_LBUTTONUP,{255,160});
|
|
SetKeyboardState(savedKeys);
|
|
require(c.marks.size()==3,"drawing over existing objects creates a connector with Ctrl");
|
|
auto &connector=c.marks[2];
|
|
require(target==0?(connector.fromNumber==1 && connector.toNumber==2):
|
|
(connector.fromLink.target==c.marks[0].id && connector.toLink.target==c.marks[1].id),
|
|
"new arrow or line automatically attaches both endpoints to targets");
|
|
require(connector.ends==(tool==11?c.arrowEnds:ks::LineEnds::None),"arrow and straight-line tools retain their end styles");
|
|
auto before=connector.from;
|
|
SendMessageW(window,WM_KEYDOWN,'V',0);
|
|
require(c.pointer && !c.arrow && !c.freehand,"V selects pointer mode");
|
|
POINT body=target==0?POINT{55,160}:target==1?POINT{30,175}:POINT{30,160};
|
|
mouse(WM_LBUTTONDOWN,body); mouse(WM_MOUSEMOVE,{body.x,body.y-20}); mouse(WM_LBUTTONUP,{body.x,body.y-20});
|
|
require(c.selected==0 && c.marks.size()==3 && c.marks[2].from.y<before.y,
|
|
"pointer moves the target and the new connector follows without adding objects");
|
|
mouse(WM_LBUTTONDOWN,{150,220}); mouse(WM_LBUTTONUP,{150,220});
|
|
require(c.selected==-1 && c.marks.size()==3,"pointer click on empty space only clears selection");
|
|
}
|
|
}
|
|
for(bool manualAngle : {false,true}) {
|
|
c.marks={{{40,140},{180,60},0}}; c.selected=0;
|
|
c.marks[0].fontSize=18; c.marks[0].fontWeight=FW_BOLD;
|
|
c.marks[0].rotation=31; c.marks[0].labelRotationManual=manualAngle;
|
|
SendMessageW(window,WM_COMMAND,35,0);
|
|
SendMessageW(c.inlineText,EM_REPLACESEL,TRUE,(LPARAM)L"Sloped caption");
|
|
ks::finishInlineText(window,c,true);
|
|
auto captionAngle=ks::screenshotArrowLabel(c.marks[0]).rotation;
|
|
require(captionAngle!=0,"caption retains its automatic or manual angle");
|
|
SendMessageW(window,WM_COMMAND,26,0);
|
|
LONG x=c.view.left+MulDiv(220,c.view.right-c.view.left,c.image->width);
|
|
LONG y=c.view.top+MulDiv(190,c.view.bottom-c.view.top,c.image->height);
|
|
SendMessageW(window,WM_LBUTTONDOWN,MK_LBUTTON,MAKELPARAM(x,y));
|
|
require(c.inlineText && !c.editingLabel,"standalone text starts independently of caption");
|
|
SendMessageW(c.inlineText,EM_REPLACESEL,TRUE,(LPARAM)L"Independent text");
|
|
ks::finishInlineText(window,c,true);
|
|
require(c.marks.size()==2 && c.marks[1].isText() && c.marks[1].rotation==0,
|
|
"new standalone text does not inherit caption rotation");
|
|
require(c.marks[1].fontSize==18 && c.marks[1].fontWeight==FW_BOLD,
|
|
"new text still inherits font styling");
|
|
require(ks::screenshotArrowLabel(c.marks[0]).rotation==captionAngle,"new text does not change caption angle");
|
|
c.marks[1].rotation=22; c.selected=1;
|
|
SendMessageW(window,WM_KEYDOWN,VK_F2,0);
|
|
SendMessageW(c.inlineText,EM_REPLACESEL,TRUE,(LPARAM)L"Edited standalone text");
|
|
ks::finishInlineText(window,c,true);
|
|
require(c.marks[1].rotation==22,"editing existing rotated text preserves its own angle");
|
|
SendMessageW(window,WM_COMMAND,27,0);
|
|
}
|
|
std::vector<ks::Mark> nudgeMarks;
|
|
nudgeMarks.push_back({{60,60},{60,60},1});
|
|
nudgeMarks.push_back({{60,60},{120,100},0});
|
|
auto nudgeText=text; nudgeText.text=L"Move"; nudgeText.from=nudgeText.to={60,60}; nudgeMarks.push_back(nudgeText);
|
|
for(auto shape:{ks::ScreenshotShape::Rectangle,ks::ScreenshotShape::Circle,ks::ScreenshotShape::Ellipse}) {
|
|
ks::Mark mark{{60,60},{100,100},0}; mark.shape=shape; nudgeMarks.push_back(mark);
|
|
}
|
|
for(bool freehand:{false,true}) {
|
|
ks::Mark mark{{60,60},{120,100},0}; mark.points={{60,60},{90,80},{120,100}}; mark.freehand=freehand;
|
|
nudgeMarks.push_back(mark);
|
|
}
|
|
for(auto mark:nudgeMarks) {
|
|
c.marks={mark}; c.selected=0; c.selectedLabel=false;
|
|
SendMessageW(window,WM_KEYDOWN,VK_RIGHT,0); SendMessageW(window,WM_KEYDOWN,VK_DOWN,0);
|
|
require(c.marks[0].from.x==61 && c.marks[0].from.y==61,"every object nudges one pixel per key");
|
|
if(!mark.points.empty()) require(c.marks[0].points[1].x==91 && c.marks[0].points[1].y==81,"nudge moves entire stroke");
|
|
SendMessageW(window,WM_KEYDOWN,VK_LEFT,0); SendMessageW(window,WM_KEYDOWN,VK_UP,0);
|
|
require(c.marks[0].from.x==60 && c.marks[0].from.y==60,"opposite keys restore original position");
|
|
}
|
|
ks::Mark nudgeArrow{{60,60},{140,140},0}; nudgeArrow.label=L"Caption"; nudgeArrow.fontSize=12;
|
|
c.marks={nudgeArrow}; c.selected=0; c.selectedLabel=true;
|
|
auto beforeNudge=ks::screenshotTextBounds(ks::screenshotArrowLabel(c.marks[0]));
|
|
for(int i=0;i<5;++i) SendMessageW(window,WM_KEYDOWN,VK_RIGHT,0);
|
|
auto afterNudge=ks::screenshotTextBounds(ks::screenshotArrowLabel(c.marks[0]));
|
|
require(afterNudge.left==beforeNudge.left+5 && afterNudge.top==beforeNudge.top,
|
|
"caption nudges exact screen pixels at a diagonal angle");
|
|
require(c.marks[0].from.x==60 && !c.marks[0].labelRotationManual,"caption nudge leaves arrow and automatic angle intact");
|
|
SendMessageW(window,WM_COMMAND,12,0);
|
|
require(ks::screenshotTextBounds(ks::screenshotArrowLabel(c.marks[0])).left==beforeNudge.left+4,"undo reverses one nudge");
|
|
SendMessageW(window,WM_COMMAND,18,0);
|
|
require(ks::screenshotTextBounds(ks::screenshotArrowLabel(c.marks[0])).left==beforeNudge.left+5,"redo restores one nudge");
|
|
SetWindowPos(window,nullptr,80,60,720,500,SWP_NOZORDER|SWP_SHOWWINDOW);
|
|
ks::copy(window,c,[](HWND,const ks::Clip &clip)->DWORD {
|
|
if(clip.formats.empty()) throw std::runtime_error("copy produced no image");
|
|
return 0;
|
|
});
|
|
require(IsWindow(window) && c.copied,"copy publishes screenshot and keeps editor open");
|
|
c.selected=0; c.selectedLabel=false;
|
|
SendMessageW(window,WM_KEYDOWN,VK_RIGHT,0);
|
|
require(!c.copied,"editing after copy requires confirmation again");
|
|
c.copied=true; SendMessageW(window,WM_COMMAND,12,0);
|
|
require(!c.copied,"undo after copy requires confirmation again");
|
|
c.copied=true; SendMessageW(window,WM_COMMAND,18,0);
|
|
require(!c.copied,"redo after copy requires confirmation again");
|
|
SetPropW(window,L"ConfirmationCentered",nullptr);
|
|
SetTimer(window,987,10,[](HWND owner,UINT,UINT_PTR id,DWORD) {
|
|
EnumThreadWindows(GetCurrentThreadId(),[](HWND popup,LPARAM parent)->BOOL {
|
|
wchar_t className[64]{}; GetClassNameW(popup,className,64);
|
|
if(GetWindow(popup,GW_OWNER)==(HWND)parent && std::wstring(className)==L"#32770") {
|
|
RECT ownerRect{},popupRect{};
|
|
GetWindowRect((HWND)parent,&ownerRect); GetWindowRect(popup,&popupRect);
|
|
if(std::abs(ownerRect.left+ownerRect.right-popupRect.left-popupRect.right)<=4 &&
|
|
std::abs(ownerRect.top+ownerRect.bottom-popupRect.top-popupRect.bottom)<=4)
|
|
SetPropW((HWND)parent,L"ConfirmationCentered",(HANDLE)1);
|
|
PostMessageW(popup,TDM_CLICK_BUTTON,IDNO,0);
|
|
}
|
|
return TRUE;
|
|
},(LPARAM)owner);
|
|
KillTimer(owner,id);
|
|
});
|
|
SendMessageW(window,WM_CLOSE,0,0);
|
|
require(IsWindow(window),"declining close confirmation keeps the screenshot open");
|
|
require(GetPropW(window,L"ConfirmationCentered")!=nullptr,"confirmation is centered on editor instead of screen");
|
|
RemovePropW(window,L"ConfirmationCentered");
|
|
c.copied=true;
|
|
SendMessageW(window,WM_KEYDOWN,VK_ESCAPE,0);
|
|
require(IsWindow(window),"Escape does not close the screenshot editor");
|
|
SendMessageW(window,WM_CLOSE,0,0);
|
|
require(!IsWindow(window),"copied screenshot closes without confirmation");
|
|
static bool published=false;
|
|
static bool notificationAccepted=false;
|
|
static HWND notificationWindow{};
|
|
static bool expectHidden=false;
|
|
app.notify=[](const ks::Clip &) {
|
|
if(!published) throw std::runtime_error("notification preceded clipboard publication");
|
|
if(expectHidden && IsWindowVisible(notificationWindow))
|
|
throw std::runtime_error("selection overlay is still visible when notification is sent");
|
|
return notificationAccepted;
|
|
};
|
|
for(bool autoOpen : {true,false}) for (bool accepted : {true, false}) {
|
|
app.autoOpenEditor=autoOpen;
|
|
published=false;
|
|
notificationAccepted=accepted;
|
|
ks::Capture selection(app);
|
|
selection.image=std::make_unique<ks::Surface>(100,100);
|
|
PatBlt(selection.image->dc,0,0,100,100,WHITENESS);
|
|
selection.start={10,20}; selection.end={70,60};
|
|
auto overlay=CreateWindowW(cls.lpszClassName,L"Selection test",WS_POPUP,0,0,100,100,app.window,nullptr,app.instance,&selection);
|
|
notificationWindow=overlay; expectHidden=!autoOpen;
|
|
ShowWindow(overlay,SW_SHOWNOACTIVATE);
|
|
ks::finishSelection(overlay,selection,[](HWND,const ks::Clip &clip)->DWORD {
|
|
auto pixels=ks::decodeImage(clip);
|
|
if(pixels.width!=60 || pixels.height!=40) throw std::runtime_error("automatic copy did not crop selection");
|
|
published=true; return 0;
|
|
});
|
|
if(!autoOpen && accepted) {
|
|
require(published && !IsWindow(overlay),"notification-first closes selection after successful submission");
|
|
continue;
|
|
}
|
|
require(published && selection.copied && IsWindowVisible(overlay) && selection.editing,
|
|
"selection opens editor whether notification is accepted or rejected");
|
|
require(selection.image->width==60 && selection.image->height==40,
|
|
"automatically opened editor contains the selected region");
|
|
SendMessageW(overlay,WM_CLOSE,0,0);
|
|
require(!IsWindow(overlay),"automatically copied screenshot closes without confirmation");
|
|
}
|
|
DestroyWindow(app.window); app.window=nullptr;
|
|
CoUninitialize();
|
|
std::cout<<"Passed screenshot editor UI integration checks\n";
|
|
} catch(const std::exception &e) { std::cerr<<e.what()<<'\n'; return 1; }
|
|
}
|