ButtonPanel Revisited ;-)

Hello NG,

    I apologise in advance for the somewhat long email I am writing. A
while back Robin asked me to implement some more functionalities, and
here we are with the improved latest version of ButtonPanel.

To say something in advance (please read it!), the docstrings needs to
be improved (i will look at it quite soon), right now support for
shortHelp in tooltip and longHelp in statusbar fields is being
implemented. And I am thinking about implementing chevron button
support for ButtonPanel and/or multirow/multicolumn layout for items
(ButtonPanel is essentially a wx.Toolbar, almost).

But before going on with this (complex, I would say) work, I would
really appreciate some inputs from this kind NG (bugs, misbehavior and
so on); moreover, there are a couple of issues (marked as #WEIRD and
#NOTE inside the source and the demo): noting that the vast majority
of people here is far smarter than me in wxPython coding, I would like
to ask wxPython-gurus for suggestions on how to improve the general
Layout strategy (faster? Nicer? Better?).

Ok, talked enough. How it works:

ButtonPanel is similar to a wx.ToolBar, with the ability to draw
gradient shading background and the possibility to add custom controls
(see below) behind which the gradient shading is kept.

Improvements:

- Existing Things:
1) Buttons: buttons now can have text associated with them. The text
and the image in the button can be drawn stacked (i.e. the text is
below the image) or the text can be drawn to the right of the button
(similar to wx.ToolBar);
2) Gradient shading: gradient can now be horizontal or vertical;
3) Rendering: the drawing of ButtonPanel background and of its custom
control has been moved to a separate manager class;

- New implementations:
1) ButtonPanel now supports the 4 alignment: left, right, top, bottom,
which have a different meaning and behavior wrt wx.Toolbar. The
easiest thing is to try the demo to understand, but I'll try to
explain how it works.

CASE 1: ButtonPanel has a main caption text
Left alignment means ButtonPanel is horizontal, with the text aligned
to the left. When you shrink the demo frame, if there is not enough
room for all the controls to be shown, the controls closest to the
text are hidden;

Right alignment means ButtonPanel is horizontal, with the text aligned
to the right. Item layout as above;

Top alignment means ButtonPanel is vertical, with the text aligned to
the top. Item layout as above;

Bottom alignment means ButtonPanel is vertical, with the text aligned
to the bottom. Item layout as above.

CASE 2: ButtonPanel has *no* main caption text
In this case, left and right alignment are the same (as top and bottom
are the same), but the layout strategy changes: now if there is not
enough room for all the controls to be shown, the last added items are
hidden ("last" means on the far right for horizontal ButtonPanels and
far bottom for vertical ButtonPanels).

I hope I made myself clear. Trying the demo is the easiest thing to do :wink:

2) Possibility to add separators (as in wx.Toolbars) and specify their size;
3) Possibility to add spacers (flexible or fixed-space) at the
beginning, at the end or between the ButtonPanel items;
4) Possibility to add wxPython controls to ButtonPanel: be aware
however that controls that do not have transparent background (i.e.
wx.CheckBox) will paint horribly on a gradient shading ButtonPanel.
5) Buttons support wx.ITEM_NORMAL (for normal buttons) and
wx.ITEM_CHECK (for toggle buttons). I am in the process of adding
wx.ITEM_RADIO support;
6) Buttons have 5 "states" by default:
a) Normal
b) Disabled
c) Hover
d) Pressed
e) Toggled
Any of these states can have a bitmap associated with it. Moreover,
you can add as many states as you want to a particular bitmap (just
name them, such like "Custom" state) and associate a custom bitmap to
that state;
7) ButtonPanel margins (i.e. space at the beginning and at the end of
ButtonPanel), padding (inter-tool spacing), border size, and separator
spacing can be customized;
8) For colours, main caption, gradients, static background, border,
hovering/pressing brush/pen selection, text in buttons colours can be
customized;
9) Fonts for main caption text and text in buttons can be customized.

Ok, I re-apologize for this fantastically long and boring e-mail. You
can find the source and the demo in the usual places:

