Repository: kivy/kivy

Issue:
TabbedPanel API is bad, needs to be like Accordion
TabbedPanel's API is unnatural and hard to understand (not to mention buggy). The container paradigm, like the one used Accordion/AccordionItem is much more consitent with the concept of hidden panels.



Discussion:
What exactly is bad? You really should elaborate more, because as it is now, this issue is useless.

TabbedPanel and Accordion are basically the same concept: hidden pannels where only one can be shown at a time, with buttons to choose which one.

The API for Accordion is simple, straightforward, and well-separated: the container Accordion describes the overall layout, and the nested AccordionItems describe each individual panel.

If I understand the documentation of the  proposed API for TabbedPanel introduces a concept unique in Kivy: content referencing. The panel is described by its header, TabbedPanelHeader, which has a binding property "content", which is a reference to the id of a widget declared somewhere else.

Moreover, the content of the first panel is a special case called "default tab", which is described inside the TabbedPanel itself, unlike the other panels. Its text is set by the property default_tab_text, not 'text' like the other panels. This one uses nesting for its content, not referencing. (And who decided the first one has to be the "default"?)

This could be a legitimate API if we didn't already have Accordion doing things differently. I'm not a big fan of the referencing of outside widgets as content (nesting is a much better model), and the first panel special case is just wrong from a design standpoint. This is just bad design to have two completely different APIs for two similar concepts.

The fact that the example in the document doesn't work certainly doesn't help. (On a side note: please don't use experimental widgets such as RstDocument in your examples, this is confusing since it's not in the main documentation.)

After experimenting quite a bit with the TabbedPanel to replace my Accordions (in kv language), I find them very hard to design with. For instance, re-ordering the tabs is not trivial if it involves the so-called "default tab". Also, one should be able to switch between Accodion and TabbedPanel very easily to try different models, which is not the case right now.

The behavior is also strange: for instance often times the content widgets are displayed in the layout, until the tabs are pressed, which then "swallows" them in the TabbedPanel. This is clearly implementation bugs, not a design issue, but it is fairly obvious that these bugs stem from the content referencing design choice.

At this point, my conclusion is that the TabbedPanel widget should not be used and needs a complete API de-resign on the model of the Accordion widget.

Hello Titousensei,
Thanks for opening the issue,

If you could please elaborate about what is "Buggy", it would help us fix it.

About the container paradigm, while attempting to provide more control to the user so that a user could do the things like the capabilities showcased inside the [tabbed_panel_showcase.py](https://github.com/kivy/kivy/blob/master/examples/widgets/tabbed_panel_showcase.py) example or like these snippets [tabbed_panel_with_screenmanager](http://paste.ubuntu.com/1267917/) , [tabbed_panel](http://paste.ubuntu.com/1267923/) meant that the focus on providing a simpler interface was lost.

However, that is no excuse to not provide a simpler interface that can be used to easily manipulate the TabbedPanel for people that don't want to customize it.

The branch `tabbed_panel_usage` houses a attempt at making thinks easier to handle like in Accordion.
let us know if the usage is easier. Basically you can with that branch simply do 

```
<TabbedPanel>:
        ...other settings
        TabbedPanelItem:
            BoxLayout:
                Label:
                    text: 'Second tab content area'
                Button:
                    text: 'Button that does nothing'
```

The tabbedpanel.py example has been adjusted to use this method. (nesting as you call it)

The concept of `default_tab` is a bit different than what you understand it to be. 
First of even the default tab uses referencing like other tabs although that part is transparent to the user. 

You can customize it by ::

a) Setting `[default_tab](http://kivy.org/docs/api-kivy.uix.tabbedpanel.html#kivy.uix.tabbedpanel.TabbedPanel.default_tab)` to a different tab that you have created and the previous default_tab will be automatically deleted (if it was the auto provided tab)

