Strange Floatcanvas.ClearAll() behavior.

I have a strange issue with Floatcanvas.

I have the following code, this is not a complete example, since the
codebase is to big to be posted here:

[code]
  def arrange_shapes_in_order(self, obj):
    """"""
    # Get the current canvas.
    canv = self.p2.GetPage(self.p2.GetSelection())
    print canv

    canv.ClearAll()

    print canv.HitDict # this is now empty.

    # Add all shapes to the canvas.
    for item in obj.objects: # there are two objects. So this is done
twice.
      print '--', item.object
      canv.AddObject(item.object)
      item.object.Bind(FloatCanvas.EVT_FC_LEFT_DOWN, self.evt_object_hit)
      print canv._DrawList # Have two objects when done.
      print canv.HitDict # After the last run, this should have both
objects, but only have the last one.

    canv.Draw(True)
[/code]

This will give the following printout. The HitDict print should have
two objects, but it only have the last one. Why?

If I remove the canv.ClearAll() and add canv._DrawList = [],
canv.HitDict = None, it works just fine. The I get the second output,
which is correct.
A complete example that works just fine even with the ClearAll()
function used is found at http://www.copypastecode.com/13778/ , have
not been able to duplicate the strange behavior I have, so there must
be something in my code that make the Bind call overwrite the list
instead of add to it. But what. Any ideas?

[code]
<wx.lib.floatcanvas.FloatCanvas.FloatCanvas; proxy of <Swig Object of
type 'wxPanel *' at 0x1e19920> >
None
-- <builder_defaults.MovingBitmap instance at 0x1a3a440>
[<builder_defaults.MovingBitmap instance at 0x1a3a440>]
{10315: {(0, 0, 1): <builder_defaults.MovingBitmap instance at
0x1a3a440>}, 10316: {}, 10317: {}, 10318: {}, 10319: {}, 10320: {},
10321: {}, 10322: {}, 10323: {}, 10326: {}, 10327: {}}
-- <builder_defaults.MovingBitmap instance at 0x1a3a5f0>
[<builder_defaults.MovingBitmap instance at 0x1a3a440>,
<builder_defaults.MovingBitmap instance at 0x1a3a5f0>]
{10315: {(0, 0, 1): <builder_defaults.MovingBitmap instance at
0x1a3a5f0>}, 10316: {}, 10317: {}, 10318: {}, 10319: {}, 10320: {},
10321: {}, 10322: {}, 10323: {}, 10326: {}, 10327: {}}
[/code]

[code]
<wx.lib.floatcanvas.FloatCanvas.FloatCanvas; proxy of <Swig Object of
type 'wxPanel *' at 0x2939920> >
None
-- <builder_defaults.MovingBitmap instance at 0x262a440>
[<builder_defaults.MovingBitmap instance at 0x262a440>]
{10315: {(0, 0, 1): <builder_defaults.MovingBitmap instance at
0x262a440>}, 10316: {}, 10317: {}, 10318: {}, 10319: {}, 10320: {},
10321: {}, 10322: {}, 10323: {}, 10326: {}, 10327: {}}
-- <builder_defaults.MovingBitmap instance at 0x262a5f0>
[<builder_defaults.MovingBitmap instance at 0x262a440>,
<builder_defaults.MovingBitmap instance at 0x262a5f0>]
{10315: {(0, 0, 1): <builder_defaults.MovingBitmap instance at
0x262a440>, (0, 0, 2): <builder_defaults.MovingBitmap instance at
0x262a5f0>}, 10316: {}, 10317: {}, 10318: {}, 10319: {}, 10320: {},
10321: {}, 10322: {}, 10323: {}, 10326: {}, 10327: {}}
[/code]

orjanp wrote:

I have a strange issue with Floatcanvas.

I'm cc-ing a copy of this to the floatcanvas list. It's the kind of thing I want in the archives there.

I have the following code, this is not a complete example, since the
codebase is to big to be posted here:

In the Demos section of floatcanvas SVN, there is a MicroDemo.py Perhaps you could add to it to make a demo of this problem. It's hard for me to test/debug without a running sample.

This will give the following printout. The HitDict print should have
two objects, but it only have the last one. Why?

If I remove the canv.ClearAll() and add canv._DrawList = ,
canv.HitDict = None, it works just fine. The I get the second output,
which is correct.

