[comp.windows.x] graphicsexpose handling

jef@well.sf.ca.us (Jef Poskanzer) (06/19/91)

In writing scrollbars for a minimal toolkit I have come across a little
puzzler.  The usual event sequence for a scroll is: the user clicks,
the window's contents get scrolled, a graphicsexpose gets generated for
the newly visible portion, and the gexpose gets handled.  Now let's say
the user clicks a scroll, and then while the resulting gexpose is being
handled the user clicks again.  Now the event sequence is click,
gexpose, click, gexpose.

But what happens if the user clicks three times in rapid succession?
It goes click, gexpose, click, click, gexpose, gexpose - and the second
gexpose is handled *incorrectly*, since the third click has already
modified the window's scrolling position.

I thought at first I could solve this by doing an XCheckMaskEvent
before my XNextEvent and always handling any queued expose events
before any other kind.  And this does indeed work correctly in the
debugger.  But in realtime there is a race and it has the same bug as
before, the second gexpose gets handled after the third click.

I also tried avoiding gexposes by doing the necessary repaints directly
while handling the click event.  This works perfectly until you have
another window partially overlapping the scrolling window.  Then the
server generates gexposes anyway.  Not much I can do about that.

Does anyone know what I'm talking about?  I looked inside Xt and it's
doing something weird with expose events, but I'm not sure whether it's
to solve this problem or just an optimization or something else.
---
Jef

  Jef Poskanzer  jef@well.sf.ca.us  {apple, ucbvax, hplabs}!well!jef
           "...and now for something completely different."

bobt@boa.mitron.tek.COM (Bob Toole) (06/20/91)

> It goes click, gexpose, click, click, gexpose, gexpose - and the second
> gexpose is handled *incorrectly*, ...

Sorry about the post, my direct mail to you bounced.

The solution to this at the Xlib level is after each XCopyPlane,
wait until you get a GraphicsExpose or a NoExpose event.
Better yet, don't do consecutive XCopyPlane calls unless
you have had a GraphicsExpose event to repair damage or
a NoExpose event to let you know there is no damage.

This assumes the GC graphics-exposure flag is true.

I am not sure what your toolkit is trying to do,
but if you have source, you should be able to fix it.  Somehow.

bobt@boa.mitron.tek.COM (Bob Toole) (06/20/91)

Oops.  In my last posting I said XCopyPlane.
I meant XCopyArea.

Sorry.

asente@adobe.com (Paul Asente) (06/21/91)

In article <25529@well.sf.ca.us> jef@well.sf.ca.us (Jef Poskanzer) writes:

	[about problems with GraphicsExpose events]

Handling GraphicsExpose events correctly is a little complex.  When you do a
CopyArea, put the change that you made into a list.  When you get a
GraphicsExpose, look at the entire list to figure out where the area you're
exposing has been moved to.  When you get a GraphicsExpose with count 0 or a
NoExpose, you can remove the oldest list entry.

Example:
	Do a CopyArea that moves everything up 10; put (10, 0) in the list.
	Do a CopyArea that moves everything up 20; add (20, 0) to the list.
	Do a CopyArea that moves everything 30 to the right; add (0, 30) to
		the list.
	Get a GraphicsExpose with count 0 indicating that bottom 10 pixels
		of your window need redisplay.  Skip the first list entry,
		add the remaining ones to discover that you need to offset the
		area in the expose event by (20, 30) to get its current
		position.   Remove the (10, 0) from the list since you got an
		event with count 0.
	Etc.

>Does anyone know what I'm talking about?  I looked inside Xt and it's
>doing something weird with expose events, but I'm not sure whether it's
>to solve this problem or just an optimization or something else.

You have 2 options with Xt.  One is not to specify XtExposeGraphicsExpose in
your compress_exposure field and to specify translations for GraphicsExpose
and NoExpose in your translations.  This means your action procedures will
get all the GraphicsExpose and NoExpose events and can handle them as above.
You might want to use XtAddExposureToRegion to do your own exposure
compression.