b) Providing a [default_tab_cls](http://kivy.org/docs/api-kivy.uix.tabbedpanel.html#kivy.uix.tabbedpanel.TabbedPanel.default_tab_cls). The default tab will be instantiated from this class.

So, you can define your own class that is used by the default_tab. basically you don't have to use the default tab and it can be taken out of your way easily using the above provided methods but is a convenience, maybe not for your needs/situation.

The rational behind having a default_tab is to avoid a situation where you don't have a tab head and only the content area. Therefore you can not remove the default_tab, you can though change another tab to default_tab and remove the previous one.  If you account for this behaviour re-ordering should be easy.

TabbedPanel is not the same concept as the Accordion. TabbedPanel differs in that the content is clearly separated from the headers. Therefore you can have the same content displayed in more than one Headers selection or not load the content  but something else depending on any dynamic situation or load the content of tab `a` when tab `b` is pressed and vice-versa. You can do these things by over-riding the [switch_to](http://kivy.org/docs/api-kivy.uix.tabbedpanel.html#kivy.uix.tabbedpanel.TabbedPanel.switch_to) method.  

TabbedPanel itself is a experimental widget that is using another experimental widget(RstDocument that came before it). What is the rational behind not using it, could you be a bit more clear about it? It is included in the documentation [here](http://kivy.org/docs/api-kivy.uix.rst.html)

We already have a Accordion widget, Tabbed Panel was intentionally meant to provide different functionality than a Accordion.

The example in the documentation is basically the inclusion of the examples/widgets/tabbedpanel.py
if it doesn't work for you please open a separate issue with your output pasted to a pastebin type site.

If there are other things that are non-intuitive, please let us know we will try and make it more intuitive.
If there are fundamental concepts that you differ with please don't hesitate in mentioning them we are here to listen to our users.

P.S.
Sorry for a long post  I hate them myself.

>    On 10/08/2012 11:48 AM, qua-non wrote:
> 
>    Hello Titousensei,
>        Thanks for opening the issue,
>    If you could please elaborate about what is "Buggy", it would help us fix it.

About the buggy behavior, I always file bugs as I find them if I
have a chance to investigate and build a test example. I will do
that too for TabbedPanel, but I find the usage so frustrating I
doubt I will have the opportunity to use them in the near future.

>  ...
> The branch tabbed_panel_usage houses a attempt at
> making thinks easier to handle like in Accordion.
> let us know if the usage is easier. ...

This looks promising. Is there a chance to see that merged soon?

> The concept of default_tab is a bit different
> than what you understand it to be. 

Thanks for giving me the details. I will comment inline below.

> First of even the default tab uses referencing like other tabs
> although that part is transparent to the user. 

But is uses different property names. If I want to list the title of
all my tabs, I need to handle the default tab differently.

> You can customize it by ::
> a) Setting [default_tab](http://kivy.org/docs/api-kivy.uix.tabbedpanel.html#kivy.uix.tabbedpanel.TabbedPanel.default_tab)
> to a different tab that you have created and the previous
> default_tab will be automatically deleted (if it was the auto
> provided tab)
> b) Providing a default_tab_cls.
> The default tab will be instantiated from this class.

So far you haven't justified the fact that one of the tabs needs
special handling, just how it's handled differently. My point was
that if one tab needs this functionality, all of them do. But most
likely none of them do need to be different from any widget. 

By having one tab being the "default tab", you're making assumptions
on the usage applications will make of the TabbedPanel. These
assumptions might be right for some cases, but certainly not for all
cases. Actually, I can't find any example of applications where such
customization might be useful in the default tab, and _only_ in the
default tab. Either this is a legitimate feature and it should
potentially apply to all widgets that resemble tabs, or it's just an
over-engineered feature that needs to be cut.

> So, you can define your own class that is used by the
> default_tab. basically you don't have to use the default tab and
> it can be taken out of your way easily using the above provided
> methods but is a convenience, maybe not for your
> needs/situation.

Isn't that the case for any widget already? You can define your own
class to be used as a widget. In the application I'm making, I have
a different class for the content of each AccordionItem. I don't see
the extra "convenience" the default tab is providing me. It seems to
just add to the confusion.

> The rational behind having a default_tab is to avoid a
> situation where you don't have a tab head and only the content
> area. Therefore you can not remove the default_tab, you can
> though change another tab to default_tab and remove the previous
> one. If you account for this behaviour re-ordering should be
> easy.
> 
> TabbedPanel is not the same concept as the Accordion.
> TabbedPanel differs in that the content is clearly separated
> from the headers. Therefore you can have the same content
> displayed in more than one Headers selection or not load the
> content but something else depending on any dynamic situation or
> load the content of tab a when tab b
> is pressed and vice-versa. You can do these things by
> over-riding the switch_to
> method. 

I don't see how any of your examples don't apply to the Accordion.
In terms of concepts (I'm not talking about what's doable in the
current implementation), why wouldn't I want the same content be
displayed in different AccordionItems? Because the animation makes
it look like they are different "drawers"? What if the animation was
different, like a swipe over a fixed content? What if the
TabbedPanel had animations for switching tabs too? Don't you see
they are the same in different clothing?

(Even some the other customizations demonstrated in the showcase
example would be nice to have in the Accordion.)

> TabbedPanel itself is a experimental widget that is using
> another experimental widget(RstDocument that came before it).
> What is the rational behind not using it, could you be a bit
> more clear about it? It is included in the documentation here

You're right, it's documented. The widget is called "RstDocument"
and the nav bar calls it "reStructuredText renderer". It's a bug in
the websites, then :-)

> We already have a Accordion widget, Tabbed Panel was
> intentionally meant to provide different functionality than a
> Accordion.

In term of UI design, the TabbedPanel concept and the Accordion
concept are identical. They provide exactly the same functionality.
The only difference is how we draw them and their use of layout
space.

> The example in the documentation is basically the inclusion of
> the examples/widgets/tabbedpanel.py
> if it doesn't work for you please open a separate issue with
> your output pasted to a pastebin type site.

RstDocument depends on docutils, which was not installed on my
system. That's why I said the example was not working. After
installing docutils, the example works fine, sorry about that.

> If there are other things that are non-intuitive, please let us
> know we will try and make it more intuitive.
> If there are fundamental concepts that you differ with please
> don't hesitate in mentioning them we are here to listen to our
> users.

I just want to summarize the issues and not be distracted by
specific cases:

1) TabbedPanel and Accordion provide the same functionally with
different graphics. They should have the same API to be easily
interchangeable when designing the UI and testing of usability. It's
also good to have similar APIs for similar concepts.