very odd. I have one idea, though. If you look at FloatCanvas.ClearAll(), you see:

     def ClearAll(self, ResetBB=True):
         """
         ClearAll(ResetBB=True)

         Removes all DrawObjects from the Canvas

         If ResetBB is set to False, the original bounding box will remain

         """
         self._DrawList =
         self._ForeDrawList =
         self._BackgroundDirty = True
         self.HitColorGenerator = None
         self.UseHitTest = False
         if ResetBB:
             self._ResetBoundingBox()
         self.MakeNewBuffers()
         self.HitDict = None

which tries to re-set everything back to its original state. ONe of those calls is:

         self.HitColorGenerator = None

which re-sets the HitColoGenerator. That is used to assign a unique color to each object on teh off-screen hit-test image, and it's also a key in HitDict.

However, in the DrawObject Bind method, there is:

         if not self.HitColor:
             if not self._Canvas.HitColorGenerator:
                 self._Canvas.HitColorGenerator = _colorGenerator()
                 self._Canvas.HitColorGenerator.next() # first call to prevent the background color from being used.
             self.HitColor = self._Canvas.HitColorGenerator.next()

So it's checkign to see if the object already has a HitColor, and only giving it a new one if it doesn't.

In this case, it looks like you are re-using objects, so they may have already been assigned a HitColor, but the HitColorGenerator is being reset, so it could be assigning the same color to one your objects that is already assigned to the other one -- so it gets overridden in the HitDict.

You can test this by adding another print or two:

    # Add all shapes to the canvas.
    for item in obj.objects:
      print '--', item.object
      print item.object.HitColor

then maybe that same print after the Bind() call.

If I'm right, I'm not sure of the fix -- you can kludge around it by clearing the HitColor yourself, but the Canvas doesn't know about all objects, only those that are on it at the time, so I'm having trouble figuring out where to fix this. I'll take suggestions!

-Chris

PS: thanks for teh bug report -- it's only through testing all these use-cases I didn't think of that things get fixed!

···

--
Christopher Barker, Ph.D.
Oceanographer

Emergency Response Division
NOAA/NOS/OR&R (206) 526-6959 voice
7600 Sand Point Way NE (206) 526-6329 fax
Seattle, WA 98115 (206) 526-6317 main reception

Chris.Barker@noaa.gov

Based on the name I would guess that HitDict is a dictionary, not a list, so presumably the two items have the same key value and the latter one is therefore replacing the first one.

···

On 11/3/09 7:04 AM, orjanp wrote:

A complete example that works just fine even with the ClearAll()
function used is found at http://www.copypastecode.com/13778/ , have
not been able to duplicate the strange behavior I have, so there must
be something in my code that make the Bind call overwrite the list
instead of add to it. But what. Any ideas?

--
Robin Dunn
Software Craftsman

Christopher Barker wrote:

orjanp wrote:
  

I have a strange issue with Floatcanvas.
    
I'm cc-ing a copy of this to the floatcanvas list. It's the kind of thing I want in the archives there.

I have the following code, this is not a complete example, since the
codebase is to big to be posted here:
    
In the Demos section of floatcanvas SVN, there is a MicroDemo.py Perhaps you could add to it to make a demo of this problem. It's hard for me to test/debug without a running sample.
  

The problem was that I wasn't able to duplicate it. It was only a problem
in my code, and not in my smaller running sample.

This will give the following printout. The HitDict print should have
two objects, but it only have the last one. Why?

If I remove the canv.ClearAll() and add canv._DrawList = ,
canv.HitDict = None, it works just fine. The I get the second output,
which is correct.
    
very odd. I have one idea, though. If you look at FloatCanvas.ClearAll(), you see:

     def ClearAll(self, ResetBB=True):
         """
         ClearAll(ResetBB=True)

         Removes all DrawObjects from the Canvas

         If ResetBB is set to False, the original bounding box will remain

         """
         self._DrawList =
         self._ForeDrawList =
         self._BackgroundDirty = True
         self.HitColorGenerator = None
         self.UseHitTest = False
         if ResetBB:
             self._ResetBoundingBox()
         self.MakeNewBuffers()
         self.HitDict = None

which tries to re-set everything back to its original state. ONe of those calls is:

         self.HitColorGenerator = None

which re-sets the HitColoGenerator. That is used to assign a unique color to each object on teh off-screen hit-test image, and it's also a key in HitDict.

However, in the DrawObject Bind method, there is:

         if not self.HitColor:
             if not self._Canvas.HitColorGenerator:
                 self._Canvas.HitColorGenerator = _colorGenerator()
                 self._Canvas.HitColorGenerator.next() # first call to prevent the background color from being used.
             self.HitColor = self._Canvas.HitColorGenerator.next()

So it's checkign to see if the object already has a HitColor, and only giving it a new one if it doesn't.