The other option is to specify XtExposeGraphicsExpose in the
compress_exposure field.  In this case GraphicsExpose events will be
dispatched to your expose procedure.  If you want exposure compression, use
XtExposeCompressSeries; using XtExposeCompressMultiple or
XtExposeCompressMaximal will prevent you from seeing all the count=0 events
and from keeping your list up to date.  Also, do not use
XtExposeGraphicsExposeMerged since that will merge GraphicsExpose and regular
Expose events, again preventing you from seeing all count=0 events.  You
probably also want to specify XtExposeNoExpose so that NoExpose events also
go to your expose procedure and you can handle all the list updating in one
place.  If you don't specify XtExposeNoExpose you need a translation for
NoExpose as above.

	-paul asente
		asente@adobe.com	...decwrl!adobe!asente

    Ratz put a bucket of liquid in front of me.
   "I wanted a glass of docs, Ratz.  What the hell is this?" I barked.
   "Motif don't fit in a glass anymore," he barked back.
    I looked at the liquid.  It was totally opaque to me.

jef@well.sf.ca.us (Jef Poskanzer) (06/21/91)

In the referenced message, jef@well.sf.ca.us (Jef Poskanzer) wrote:
}I thought at first I could solve this by doing an XCheckMaskEvent
}before my XNextEvent and always handling any queued expose events
}before any other kind.  And this does indeed work correctly in the
}debugger.  But in realtime there is a race and it has the same bug as
}before, the second gexpose gets handled after the third click.

I ended up solving the problem by doing this plus having the clickproc
do an XSync after the XCopyArea.  This forces the just-generated gexpose
into the input queue, where it can be handled right away.  And it only
slows things down if the user does a whole bunch of scrolls, which doesn't
happen too often.

I still wonder if there's a cleaner solution.
---
Jef

  Jef Poskanzer  jef@well.sf.ca.us  {apple, ucbvax, hplabs}!well!jef
                     WCBG: All Elvis, All The Time

pete@iris49.biosym.COM (Pete Ware) (06/21/91)

To deal with it correctly, you need to keep track of all you scrolling
actions until a gexpose (or noexpose) event arrives to indicate it is
finished.  chuck@fid.Morgan.com (Chuck Ocheret) recently posted
something called "Panhandler" to the motif mailling list that deals
with this problem.  To quote from his description:

    This problem is the result of the asynchronous nature of X.  If you
    have an occluding window over the rectangle you are trying to pan, the
    your XCopyArea() calls can result in GraphicsExpose events since some
    areas couldn't get copied.  However, you might not see those
    GraphicsExpose events until after you have done a bunch of pans.

    When the GraphicsExpose events eventually get to your client, you have
    already scrolled the damaged areas to new locations, so that the x, y
    members of the GraphicsExpose events need to be adjusted.

    This is what the PanHandler fixes for you.  It maintains a queue of
    your pan requests and keeps it up to date with respect to incoming
    Expose, GraphicsExpose and NoExpose events.  It adjusts the Expose and
    GraphicsExpose events accordingly so that you repair the right
    portions of your window.

I haven't actually used the code so you on your own.

--pete
Pete Ware / Biosym / San Diego CA / (619) 546-5532
     email: pete@biosym.com

mouse@lightning.mcrcim.mcgill.EDU (der Mouse) (06/22/91)

>> I thought at first I could solve this by doing an XCheckMaskEvent
>> before my XNextEvent and always handling any queued expose events
>> before any other kind.  And this does indeed work correctly in the
>> debugger.  But in realtime there is a race and it has the same bug
>> as before, the second gexpose gets handled after the third click.

mterm has this same race when scrolling.  The README file says

- There's a race which will occasionally cause a damaged area of the
  window to not be repainted.  The race occurs when an exposure of some
  sort happens and mterm scrolls at about the same time, such that the
  server sees the CopyArea for the scroll after the exposure, but mterm
  doesn't see the exposure event until after the scroll.  This should
  be fixable, but it looks like a good deal of work, and it doesn't
  happen very often.

(It actually happens to me often enough to be very annoying.)

> I ended up solving the problem by doing this plus having the
> clickproc do an XSync after the XCopyArea.  This forces the
> just-generated gexpose into the input queue, where it can be handled
> right away.  And it only slows things down if the user does a whole
> bunch of scrolls, which doesn't happen too often.

Are you sure this fixes it?  Even with XSync(), you still have to be
very careful to offset events correctly.  (Using XSync narrows the
window but still doesn't close it.)

> I still wonder if there's a cleaner solution.

I don't think so.  Someday I'll get around to fixing mterm....

					der Mouse

			old: mcgill-vision!mouse
			new: mouse@larry.mcrcim.mcgill.edu