2) Having a "default tab" is an arbitrary constraint that makes a
special case of something that is not. It complicates the API by
trying to add special features to one tab when either all the tabs
should have it, or there's other ways to do it already
(subclassing), or the feature is never going to be used.

3) TabbedPanel is the only widget using referencing. Every other
widget uses nesting: Layouts, Accordion, even canvas. TabbedPanel's
API is just not consistent with the rest of Kivy.

> P.S.
> Sorry for a long post I hate them myself.

Thank you, that's a lot of useful information. May be some of it
should be in the documentation ;-)

Hum, replying by email doesn't indent the quoted parts properly, sorry about that.

you can edit the answer by clicking on the pencil, if you are willing to make things clearer :) (i think it's readable, though)

```
This looks promising. Is there a chance to see that merged soon?
```

Hopefully this will be merged before 1.5, but let's first attempt to address most of the issue being faced :).  You can in order to test just do a `git pull` if you are using the dev version and `git checkout tabbed_panel_usage`.

```
I find the usage so frustrating I doubt I will have the opportunity to use them in the near future.
```

I sincerely hope we can change that. With the above mentioned branch, in terms of usage inside the kv language
you can just use nesting to define your widgets inside the TabbedPanel. The referencing part is still there for facilitating customisation but isn't forced on you.

```
But is uses different property names. If I want to list the title of all my tabs, I need to handle the default tab differently.
```

The `default_tab` is a instance of the `dafault_tab_cls` which by default is `TabbedPanelHeader` just like all other tabs, unless you specifically specify the `default_tab_cls`  to be of different type than the rest of the tabs.

So by default `default_tab` does not have different property names, as in `default_tab.text` is still there. [default_tab_text](http://kivy.org/docs/api-kivy.uix.tabbedpanel.html#kivy.uix.tabbedpanel.TabbedPanel.default_tab_text) is a property of the `TabbedPanel` just like  `default_tab` and not a property of the `default_tab`. The property `default_tab_text` is basically updates `default_tab.text`, and is provided so you can change the default_tab's text property from within the TabbedPanel inside kv language.

In other words, there is nothing stopping you to  access `default_tab.text`. and by default the `default_tab` has the same properties as the rest of the tabs. So you don't need to access the default_tab differently.

```
So far you haven't justified the fact that one of the tabs needs special handling, just how it's handled differently...
```

As outlined above the `default_tab` doesn't need special handling

```
I can't find any example of applications where such customization might be useful in the default tab, and *only* in the default tab.
```

I can understand that you haven't come across a situation where you have ever needed the default_tab to behave different than others. This doesn't negate that others might as I have a need for it. Of-course we need to design the widget for most common needs and still provide a way to customize it for those who need to. 

Therefore by default the `default_tab_class` is of the same type the rest of the tabs and thus behaves like anyother tab with only one exception that it can not be removed. Maybe that restriction being forced by default is not a good Idea and we should disable it by default and give the user a option `default_tab_removable` or something similar  which is True by default?

```
Isn't that the case for any widget already? You can define your own class to be used as a widget. In the application I'm making, I have a different class for the content of each AccordionItem. I don't see the extra "convenience" the default tab is providing me. It seems to just add to the confusion
```

Here again you are confusing the `TabbedPanelHeader` with content, The class `default_tab_cls` defines the class that controls behaviour of the default tab Header not of the content. This class basically facilitates you to have your default_tab header behave/look differently than the rest of the tabs if you so choose. (similar behaviour/looks by default)  

```
I don't see how any of your examples don't apply to the Accordion. In terms of concepts (I'm not talking about what's doable in the current implementation), why wouldn't I want the same content be displayed in different AccordionItems? Because the animation makes it look like they are different "drawers"? What if the animation was different, like a swipe over a fixed content?
```

The basic difference between the Accordion and the TabbedPanel is that TabbedPanel facilitates you to treat the Head of the Item(header+content) separately from the content. You are saying that you would like to see that functionality in accordion too?

```
What if the TabbedPanel had animations for switching tabs too.
```

The tabbed_panel_showcase.py does show how to add animation of your choice to tab switching. Did you try the example snippets I linked to? One of them links a tabbed panels Headers to a screenmanager's screens, retaining the animations  and the other does the same for  a carousel.

```
Don't you see they are the same in different clothing?
```

I see that as a failure at adequately explaining the difference in the documentation.

```
The widget is called "RstDocument" and the nav bar calls it "reStructuredText renderer". It's a bug in the websites, then :-)
```

I understand the reasoning behind giving the widget heading as a more descriptive name than the Widget name itself. Think of the people who are searching for widget's by functionality rather than name. You can still search by name through the search box. Regardless of what I or you think, we need more input on what is the better approach from the community and that can be only done by a discussion on a separate thread on ML/issue  not on this one.

```
TabbedPanel is the only widget using referencing. Every other widget uses nesting: Layouts, Accordion, even canvas. TabbedPanel's API is just not consistent with the rest of Kivy.
```

This might be a new concept but thanks to you the new introduction of  a `TabbedPanelItem` now facilitates the usage to be similar to the other widgets while still retaining the flexibility that is provided by referencing.

```
May be some of it should be in the documentation
```

Yes there is a new effort for documentation under way and I'll make it a point to explain this widget more clearly, I suggest you take the [survey](https://groups.google.com/forum/?fromgroups=#!topic/kivy-users/bBy3N14uIOo) if you haven't already 

> On 10/09/2012 04:30 AM, qua-non wrote:
> I can understand that you haven't come across a situation where you have ever needed the default_tab to behave different than others. This doesn't negate that others might as I have a need for it. Of-course we need to design the widget for most common needs and still provide a way to customize it for those who need to.
> ...
> Here again you are confusing the TabbedPanelHeader with content, The class default_tab_cls defines the class that controls behaviour of the default tab Header not of the content. This class basically facilitates you to have your default_tab header behave/look differently than the rest of the tabs if you so choose. (similar behaviour/looks by default)

I do understand that the current implementation of TabbedPanel makes the tab header the main focus of this widget. That's exactly what I'm objecting to! In UI design, hidden panel widgets (like tabs and accordions) are used with the panel content in mind, to present one panel at a time in a limited real estate.

I'm not saying that some application would not want to do special things with the tab header (or the accordion header, for that matter). This is a perfectly legitimate customization. What I'm saying is that it's extremely uncommon.

Instead of guessing all the possible customizations the UI could do, Kivy should let the UI subclass the Item (TabbedPanelItem or AccordionItem) for those who really want to, and not center the design of the API around this exotic customization option.

Since subclassing the existing widget classes is already a possibility, it seems unnecessary and counter-productive to have the design of this particular widget implement a completely different model for concepts that already exist in other widgets with a cleaner implementation, consitent with the otherwise very good and intuitive API design of Kivy.

Do you agree that if my application needed it, I could very easily subclass AccordionItem and do the things that TabbedPanelHeader provides?

> I'm not saying that some application would not want to do special things with the tab header (or the accordion header, for that matter). This is a perfectly legitimate customization. What I'm saying is that it's extremely uncommon.

This is where my opinion differs from yours. I see the customisation of a TabbedPanel's  Header as the most common customisation out there on a tabbed panel. Weather it maybe about having the header appear at a angle or adding images to it or standing tabs like in the find my cat flier or like in the browser closable headers. In fact I very rarely see a tabbed panel used without any customisation to the header. 

Regardless of weather you customize the header or not by default the tabs remain/act normally.

> I do understand that the current implementation of TabbedPanel makes the tab header the main focus of this widget. That's exactly what I'm objecting to! In UI design, hidden panel widgets (like tabs and accordions) are used with the panel content in mind, to present one panel at a time in a limited real estate.

If you prefer to think of this widget as a traditional tabbed panel there is nothing stopping you from doing that. In fact you can with the branch mentioned above Just use a `TabbedPanelItem`, which basically gives you the paradigm that you are looking for. Just because there is some added functionality available doesn't mean you have to use it.

I'm not outright dismissing what you are saying and the branch mentioned above is a direct result of you arguments. I'm just saying that you are one voice out of many users and we need to know what other users think/prefer.

Further-more, removing a functionality that was available before would mean abandoning support for those who use it in that manner. That prospect doesn't sound as enticing, although can be achieved by deprecating the older API, if majority users feel that way. 

Of course, at the end of the day you're free to design the widgets as you like. I still don't see why TabbedPanelItem cannot provide the functionnalities you want with TabbedPanelHeader, and not violate the principle of least astonisment.

I will try to make time and use TabbedPanelItem in my application in the future. Feel free to close this bug if/when you think your branch solves this issue.


Resolving patch:
diff --git a/examples/widgets/tabbed_panel_showcase.py b/examples/widgets/tabbed_panel_showcase.py
--- a/examples/widgets/tabbed_panel_showcase.py
+++ b/examples/widgets/tabbed_panel_showcase.py
@@ -54,8 +54,9 @@ class CloseableHeader(TabbedPanelHeader):
 <PanelLeft>
     size_hint: (.45, .45)
     pos_hint: {'center_x': .25, 'y': .55}
-    #replace the default tab with our custom tab
+    #replace the default tab with our custom tab class
     default_tab_cls: sh.__class__
+    do_default_tab: True
     default_tab_content: default_content
     tab_width: 40
     tab_height: 70
@@ -100,19 +101,18 @@ class CloseableHeader(TabbedPanelHeader):
             size_hint: None, 1
             orientation: 'vertical'
             width: 22
-            Widget:
-            Button:
-                border: 0,0,0,0
-                background_normal: 'tools/theming/defaulttheme/close.png'
-                on_release: root.panel.remove_widget(root)
-            Widget:
+            Image:
+                source: 'tools/theming/defaulttheme/close.png'
+                on_touch_down:
+                    if self.collide_point(*args[1].pos) :\
+                        root.panel.remove_widget(root)
 
 
 <PanelRight>
     tab_pos: 'top_right'
     size_hint: (.45, .45)
     pos_hint: {'center_x': .75, 'y': .55}
-    #replace the default tab with our custom tab
+    # replace the default tab with our custom tab
     default_tab: def_tab
     #allow variable tab width
     tab_width: None
@@ -170,29 +170,23 @@ class CloseableHeader(TabbedPanelHeader):
     tab_pos: 'bottom_left'
     size_hint: (.45, .45)
     pos_hint: {'center_x': .25, 'y': .02}
-    default_tab_text: 'Settings'
-    default_tab_content: default_content
-    FloatLayout:
+    do_default_tab: False
+
+    TabbedPanelItem:
+        id: settings
+        text: 'Settings'
         RstDocument:
-            id: default_content
             text: '\\n'.join(("Normal tabs", "-------------",\
-                "Tabs in \\'%s\\' position" %root.tab_pos))
-        Image:
-            id: tab_2_content
-            pos: self.parent.pos
-            size: self.parent.size
-            source: 'data/images/defaulttheme-0.png'
-        Image:
-            id: tab_3_content
-            pos:self.parent.pos
-            size: self.parent.size
-            source: 'data/images/image-loading.gif'
-    TabbedPanelHeader:
+            "Tabs in \\'%s\\' position" %root.tab_pos))
+    TabbedPanelItem:
         text: 'tab2'