In this case, it looks like you are re-using objects, so they may have already been assigned a HitColor, but the HitColorGenerator is being reset, so it could be assigning the same color to one your objects that is already assigned to the other one -- so it gets overridden in the HitDict.

You can test this by adding another print or two:

    # Add all shapes to the canvas.
    for item in obj.objects:
      print '--', item.object
      print item.object.HitColor

then maybe that same print after the Bind() call.

If I'm right, I'm not sure of the fix -- you can kludge around it by clearing the HitColor yourself, but the Canvas doesn't know about all objects, only those that are on it at the time, so I'm having trouble figuring out where to fix this. I'll take suggestions!

-Chris

PS: thanks for teh bug report -- it's only through testing all these use-cases I didn't think of that things get fixed!
  

You are correct. I'm reusing objects. I remove them from the canvas and put them back in
a controlled order. The problem was indeed the HitColor being the same for multiple objects.

I'm not sure if my temporary solution is viable, but it works, at least for the small amount of
testing I have done so far. In FloatCanvas.ClearAll()

    def ClearAll(self, ResetBB=True, ResetHCG=True):
        """
        ClearAll(ResetBB=True)

        Removes all DrawObjects from the Canvas

        If ResetBB is set to False, the original bounding box will remain

        """
        self._DrawList =
        self._ForeDrawList =
        self._BackgroundDirty = True
        if ResetHCG:
            self.HitColorGenerator = None
        self.UseHitTest = False
        if ResetBB:
            self._ResetBoundingBox()
        self.MakeNewBuffers()
        self.HitDict = None

Or I could just save the HitColorGenerator object before calling the ClearAll() function.

        hcg = canvas.HitColorGenerator
        canvas.ClearAll()
        canvas.HitColorGenerator = hcg

Works fine to it seems.

So, thanks for the help. :slight_smile:

Oerjan...

Yes, you are correct. It was just me not being very precise. It is indeed a dictionary and not a
list.

Yes, the key value, the object.HitColor value, is the same. And it should not be. The problem is
that I am reusing objects, and that creates problems. Christopher Barker did pinpoint the
problem. So it is kind of solved.

Oerjan...

Robin Dunn wrote:

···

On 11/3/09 7:04 AM, orjanp wrote:
  

A complete example that works just fine even with the ClearAll()
function used is found at http://www.copypastecode.com/13778/ , have
not been able to duplicate the strange behavior I have, so there must
be something in my code that make the Bind call overwrite the list
instead of add to it. But what. Any ideas?
    
Based on the name I would guess that HitDict is a dictionary, not a list, so presumably the two items have the same key value and the latter one is therefore replacing the first one.

Ørjan Pettersen wrote:

The problem was that I wasn't able to duplicate it. It was only a problem
in my code, and not in my smaller running sample.

Always a challenge!

You are correct. I'm reusing objects.

Which I really intend to support!

I remove them from the canvas and put them back in
a controlled order.

Which brings up another feature I've been meaning to add -- an API for moving the order of objects on the Canvas.

If the only reason you have to Clearing and re-adding is to change the order, you may want to simply manipulate the _DrawList -- there should be an API for that, but since there isn't a hack may suffice.

I'm not sure if my temporary solution is viable, but it works, at least for the small amount of
testing I have done so far. In FloatCanvas.ClearAll()

    def ClearAll(self, ResetBB=True, ResetHCG=True):
        """
        ClearAll(ResetBB=True)

        Removes all DrawObjects from the Canvas

        If ResetBB is set to False, the original bounding box will remain

        """
        self._DrawList =
        self._ForeDrawList =
        self._BackgroundDirty = True
        if ResetHCG:
            self.HitColorGenerator = None
        self.UseHitTest = False
        if ResetBB:
            self._ResetBoundingBox()
        self.MakeNewBuffers()
        self.HitDict = None

That'll work -- the only issue is that if you don't reset the HitColorGenerator, and you do this a LOT with LOT of objects, you could run out of colors.

Or I could just save the HitColorGenerator object before calling the ClearAll() function.

        hcg = canvas.HitColorGenerator
        canvas.ClearAll()
        canvas.HitColorGenerator = hcg

Works fine to it seems.

That should work too, and may be a better hack for you, as it doesn't require changing FloatCanvas itself.

I'm still trying to think of a good general solution, though....

-Chris

···

--
Christopher Barker, Ph.D.
Oceanographer

Emergency Response Division
NOAA/NOS/OR&R (206) 526-6959 voice
7600 Sand Point Way NE (206) 526-6329 fax
Seattle, WA 98115 (206) 526-6317 main reception

Chris.Barker@noaa.gov