http://xoomer.alice.it/infinity77/eng/ButtonPanel.html
http://xoomer.alice.it/infinity77/ita/ButtonPanel.html

If you have any suggestion (and maybe something to fix the #WEIRD and
#NOTE problems in the source), please let me know.

Happy wxPythoning :wink:

Andrea.

"Imagination Is The Only Weapon In The War Against Reality."
http://xoomer.virgilio.it/infinity77/

Hello Andrea:

Your enhancements sound interesting, but unfortunately, the demo won’t
run.

The error traceback is given below. It appears that the method
‘IsFrozen’ is not defined in your code, as well as a ‘Thaw’ method.

I am running windowsXP-SP2, wxpython 2.6.3.2 and python 2.4.2

Traceback (most recent call last):

File “C:\Documents and Settings\Owner\My
Documents\Python\wxPy_Tools\FancyButtonPanel\ButtonPanelDemo.py”, line
873, in ?

main()

File “C:\Documents and Settings\Owner\My
Documents\Python\wxPy_Tools\FancyButtonPanel\ButtonPanelDemo.py”, line
866, in main

frame = ButtonPanelDemo(None, -1)

File “C:\Documents and Settings\Owner\My
Documents\Python\wxPy_Tools\FancyButtonPanel\ButtonPanelDemo.py”, line
501, in init

self.CreateButtons()

File “C:\Documents and Settings\Owner\My
Documents\Python\wxPy_Tools\FancyButtonPanel\ButtonPanelDemo.py”, line
641, in CreateButtons

self.titleBar.DoLayout()

File “C:\Documents and Settings\Owner\My
Documents\Python\wxPy_Tools\FancyButtonPanel\ButtonPanel.py”, line
1389, in DoLayout

if self.IsFrozen():

AttributeError: ‘ButtonPanel’ object has no attribute ‘IsFrozen’

Regards


<details class='elided'>
<summary title='Show trimmed content'>&#183;&#183;&#183;</summary>

--

Lawrence
Gray

**e-mail: lawrence.gray@zing-net.ca
**


Hello Lawrence,

Your enhancements sound interesting, but unfortunately, the demo won’t run.

The error traceback is given below. It appears that the method ‘IsFrozen’ is
not defined in your code, as well as a ‘Thaw’ method.

uhm, curiously enough it works on 2.7.1 but fails with whatever 2.6* wxPython version. I guess I will have to add these methods to ButtonPanel, even if in my understanding the hierarchy should be:

ButtonPanal => wx.PyPanel => (wx.Panel) => wx.Window

So ButtonPanel should have methods like Freeze(), Thaw(), IsFrozen(). But I am surely wrong.

Thanks for the report, I’ll try to think something in order to keep ButtonPanel consistent between 2.6 and 2.7 (again).

Andrea.

···

Andrea Gavana
(gavana@kpo.kz)

Reservoir Engineer

KPDL

4, Millbank

SW1P 3JA London

Direct Tel: +44 (0) 20 717 08936

Mobile Tel: +44 (0) 77 487 70534

Fax: +44 (0) 20 717 08900

Web:
http://xoomer.virgilio.it/infinity77

¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯

Gavana, Andrea wrote:

Hello Lawrence,

Your enhancements sound interesting, but unfortunately, the demo won't

run.

The error traceback is given below. It appears that the method

'IsFrozen' is

not defined in your code, as well as a 'Thaw' method.

uhm, curiously enough it works on 2.7.1 but fails with whatever 2.6* wxPython version. I guess I will have to add these methods to ButtonPanel, even if in my understanding the hierarchy should be:
ButtonPanal => wx.PyPanel => (wx.Panel) => wx.Window
So ButtonPanel should have methods like Freeze(), Thaw(), IsFrozen(). But I am surely wrong.

Freeze and Thaw should be there in 2.6, but IsFrozen is new in 2.7.

···

--
Robin Dunn
Software Craftsman
http://wxPython.org Java give you jitters? Relax with wxPython!

Andrea Gavana wrote:

Hello NG,

   I apologise in advance for the somewhat long email I am writing. A
while back Robin asked me to implement some more functionalities, and
here we are with the improved latest version of ButtonPanel.

To say something in advance (please read it!), the docstrings needs to
be improved (i will look at it quite soon), right now support for
shortHelp in tooltip and longHelp in statusbar fields is being
implemented. And I am thinking about implementing chevron button
support for ButtonPanel and/or multirow/multicolumn layout for items
(ButtonPanel is essentially a wx.Toolbar, almost).

But before going on with this (complex, I would say) work, I would
really appreciate some inputs from this kind NG (bugs, misbehavior and
so on); moreover, there are a couple of issues (marked as #WEIRD and
#NOTE inside the source and the demo): noting that the vast majority
of people here is far smarter than me in wxPython coding, I would like
to ask wxPython-gurus for suggestions on how to improve the general
Layout strategy (faster? Nicer? Better?).

I like the improvements that I see, however there are a couple problems right out of the box. (I'm using my current 2.7 cvs workspace for both of these. I haven't tried with any other version yet.)

* First, on wxGTK it is not removing the buttons that don't fit, they just overwrite the label. I'm looking into this now but if you could point me to the right places in the code to isolate this I would appreciate it.

* Secondly on wxMac it is not displaying anything at all. It is reserving the space for it in the layout, but nothing is there. This may be just a simple missing Thaw, or perhaps a snafu in the demo code or something. I'll try to track this one down too.

···

--
Robin Dunn
Software Craftsman
http://wxPython.org Java give you jitters? Relax with wxPython!

Hi Robin & NG,

Freeze and Thaw should be there in 2.6, but IsFrozen is new in 2.7.

Ok, I have implemented Freeze, Thaw and IsFrozen and they seem to work as expected.

* First, on wxGTK it is not removing the buttons that don't
fit, they just overwrite the label. I'm looking into this
now but if you could point me to the right places in the code
to isolate this I would appreciate it.

The code that handles the layout when the main caption text is present is located in the method FlexibleLayout(). It works in this way:

Size event (or a call to DoLayout) =>
Start LayoutItems() method =>
Start FlexibleLayout() method

When no main caption text is present (when you set as text "", an empty string), the layout is done by the SizeLayout() method.

* Secondly on wxMac it is not displaying anything at all. It
is reserving the space for it in the layout, but nothing is
there. This may be just a simple missing Thaw, or perhaps a
snafu in the demo code or something. I'll try to track this
one down too.

Robin, may it be due to my use of wx.BufferedPaintDC()? Remember last week the discussion about the problems on Mac by using this class?

Andrea.

···

_________________________________________
Andrea Gavana (gavana@kpo.kz)
Reservoir Engineer
KPDL
4, Millbank
SW1P 3JA London

Direct Tel: +44 (0) 20 717 08936
Mobile Tel: +44 (0) 77 487 70534
Fax: +44 (0) 20 717 08900
Web: http://xoomer.virgilio.it/infinity77
¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯

Hi Robin & NG,

The code that handles the layout when the main caption text is
present is located in the method FlexibleLayout().

Size event (or a call to DoLayout) =>
Start LayoutItems() method =>
Start FlexibleLayout() method

Sorry, maybe you would have liked more details. Ok, this is how it works:

def LayoutItems(self):
    """
    Layout the items using a different algorithm depending on the existance
    of the main caption.
    """

    # Here I get all the sizer children EXCEPT the flexible spacer
    # in which I am not interested. They may be zero sized without
    # problems, while controls, buttons and fixed-space spacers
    # *must* remain their size.
    # The non-flexible items are stored in the list 'nonspacers'
    # while inside 'allchildren' I keep *all* the sizer's children
    nonspacers, allchildren = self.GetNonFlexibleChildren()
    
    if self.HasBarText():
        # This is the layout when the main caption text is present
        self.FlexibleLayout(nonspacers, allchildren)
    else:
        self.SizeLayout(nonspacers, allchildren)
        
    self._mainsizer.Layout()

Now, for the FlexibleLayout method:

def FlexibleLayout(self, nonspacers, allchildren):
    """ Layout the items when the main caption exists. """

    # Minimum of 2 non-flexible are needed, otherwise no
    # intelligent layout can be done
    if len(nonspacers) < 2:
        return
    
    # IsVertical returns whether ButtonPanel is vertical or not
    # (like the wx.ToolBar)
    # IsStandard returns True if the alignment chosen by the user
    # is LEFT or TOP.

    isVertical = self.IsVertical()
    isStandard = self.IsStandard()
    
    size = self.GetSize()[isVertical]
    padding = self._art.GetMetric(BP_PADDING_SIZE)

    # 'fixed' is the fixed main caption in ButtonPanel, that must
    # be excluded from the list of items that may be hidden in case
    # of missing space.
    fixed = (isStandard and [nonspacers[1]] or [nonspacers[-2]])[0]
    
    if isStandard:
        # I reverse the list because I need to start from the last
        # item added to ButtonPanel to calculate which items should
        # be hidden
        nonspacers.reverse()

        # left (top) position of the main caption text inside the
        # sizer plus the padding
        leftendx = fixed.GetSize()[isVertical] + padding.x
    else:
        # Here instead I do not need a reverse list of nonspacers
        # children, and I start from the left (bottom) position
        # of the main caption text
        rightstartx = size - fixed.GetSize()[isVertical]
        size = 0

    count = lennonspacers = len(nonspacers)
    
    for item in nonspacers:
        if isStandard:
            # Subtract from 'size' the size of the current item
            size -= self.GetItemSize(item, isVertical)

            # if we are at the left (or above) the main caption text
            # it's time to hide all the controls that are before
            # the current item and the current item
            if size < leftendx:
                break
        else:
            # Add to 'size' the size of the current item
            size += self.GetItemSize(item, isVertical)

            # if we are at the right (or below) the main caption text
            # it's time to hide all the controls that are after
            # the current item and the current item
            if size > rightstartx:
                break
            
        count = count - 1

    nonspacers.reverse()
    
    # Loop over all the items starting from the flexible spacers
    # near the main caption text
    for jj in xrange(2, lennonspacers):
        indx = allchildren.index(nonspacers[jj])
        self._mainsizer.Show(indx, jj >= count)

Please let me know if you need any further explanation.

Andrea.

···

_________________________________________
Andrea Gavana (gavana@kpo.kz)
Reservoir Engineer
KPDL
4, Millbank
SW1P 3JA London

Direct Tel: +44 (0) 20 717 08936
Mobile Tel: +44 (0) 77 487 70534
Fax: +44 (0) 20 717 08900
Web: http://xoomer.virgilio.it/infinity77
¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯

Robin Dunn wrote:

I like the improvements that I see, however there are a couple problems right out of the box. (I'm using my current 2.7 cvs workspace for both of these. I haven't tried with any other version yet.)

Both of these are problems with wx/wxPython, not ButtonPanel.

* First, on wxGTK it is not removing the buttons that don't fit, they just overwrite the label. I'm looking into this now but if you could point me to the right places in the code to isolate this I would appreciate it.

This was due to a mistake in how wx.SizerItem.GetSpacer was wrapped, and causing it to return a reference to a temporary wx.Size object, instead of making a copy of it.

After making the fix I also simplified your GetItemSize method since item.GetSize will return the right value no matter what type of item it is.

* Secondly on wxMac it is not displaying anything at all. It is reserving the space for it in the layout, but nothing is there. This may be just a simple missing Thaw, or perhaps a snafu in the demo code or something. I'll try to track this one down too.

I've also fixed this one. It's due to IsFrozen not being completely implemented for wxMac so your Thaw was never being called. I just implemented wxMac's IsFrozen method.

I'll check in the newest version with my fixes in a few minutes.

···

--
Robin Dunn
Software Craftsman
http://wxPython.org Java give you jitters? Relax with wxPython!

Gavana, Andrea wrote:

* Secondly on wxMac it is not displaying anything at all. It is reserving the space for it in the layout, but nothing is there. This may be just a simple missing Thaw, or perhaps a snafu in the demo code or something. I'll try to track this one down too.

Robin, may it be due to my use of wx.BufferedPaintDC()? Remember last week the discussion about the problems on Mac by using this class?

No, I've already fixed that, and as long as you are not trying to use the bufferedDC as the source of a Blit it wouldn't have triggered that problem anyway

···

--
Robin Dunn
Software Craftsman
http://wxPython.org Java give you jitters? Relax with wxPython!

Robin Dunn wrote:

* First, on wxGTK it is not removing the buttons that don't fit, they just overwrite the label. I'm looking into this now but if you could point me to the right places in the code to isolate this I would appreciate it.

This was due to a mistake in how wx.SizerItem.GetSpacer was wrapped, and causing it to return a reference to a temporary wx.Size object, instead of making a copy of it.

Oh, I forgot to say that it was probably just lucky that this problem didn't show up on the other platforms as well. Probably just a matter of those bytes in the stack memory not being overwritten yet by the next function call.

Also, on easy optimization you could make is to not always refresh the whole panel. For example, if only one button needs to be redrawn (to show or hide the hover image for example) then call Refresh with the rect that just that button occupies. Since you're using a buffered DC you'll still need to draw the whole thing, but only those pixels in the update region will actually be transferred to the screen.

···

--
Robin Dunn
Software Craftsman
http://wxPython.org Java give you jitters? Relax with wxPython!

Hi Robin,

thanks for your tips on optimization. I will modify the code as soon
as I am able to get the latest CVS of ButtonPanel (I have checked out
now with TortoiseCVS but it still reports the old version dated 6
october). It seems there are some problems on wxWidgets/wxPython web
domain, I cannot access the web pages nor the download pages nor the
CVS browsing via html.
I have added also support for tootips for shortHelp in buttons and
statusbar help for longHelp. If it is Ok for you, I'll send you a
patch against your latest modified CVS version.

Andrea.

"Imagination Is The Only Weapon In The War Against Reality."
http://xoomer.virgilio.it/infinity77/

Andrea Gavana wrote:

Hi Robin,

thanks for your tips on optimization. I will modify the code as soon
as I am able to get the latest CVS of ButtonPanel (I have checked out
now with TortoiseCVS but it still reports the old version dated 6
october).

It's checked in now.

It seems there are some problems on wxWidgets/wxPython web
domain, I cannot access the web pages nor the download pages

Looks like SourceForge webhost is hosed again.

nor the
CVS browsing via html.

These are on a different machine however and should work: http://cvs.wxwidgets.org/viewcvs.cgi/wxWidgets/

I have added also support for tootips for shortHelp in buttons and
statusbar help for longHelp. If it is Ok for you, I'll send you a
patch against your latest modified CVS version.

Sounds good. Thanks.

···

--
Robin Dunn
Software Craftsman
http://wxPython.org Java give you jitters? Relax with wxPython!

Hi gang,

    I have updated the source code and the demo with the latest
modifications, which include support for tooltips and for help in the
statusbar (if any) for every button in ButtonPanel. Moreover, the
drawing speed should now be much faster (I removed a bunch of useless
Refresh() and now I call a RefreshRect() for a single button, when
needed) and I have integrated the suggestions from Robin.
The code now should work also on wxPython 2.6, I tested it here with 2.6.3.3.

Sources and demo are in the usual places:

http://xoomer.alice.it/infinity77/eng/freeware.html#buttonpanel

Or:

http://xoomer.alice.it/infinity77/ita/freeware.html#buttonpanel

Please report any bug and/or request for improvements.

Happy wxPythoning :wink:

Andrea.

"Imagination Is The Only Weapon In The War Against Reality."
http://xoomer.virgilio.it/infinity77/