-        content: tab_2_content
-    TabbedPanelHeader:
+        BubbleButton:
+            text: 'switch to settings'
+            on_press: root.switch_to(settings)
+    TabbedPanelItem:
         text: 'tab3'
-        content: tab_3_content
+        Image:
+            source: 'data/images/image-loading.gif'
 
 <PanelbRight>
     tab_pos: 'right_top'
@@ -257,26 +251,23 @@ class CloseableHeader(TabbedPanelHeader):
 
 
 class Tp(TabbedPanel):
+
     #override tab switching method to animate on tab switch
     def switch_to(self, header):
-        anim = Animation(color=(1, 1, 1, 0), d =.24, t = 'in_out_quad')
+        anim = Animation(opacity=0, d=.24, t='in_out_quad')
 
         def start_anim(_anim, child, in_complete, *lt):
-            if hasattr(child, 'color'):
-                _anim.start(child)
-            elif not in_complete:
-                _on_complete()
+            _anim.start(child)
 
         def _on_complete(*lt):
-            if hasattr(header.content, 'color'):
-                header.content.color = (0, 0, 0, 0)
-                anim = Animation(color =
-                    (1, 1, 1, 1), d =.23, t = 'in_out_quad')
+            if header.content:
+                header.content.opacity = 0
+                anim = Animation(opacity=1, d=.43, t='in_out_quad')
                 start_anim(anim, header.content, True)
             super(Tp, self).switch_to(header)
 
         anim.bind(on_complete = _on_complete)
