Hi folks,
I'm writing a little application in THINK C, targeting the standard Mac Toolbox API. I have a window with a scroll bar. Usually it scrolls up and down correctly and always correctly if it's a page up/down or slide to any position. However, sometimes if I hit the same arrow button (either up or down) multiple times quickly, it can generate a gap. For example:

Here, I managed to click down twice quickly enough for there to be 2 scroll events before there was a single update event. My code doesn't repaint everything, I'm trying to be clever, so I only repaint the the bits that need updating, see later...
The bug is either in the scroll code or the update code. Here's the scroll code.
I had had an earlier bug where I'd had: ScrollRect(&scrlRect,0,newVal*=-9, ((WindowRecord*)aWindow)->updateRgn); but the update region seems to be in local coordinates; which would cause the right and bottom parts of the window to be erased. To explain a bit further: when the window is scrolled up, the newly exposed part of the window is deemed 'uncovered', so it sets the update region for that area. Then the code would get an Update Event and see that pretty much everything needs to be updated (because at some point the region in local coordinate space was treated as being in global coordinate space, converted to local coordinates; and my Repaint code simply takes its bounding box (which is now way off to the top and left); erases that rect and redraws the remaining lines.
So, I decided to convert the region from local to global coordinates (see the LocalToGlobal call above) and then add that to the update region. It's not good enough to convert the update region itself from local to global, because if you click twice between a repaint then the first region that gets updated, gets converted to global coordinates; then on the second scroll that region gets converted again. So, what you want to do is get the update region for each scroll, convert that and then add that to the existing update region.
And here's the essential bit of the update code:
It must be a really basic mistake, but I can't currently see it. So, any hints (e.g. @David Cook or @joevt or @cheesestraws ) would be welcome! And yes, I
I'm writing a little application in THINK C, targeting the standard Mac Toolbox API. I have a window with a scroll bar. Usually it scrolls up and down correctly and always correctly if it's a page up/down or slide to any position. However, sometimes if I hit the same arrow button (either up or down) multiple times quickly, it can generate a gap. For example:

Here, I managed to click down twice quickly enough for there to be 2 scroll events before there was a single update event. My code doesn't repaint everything, I'm trying to be clever, so I only repaint the the bits that need updating, see later...
The bug is either in the scroll code or the update code. Here's the scroll code.
C:
void MyPaint(WindowPtr aWindow, Rect *aIntersect)
{
Rect rct;
char numStr[3]; // enought for 2 chars.
int16_t from,too,oY,line;
int16_t winWidth=aWindow->portRect.right-kMyScrollBarWidth;
oY=(*gMyInfo->iScroll)->contrlValue;
from=aIntersect->top/kMyFontHeight; // round down.
// too rounds up.
too=(aIntersect->bottom+kMyFontHeight-1)/kMyFontHeight;
if(from>kMyLinesPerPage) {
from=kMyLinesPerPage-1;
}
if(too>kMyLinesPerPage) {
too=kMyLinesPerPage;
}
SetRect(&rct,0,from*9,winWidth, too*9);
EraseRect(&rct);
for(line=from; line<too; line++) {
MoveTo(4,(line+1)*9);
NumToString((int32_t)(line+oY), numStr);
DrawString(numStr);
MoveTo(31,(line+1)*9);
if(line+oY!=gMyInfo->iEditLine) {
DrawString(gMyInfo->iLib[line+oY].iEntry.iData.iName);
}
else {
SetRect(&rct,kMyNameOx,0, winWidth-4,9);
TEUpdate(&rct, gMyInfo->iText);
}
}
}
/**
* @Precondition: GrafPort is already set.
*/
void MyClickControl(WindowPtr aWindow, ControlHandle aControl,
Point *aWhere, int16_t aPart)
{
Rect scrlRect;
Point updateOffset;
RgnHandle tmpRgn;
int16_t newVal,oldVal=(*gMyInfo->iScroll)->contrlValue;
if(TrackControl(aControl, *aWhere, NULL)) { // only buttons.
// The only control is the scroll bar.
newVal=oldVal;
switch(aPart) {
case inUpButton:
--newVal;
break;
case inDownButton:
++newVal;
break;
}
if(newVal<0) {
newVal=0;
}
else if(newVal>(*gMyInfo->iScroll)->contrlMax) {
newVal=(*gMyInfo->iScroll)->contrlMax;
}
SetCtlValue(gMyInfo->iScroll, newVal);
newVal-=oldVal;
if(newVal) {
BlockMove(&aWindow->portRect, &scrlRect, sizeof(Rect));
scrlRect.right-=16; // ignore the scroll bar itself.
scrlRect.bottom=126;
{
tmpRgn=NewRgn();
ScrollRect(&scrlRect,0,newVal*=-9,tmpRgn);
MyOffsetEdit(newVal, aWindow);
updateOffset.h=updateOffset.v=0;
LocalToGlobal(&updateOffset);
OffsetRgn(tmpRgn, updateOffset.h, updateOffset.v);
UnionRgn(tmpRgn, ((WindowRecord*)aWindow)->updateRgn,
((WindowRecord*)aWindow)->updateRgn);
DisposeRgn(tmpRgn);
++gScrolls;
}
}
}
}
/**
* Precondition, the grafport is already set.
*/
tBool MainClick(WindowPtr aWindow, Point *aWhere,
uint16_t aModifiers)
{
ControlHandle control;
Rect nameRect, destRect;
char *str=NULL;
int16_t len, edLine, topLine;
int16_t part=FindControl(*aWhere, aWindow, &control);
if(part) {
MyClickControl(aWindow, control, aWhere, part);
}
return kTrue;
}
I had had an earlier bug where I'd had: ScrollRect(&scrlRect,0,newVal*=-9, ((WindowRecord*)aWindow)->updateRgn); but the update region seems to be in local coordinates; which would cause the right and bottom parts of the window to be erased. To explain a bit further: when the window is scrolled up, the newly exposed part of the window is deemed 'uncovered', so it sets the update region for that area. Then the code would get an Update Event and see that pretty much everything needs to be updated (because at some point the region in local coordinate space was treated as being in global coordinate space, converted to local coordinates; and my Repaint code simply takes its bounding box (which is now way off to the top and left); erases that rect and redraws the remaining lines.
So, I decided to convert the region from local to global coordinates (see the LocalToGlobal call above) and then add that to the update region. It's not good enough to convert the update region itself from local to global, because if you click twice between a repaint then the first region that gets updated, gets converted to global coordinates; then on the second scroll that region gets converted again. So, what you want to do is get the update region for each scroll, convert that and then add that to the existing update region.
And here's the essential bit of the update code:
C:
void MyClick(WindowPtr aWindow, Point *aWhere, uint16_t aModifiers)
{
int16_t h,v;
Rect keyPadRect;
GrafPtr oldPort;
if(aWindow!=FrontWindow()) {
SelectWindow(aWindow);
}
GetPort(&oldPort);
SetPort(aWindow);
GlobalToLocal(aWhere);
MainWinClick(aWindow, &gTheEvent.where, gTheEvent.modifiers);
SetPort(oldPort);
}
void DoMouseDown(void)
{
WindowPtr whichWindow;
int16_t thePart;
int32_t windSize, menuChoice;
GrafPtr oldPort;
thePart=FindWindow(gTheEvent.where, &whichWindow);
switch(thePart) { //<snip> other parts.
case inContent:
MyClick(whichWindow, &gTheEvent.where, gTheEvent.modifiers);
break;
} // <snip> other parts.
}
void RepaintAll(WindowPtr aWindow)
{
uint16_t row, col;
Rect dataRect, intersect;
char *pad=(char*)gPageAahKeyPad;
GrafPtr oldPort;
GetPort(&oldPort);
SetPort(aWindow);
SetRect(&dataRect,kOriginX, kOriginY, kOriginX+100, kOriginY+12);
// Check rgnBBox instead and do SectRect.
BlockMove(&((*aWindow->visRgn)->rgnBBox), &intersect, sizeof(Rect));
MyPaint(aWindow, &intersect);
DrawControls(aWindow);
SetPort(oldPort);
}
void DoUpdate(WindowPtr aWindow)
{
Rect myRect, drawingClipRect;
GrafPtr oldPort;
BeginUpdate(aWindow);
RepaintAll(aWindow);
EndUpdate(aWindow);
}
tBool DoEvent(void)
{
tBool gotOne=kFalse;
gotOne=WaitNextEvent(everyEvent, &gTheEvent, kSleep, NULL);
if(gotOne || gotOne==0 && gTheEvent.what==nullEvent) {
switch(gTheEvent.what) { // Snip other events.
case updateEvt:
DoUpdate((WindowPtr)gTheEvent.message);
break;
}
}
}
main()
{
ToolboxInit();
if(Init()) {
while(!gDone) {
DoEvent();
}
SoundEndIf();
}
}
It must be a really basic mistake, but I can't currently see it. So, any hints (e.g. @David Cook or @joevt or @cheesestraws ) would be welcome! And yes, I