-        if self.content:
+        if self.current_tab.content:
             start_anim(anim, self.current_tab.content, False)
         else:
             _on_complete()
diff --git a/examples/widgets/tabbedpanel.py b/examples/widgets/tabbedpanel.py
--- a/examples/widgets/tabbedpanel.py
+++ b/examples/widgets/tabbedpanel.py
@@ -15,30 +15,24 @@
 <Test>:
     size_hint: .5, .5
     pos_hint: {'center_x': .5, 'center_y': .5}
-    default_tab_content: set1_content
+    do_default_tab: False
 
-    Label:
-        id: set1_content
-        text: 'First tab content area'
-
-    BoxLayout:
-        id: set2_content
+    TabbedPanelItem:
+        text: 'first tab'
         Label:
-            text: 'Second tab content area'
-        Button:
-            text: 'Button that does nothing'
-
-    RstDocument:
-        id: set3_content
-        text: '\\n'.join(("Hello world", "-----------", "You are in the third tab."))
-
-    # now categorize widgets inserted above in a specific header
-    TabbedPanelHeader:
-        text: 'Tab 2'
-        content: set2_content
-    TabbedPanelHeader:
-        text: 'Tab 3'
-        content: set3_content
+            text: 'First tab content area'
+    TabbedPanelItem:
+        text: 'tab2'
+        BoxLayout:
+            Label:
+                text: 'Second tab content area'
+            Button:
+                text: 'Button that does nothing'
+    TabbedPanelItem:
+        text: 'tab3'
+        RstDocument:
+            text: '\\n'.join(("Hello world", "-----------", "You are in the third tab."))
+
 """)
 
 class Test(TabbedPanel):
diff --git a/kivy/uix/tabbedpanel.py b/kivy/uix/tabbedpanel.py
--- a/kivy/uix/tabbedpanel.py
+++ b/kivy/uix/tabbedpanel.py
@@ -23,6 +23,13 @@
 .. include:: ../../examples/widgets/tabbedpanel.py
     :literal:
 
+.. note::
+
+    A new Class :class:`TabbedPanelItem` has been introduced in 1.5.0 for
+    convineance. So now one can simply add a :class:`TabbedPanelItem` to a
+    :class:`TabbedPanel` and the `content` to the :class:`TabbedPanelItem`
+    like in the example provided above.
+
 Customize the Tabbed Panel
 --------------------------
 
@@ -45,23 +52,34 @@
 
     th.content = your_content_instance
 
-Note: There is one "shared" main content area, active at a given time, for all
+There is one "shared" main content area, active at a given time, for all
 the tabs. Your app is responsible for adding the content of individual tabs,
 and for managing it, but not for doing the content switching. The tabbed panel
 handles switching of the main content object, per user action.
 
-There is always a default tab added when the tabbed panel is instantiated.
+.. note::
+    The default_tab functionality is turned off by default since 1.5.0 to
+    turn it back on set `do_default_tab` = True.
+
+There is a default tab added when the tabbed panel is instantiated.
 Tabs that you add individually as above, are added in addition to the default
 tab. Thus, depending on your needs and design, you will want to customize the
-default tab, including its text::
+default tab::
 
     tp.default_tab_text = 'Something Specific To Your Use'
 
+
 The default tab machinery requires special consideration and management.
 Accordingly, an `on_default_tab` event is provided for associating a callback::
 
     tp.bind(on_default_tab = my_default_tab_callback)
 
+It's important to note that as by default :data:`default_tab_cls` is of type
+:class:`TabbedPanelHeader` it has the same properties as other tabs.
+
+Since 1.5.0 it is now possible to disable the creation of the
+:data:`default_tab` by setting :data:`do_default_tab` to False
+
 Tabs and content can be removed in several ways::
 
     tp.remove_widget(Widget/TabbedPanelHeader)
@@ -108,7 +126,7 @@
 '''
 
 __all__ = ('TabbedPanel', 'TabbedPanelContent', 'TabbedPanelHeader',
-    'TabbedPanelStrip', 'TabbedPanelException')
+    'TabbedPanelItem', 'TabbedPanelStrip', 'TabbedPanelException')
 
 from functools import partial
 from kivy.clock import Clock
@@ -121,7 +139,7 @@
 from kivy.uix.floatlayout import FloatLayout
 from kivy.logger import Logger
 from kivy.properties import ObjectProperty, StringProperty, OptionProperty, \
-        ListProperty, NumericProperty, AliasProperty
+        ListProperty, NumericProperty, AliasProperty, BooleanProperty
 
 
 class TabbedPanelException(Exception):
@@ -137,11 +155,11 @@ class TabbedPanelHeader(ToggleButton):
     You can use this TabbedPanelHeader widget to add a new tab to TabbedPanel.
     '''
 
-    content = ObjectProperty(None)
+    content = ObjectProperty(None, allownone=True)
     '''Content to be loaded when this tab header is selected.
 
     :data:`content` is a :class:`~kivy.properties.ObjectProperty` default
-    to None .
+    to None.
     '''
 
     # only allow selecting the tab if not already selected
@@ -154,13 +172,45 @@ def on_touch_down(self, touch):
         else:
             super(TabbedPanelHeader, self).on_touch_down(touch)
 
-    def on_release(self, *l):
-        parent = self.parent
-        while parent is not None and not isinstance(parent, TabbedPanel):
-            parent = parent.parent
-        if not parent:
+    def on_release(self, *largs):
+        # Tabbed panel header is a child of tab_strib which has a
+        # `tabbed_panel` property
+        if self.parent:
+            self.parent.tabbed_panel.switch_to(self)
+
+
+class TabbedPanelItem(TabbedPanelHeader):
+    '''This is a convenience class that provides a header of type
+    TabbedPanelHeader and links it with the content automatically. Thus
+    facilitating you to simply do the following in kv language::
+
+        <TabbedPanel>:
+            ...other settings
+            TabbedPanelItem:
+                BoxLayout:
+                    Label:
+                        text: 'Second tab content area'
+                    Button:
+                        text: 'Button that does nothing'
+
+    .. versionadded:: 1.5.0
+    '''
+
+    def add_widget(self, widget, index=0):
+        self.content = widget
+        if not self.parent:
+            return
+        panel = self.parent.tabbed_panel
+        if panel.current_tab == self:
+            panel.switch_to(self)
+
+    def remove_widget(self, widget):
+        self.content = None
+        if not self.parent:
             return
-        parent.switch_to(self)
+        panel = self.parent.tabbed_panel
+        if panel.current_tab == self:
+            panel.remove_widget(widget)
 
 
 class TabbedPanelStrip(GridLayout):
@@ -248,6 +298,15 @@ def get_current_tab(self):
     default to 100.
     '''
 
+    do_default_tab = BooleanProperty(True)
+    '''Specifies weather a default_tab head is provided.
+
+    .. versionadded:: 1.5.0
+
+    :data:`do_default_tab` is a :class:`~kivy.properties.BooleanProperty`,
+    defaults to 'True'.
+    '''
+
     default_tab_text = StringProperty('Default tab')
     '''Specifies the text displayed on the default tab header.
 
@@ -326,59 +385,37 @@ def set_def_tab_content(self, *l):
     '''
 
     def __init__(self, **kwargs):
+        # these variables need to be initialised before the kv lang is processed
+        # setup the base layout for the tabbed panel
         self._tab_layout = GridLayout(rows=1)
+        self.rows = 1
+        # bakground_image
         self._bk_img = Image(
             source=self.background_image, allow_stretch=True,
             keep_ratio=False, color=self.background_color)
         self._tab_strip = _tabs = TabbedPanelStrip(tabbed_panel=self,
             rows=1, cols=99, size_hint=(None, None),
             height=self.tab_height, width=self.tab_width)
-
         self._partial_update_scrollview = None
         self.content = content = TabbedPanelContent()
-
         self._current_tab = default_tab = self._original_tab \
             = self._default_tab = TabbedPanelHeader()
-        super(TabbedPanel, self).__init__(**kwargs)
 
-        content = self._default_tab.content
-        cls = self.default_tab_cls
-        if not issubclass(cls, TabbedPanelHeader):
-            raise TabbedPanelException('`default_tab_class` should be\
-                subclassed from `TabbedPanelHeader`')
-
-        if cls != type(default_tab):
-            self._default_tab = cls()
-        default_tab = self.default_tab
-        default_tab.text = self.default_tab_text
-        default_tab.height = self.tab_height
-        default_tab.group = '__tab%r__' % _tabs.uid
-        default_tab.state = 'down'
-        default_tab.width = self.tab_width if self.tab_width else 100
-        default_tab.content = content
-
-        tl = self.tab_list
-        if default_tab not in tl:
-            _tabs.add_widget(default_tab, len(tl))
-
-        if default_tab.content:
-            self.clear_widgets()
-            self.add_widget(self.default_tab.content)
-        else:
-            Clock.schedule_once(self._load_default_tab_content)
-        self._current_tab = default_tab
-        self.on_tab_pos()
+        super(TabbedPanel, self).__init__(**kwargs)
 
-    def _load_default_tab_content(self, dt):
+        self.bind(size=self._reposition_tabs)
+        if not self.do_default_tab:
+            Clock.schedule_once(self._switch_to_first_tab)
+            return
+        self._setup_default_tab()
         self.switch_to(self.default_tab)
 
-    def on_default_tab_text(self, *args):
-        self._default_tab.text = self.default_tab_text
-
     def switch_to(self, header):
         '''Switch to a specific panel header.
         '''
         header_content = header.content
+        self._current_tab.state = 'normal'
+        header.state = 'down'
         self._current_tab = header
         self.clear_widgets()
         if header_content is None:
@@ -389,14 +426,23 @@ def switch_to(self, header):
             parent.remove_widget(header_content)
         self.add_widget(header_content)
 
+    def clear_tabs(self, *l):
+        self_tabs = self._tab_strip
+        self_tabs.clear_widgets()
+        if self.do_default_tab:
+            self_default_tab = self._default_tab
+            self_tabs.add_widget(self_default_tab)
+            self_tabs.width = self_default_tab.width
+        self._reposition_tabs()
+
     def add_widget(self, widget, index=0):
         content = self.content
         if content is None:
             return
         parent = widget.parent
-        if widget.parent:
+        if parent:
             parent.remove_widget(widget)
-        if widget == content or widget == self._tab_layout:
+        if widget in (content, self._tab_layout):
             super(TabbedPanel, self).add_widget(widget, index)
         elif isinstance(widget, TabbedPanelHeader):
             self_tabs = self._tab_strip
@@ -411,7 +457,7 @@ def remove_widget(self, widget):
         content = self.content
         if content is None:
             return
-        if widget == content or widget == self._tab_layout:
+        if widget in (content, self._tab_layout):
             super(TabbedPanel, self).remove_widget(widget)
         elif isinstance(widget, TabbedPanelHeader):
             if widget != self._default_tab:
@@ -419,13 +465,15 @@ def remove_widget(self, widget):
                 self_tabs.width -= widget.width
                 self_tabs.remove_widget(widget)
                 if widget.state == 'down':
-                    self._default_tab.on_release()
-                self.reposition_tabs()
+                    if self.do_default_tab:
+                        self._default_tab.on_release()
+                self._reposition_tabs()
             else:
                 Logger.info('TabbedPanel: default tab! can\'t be removed.\n' +
                     'Change `default_tab` to a different tab.')
         else:
-            content.remove_widget(widget)
+            if widget in content.children:
+                content.remove_widget(widget)
 
     def clear_widgets(self, **kwargs):
         content = self.content
@@ -436,18 +484,6 @@ def clear_widgets(self, **kwargs):
         else:
             content.clear_widgets()
 
-    def clear_tabs(self, *l):
-        self_tabs = self._tab_strip
-        self_tabs.clear_widgets()
-        self_default_tab = self._default_tab
-        self_tabs.add_widget(self_default_tab)
-        self_tabs.width = self_default_tab.width
-        self.reposition_tabs()
-
-    def reposition_tabs(self, *l):
-        Clock.unschedule(self.on_tab_pos)
-        Clock.schedule_once(self.on_tab_pos, 0)
-
     def on_background_image(self, *l):
         self._bk_img.source = self.background_image
 
@@ -456,35 +492,84 @@ def on_background_color(self, *l):
             return
         self._bk_img.color = self.background_color
 
-    def on_tab_width(self, *l):
-        Clock.unschedule(self.update_tab_width)
-        Clock.schedule_once(self.update_tab_width, 0)
-
-    def update_tab_width(self, *l):
-        if self.tab_width:
-            for tab in self.tab_list:
-                tab.size_hint_x = 1
-            tsw = self.tab_width * len(self._tab_strip.children)
+    def on_do_default_tab(self, instance, value):
+        if not value:
+            dft = self.default_tab
+            if dft in self.tab_list:
+                self._default_tab = None
+                self.remove_widget(dft)
+                self._switch_to_first_tab()
         else:
-            # tab_width = None
-            tsw = 0
-            for tab in self.tab_list:
-                if tab.size_hint_x:
-                    # size_hint_x: x/.xyz
-                    tab.size_hint_x = 1
-                    #drop to default tab_width
-                    tsw += 100
-                else:
-                    # size_hint_x: None
-                    tsw += tab.width
-        self._tab_strip.width = tsw
-        self.reposition_tabs()
+            self._current_tab.state = 'normal'
+            self._setup_default_tab()
+
+    def on_default_tab_text(self, *args):
+        self._default_tab.text = self.default_tab_text
+
+    def on_tab_width(self, *l):
+        Clock.unschedule(self._update_tab_width)
+        Clock.schedule_once(self._update_tab_width, 0)
 
     def on_tab_height(self, *l):
         self._tab_layout.height = self._tab_strip.height = self.tab_height
-        self.reposition_tabs()
+        self._reposition_tabs()
 
     def on_tab_pos(self, *l):
+        # ensure canvas
+        self._reposition_tabs()
+
+    def _setup_default_tab(self):
+        if self._default_tab in self.tab_list:
+            return
+        content = self._default_tab.content
+        _tabs = self._tab_strip
+        cls = self.default_tab_cls
+
+        if not issubclass(cls, TabbedPanelHeader):
+            raise TabbedPanelException('`default_tab_class` should be\
+                subclassed from `TabbedPanelHeader`')
+
+        # no need to instanciate if class is TabbedPanelHeader
+        if cls != TabbedPanelHeader:
+            self._current_tab = self._original_tab = self._default_tab = cls()
+
+        default_tab = self.default_tab
+        if self._original_tab == self.default_tab:
+            default_tab.text = self.default_tab_text
+
+        default_tab.height = self.tab_height
+        default_tab.group = '__tab%r__' % _tabs.uid
+        default_tab.state = 'down'
+        default_tab.width = self.tab_width if self.tab_width else 100
+        default_tab.content = content
+
+        tl = self.tab_list
+        if default_tab not in tl:
+            _tabs.add_widget(default_tab, len(tl))
+
+        if default_tab.content:
+            self.clear_widgets()
+            self.add_widget(self.default_tab.content)
+        else:
+            Clock.schedule_once(self._load_default_tab_content)
+        self._current_tab = default_tab
+
+    def _switch_to_first_tab(self, *l):
+        ltl = len(self.tab_list) - 1
+        if ltl > -1:
+            self._current_tab = dt = self._original_tab \
+                = self.tab_list[ltl]
+            self.switch_to(dt)
+
+    def _load_default_tab_content(self, dt):
+        if self.default_tab:
+            self.switch_to(self.default_tab)
+
+    def _reposition_tabs(self, *l):
+        Clock.unschedule(self._update_tabs)
+        Clock.schedule_once(self._update_tabs, 0)
+
+    def _update_tabs(self, *l):
         self_content = self.content
         if not self_content:
             return
@@ -504,15 +589,9 @@ def on_tab_pos(self, *l):
         # update scrlv width when tab width changes depends on tab_pos
         if self._partial_update_scrollview is not None:
             tabs.unbind(width=self._partial_update_scrollview)
-            self.unbind(
-                width=self._partial_update_scrollview,
-                height=self._partial_update_scrollview)
         self._partial_update_scrollview = partial(
             self_update_scrollview, scrl_v)
         tabs.bind(width=self._partial_update_scrollview)
-        self.bind(
-            width=self._partial_update_scrollview,
-            height=self._partial_update_scrollview)
 
         # remove all widgets from the tab_strip
         self.clear_widgets(do_super=True)
@@ -561,7 +640,7 @@ def on_tab_pos(self, *l):
             self.cols = 2
             self.rows = 1
             # tab_layout contains two blank dummy widgets for spacing
-            #"vertically" and the scatter containing scrollview containing tabs
+            # "vertically" and the scatter containing scrollview containing tabs
             tab_layout.rows = 3
             tab_layout.cols = 1
             tab_layout.size_hint = (None, 1)
@@ -617,6 +696,26 @@ def on_tab_pos(self, *l):
         for widg in widget_list:
             add(widg)
 
+    def _update_tab_width(self, *l):
+        if self.tab_width:
+            for tab in self.tab_list:
+                tab.size_hint_x = 1
+            tsw = self.tab_width * len(self._tab_strip.children)
+        else:
+            # tab_width = None
+            tsw = 0
+            for tab in self.tab_list:
+                if tab.size_hint_x:
+                    # size_hint_x: x/.xyz
+                    tab.size_hint_x = 1
+                    #drop to default tab_width
+                    tsw += 100
+                else:
+                    # size_hint_x: None
+                    tsw += tab.width
+        self._tab_strip.width = tsw
+        self._reposition_tabs()
+
     def _update_top(self, *args):
         sctr, top, scrl_v_width, x, y = args
         Clock.unschedule(partial(self._updt_top, sctr, top, scrl_v_width))


Test patch: