1 /***************************************************************************
2  *   Copyright (c) 2017-2019 by the fifechan team                               *
3  *   https://github.com/fifengine/fifechan                                 *
4  *   This file is part of fifechan.                                        *
5  *                                                                         *
6  *   fifechan is free software; you can redistribute it and/or             *
7  *   modify it under the terms of the GNU Lesser General Public            *
8  *   License as published by the Free Software Foundation; either          *
9  *   version 2.1 of the License, or (at your option) any later version.    *
10  *                                                                         *
11  *   This library is distributed in the hope that it will be useful,       *
12  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
13  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU     *
14  *   Lesser General Public License for more details.                       *
15  *                                                                         *
16  *   You should have received a copy of the GNU Lesser General Public      *
17  *   License along with this library; if not, write to the                 *
18  *   Free Software Foundation, Inc.,                                       *
19  *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA          *
20  ***************************************************************************/
21 
22 /*      _______   __   __   __   ______   __   __   _______   __   __
23  *     / _____/\ / /\ / /\ / /\ / ____/\ / /\ / /\ / ___  /\ /  |\/ /\
24  *    / /\____\// / // / // / // /\___\// /_// / // /\_/ / // , |/ / /
25  *   / / /__   / / // / // / // / /    / ___  / // ___  / // /| ' / /
26  *  / /_// /\ / /_// / // / // /_/_   / / // / // /\_/ / // / |  / /
27  * /______/ //______/ //_/ //_____/\ /_/ //_/ //_/ //_/ //_/ /|_/ /
28  * \______\/ \______\/ \_\/ \_____\/ \_\/ \_\/ \_\/ \_\/ \_\/ \_\/
29  *
30  * Copyright (c) 2004 - 2008 Olof Naess�n and Per Larsson
31  *
32  *
33  * Per Larsson a.k.a finalman
34  * Olof Naess�n a.k.a jansem/yakslem
35  *
36  * Visit: http://guichan.sourceforge.net
37  *
38  * License: (BSD)
39  * Redistribution and use in source and binary forms, with or without
40  * modification, are permitted provided that the following conditions
41  * are met:
42  * 1. Redistributions of source code must retain the above copyright
43  *    notice, this list of conditions and the following disclaimer.
44  * 2. Redistributions in binary form must reproduce the above copyright
45  *    notice, this list of conditions and the following disclaimer in
46  *    the documentation and/or other materials provided with the
47  *    distribution.
48  * 3. Neither the name of Guichan nor the names of its contributors may
49  *    be used to endorse or promote products derived from this software
50  *    without specific prior written permission.
51  *
52  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
53  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
54  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
55  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
56  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
57  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
58  * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
59  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
60  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
61  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
62  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
63  */
64 
65 /*
66  * For comments regarding functions please see the header file.
67  */
68 
69 #include "fifechan/widgets/tabbedarea.hpp"
70 
71 #include "fifechan/exception.hpp"
72 #include "fifechan/focushandler.hpp"
73 #include "fifechan/font.hpp"
74 #include "fifechan/graphics.hpp"
75 
76 #include "fifechan/widgets/tab.hpp"
77 
78 #include <algorithm>
79 
80 namespace fcn
81 {
TabbedArea()82     TabbedArea::TabbedArea()
83             :mSelectedTab(NULL),
84              mOpaque(false)
85     {
86         setFocusable(true);
87         addKeyListener(this);
88         addMouseListener(this);
89 
90         mTabContainer = new Container();
91         mTabContainer->setOpaque(false);
92         mTabContainer->setLayout(Container::Horizontal);
93         mWidgetContainer = new Container();
94         mWidgetContainer->setLayout(Container::Vertical);
95         add(mTabContainer);
96         add(mWidgetContainer);
97     }
98 
~TabbedArea()99     TabbedArea::~TabbedArea()
100     {
101         remove(mTabContainer);
102         remove(mWidgetContainer);
103 
104         delete mTabContainer;
105         delete mWidgetContainer;
106 
107         for (unsigned int i = 0; i < mTabsToDelete.size(); i++)
108         {
109             delete mTabsToDelete[i];
110         }
111     }
112 
addTab(Tab * tab,Widget * widget)113     void TabbedArea::addTab(Tab* tab, Widget* widget)
114     {
115         tab->setTabbedArea(this);
116         tab->addActionListener(this);
117         if (tab->getLayout() == Container::Absolute) {
118             tab->setLayout(getLayout());
119         }
120         mTabContainer->add(tab);
121         mTabs.push_back(std::pair<Tab*, Widget*>(tab, widget));
122 
123         if (mSelectedTab == NULL) {
124             setSelectedTab(tab);
125         } else {
126             adaptLayout();
127         }
128     }
129 
removeTabWithIndex(unsigned int index)130     void TabbedArea::removeTabWithIndex(unsigned int index)
131     {
132         if (index >= mTabs.size())
133         {
134             throw FCN_EXCEPTION("No such tab index.");
135         }
136 
137         removeTab(mTabs[index].first);
138     }
139 
removeTab(Tab * tab)140     void TabbedArea::removeTab(Tab* tab)
141     {
142         int tabIndexToBeSelected = - 1;
143 
144         if (tab == mSelectedTab)
145         {
146             int index = getSelectedTabIndex();
147 
148             if (index == (int)mTabs.size() - 1
149                 && mTabs.size() >= 2)
150             {
151                 tabIndexToBeSelected = index-1;
152             }
153             else if (index == (int)mTabs.size() - 1
154                      && mTabs.size() == 1)
155             {
156                 tabIndexToBeSelected = -1;
157             }
158             else
159             {
160                 tabIndexToBeSelected = index;
161             }
162         }
163 
164         std::vector<std::pair<Tab*, Widget*> >::iterator iter;
165         for (iter = mTabs.begin(); iter != mTabs.end(); iter++)
166         {
167             if (iter->first == tab)
168             {
169                 mTabContainer->remove(tab);
170                 mTabs.erase(iter);
171                 break;
172             }
173         }
174 
175         std::vector<Tab*>::iterator iter2;
176         for (iter2 = mTabsToDelete.begin(); iter2 != mTabsToDelete.end(); iter2++)
177         {
178             if (*iter2 == tab)
179             {
180                 mTabsToDelete.erase(iter2);
181                 delete tab;
182                 break;
183             }
184         }
185 
186         if (tabIndexToBeSelected == -1)
187         {
188             mSelectedTab = NULL;
189             mWidgetContainer->clear();
190             adaptLayout();
191         }
192         else
193         {
194             mWidgetContainer->clear();
195             setSelectedTab(tabIndexToBeSelected);
196         }
197     }
198 
getNumberOfTabs() const199     int TabbedArea::getNumberOfTabs() const
200     {
201         return mTabs.size();
202     }
203 
isTabSelected(unsigned int index) const204     bool TabbedArea::isTabSelected(unsigned int index) const
205     {
206         if (index >= mTabs.size())
207         {
208             throw FCN_EXCEPTION("No such tab index.");
209         }
210 
211         return mSelectedTab == mTabs[index].first;
212     }
213 
isTabSelected(Tab * tab) const214     bool TabbedArea::isTabSelected(Tab* tab) const
215     {
216         return mSelectedTab == tab;
217     }
218 
setSelectedTab(unsigned int index)219     void TabbedArea::setSelectedTab(unsigned int index)
220     {
221         if (index >= mTabs.size())
222         {
223             throw FCN_EXCEPTION("No such tab index.");
224         }
225 
226         setSelectedTab(mTabs[index].first);
227     }
228 
setSelectedTab(Tab * tab)229     void TabbedArea::setSelectedTab(Tab* tab)
230     {
231         if (tab == mSelectedTab) {
232             return;
233         }
234         unsigned int i;
235         for (i = 0; i < mTabs.size(); i++)
236         {
237             if (mTabs[i].first == mSelectedTab)
238             {
239                 mWidgetContainer->remove(mTabs[i].second);
240             }
241         }
242 
243         for (i = 0; i < mTabs.size(); i++)
244         {
245             if (mTabs[i].first == tab)
246             {
247                 mSelectedTab = tab;
248                 mWidgetContainer->add(mTabs[i].second);
249             }
250         }
251         adaptLayout();
252     }
253 
getSelectedTabIndex() const254     int TabbedArea::getSelectedTabIndex() const
255     {
256         unsigned int i;
257         for (i = 0; i < mTabs.size(); i++)
258         {
259             if (mTabs[i].first == mSelectedTab)
260             {
261                 return i;
262             }
263         }
264 
265         return -1;
266     }
267 
getSelectedTab() const268     Tab* TabbedArea::getSelectedTab() const
269     {
270         return mSelectedTab;
271     }
272 
setOpaque(bool opaque)273     void TabbedArea::setOpaque(bool opaque)
274     {
275         mOpaque = opaque;
276     }
277 
isOpaque() const278     bool TabbedArea::isOpaque() const
279     {
280         return mOpaque;
281     }
282 
setBackgroundWidget(Widget * widget)283     void TabbedArea::setBackgroundWidget(Widget* widget) {
284         mTabContainer->setBackgroundWidget(widget);
285         mWidgetContainer->setBackgroundWidget(widget);
286     }
287 
getBackgroundWidget()288     Widget* TabbedArea::getBackgroundWidget() {
289         return mTabContainer->getBackgroundWidget();
290     }
291 
draw(Graphics * graphics)292     void TabbedArea::draw(Graphics *graphics)
293     {
294         const Color &faceColor = getBaseColor();
295         const int alpha = getBaseColor().a;
296         Color highlightColor = faceColor + 0x303030;
297         highlightColor.a = alpha;
298         Color shadowColor = faceColor - 0x303030;
299         shadowColor.a = alpha;
300 
301         // Draw a border.
302         graphics->setColor(highlightColor);
303         graphics->drawLine(0,
304                            mTabContainer->getHeight(),
305                            0,
306                            getHeight() - 2);
307         graphics->setColor(shadowColor);
308         graphics->drawLine(getWidth() - 1,
309                            mTabContainer->getHeight() + 1,
310                            getWidth() - 1,
311                            getHeight() - 1);
312         graphics->drawLine(1,
313                            getHeight() - 1,
314                            getWidth() - 1,
315                            getHeight() - 1);
316 
317         if (isOpaque())
318         {
319             graphics->setColor(getBaseColor());
320             graphics->fillRectangle(1, 1,
321                                     getWidth() - 2,
322                                     getHeight() - 2);
323         }
324 
325         // Draw a line underneath the tabs.
326         graphics->setColor(highlightColor);
327         graphics->drawLine(1,
328                            mTabContainer->getHeight(),
329                            getWidth() - 1,
330                            mTabContainer->getHeight());
331 
332         // If a tab is selected, remove the line right underneath
333         // the selected tab.
334         if (mSelectedTab != NULL)
335         {
336             graphics->setColor(getBaseColor());
337             graphics->drawLine(mSelectedTab->getX() + 1,
338                                mTabContainer->getHeight(),
339                                mSelectedTab->getX() + mSelectedTab->getWidth() - 2,
340                                mTabContainer->getHeight());
341 
342         }
343 
344         //drawChildren(graphics);
345     }
346 
getChildrenArea()347     Rectangle TabbedArea::getChildrenArea() {
348         Rectangle rec;
349         rec.x = getBorderSize() + getPaddingLeft();
350         rec.y = getBorderSize() + getPaddingTop();
351         rec.width = getWidth() - 2 * getBorderSize() - getPaddingLeft() - getPaddingRight();
352         rec.height = getHeight() - 2 * getBorderSize() - getPaddingTop() - getPaddingBottom();
353         return rec;
354     }
355 
resizeToContent(bool recursiv)356     void TabbedArea::resizeToContent(bool recursiv) {
357         if (recursiv) {
358             mTabContainer->resizeToContent(recursiv);
359             mWidgetContainer->resizeToContent(recursiv);
360         }
361         resizeToChildren();
362         adjustSize();
363         adjustTabPositions();
364     }
365 
expandContent(bool recursiv)366     void TabbedArea::expandContent(bool recursiv) {
367         if (recursiv) {
368             mTabContainer->expandContent(recursiv);
369             mWidgetContainer->expandContent(recursiv);
370         }
371         adjustSize();
372         adjustTabPositions();
373     }
374 
adjustSize()375     void TabbedArea::adjustSize()
376     {
377         int totalTabWidth = 0;
378         int totalTabHeight = 0;
379         int maxTabWidth = 0;
380         int maxTabHeight = 0;
381 
382         Rectangle area = getChildrenArea();
383         for (unsigned int i = 0; i < mTabs.size(); i++)
384         {
385             totalTabWidth += mTabs[i].first->getWidth();
386             totalTabHeight += mTabs[i].first->getHeight();
387             if (mTabs[i].first->getWidth() > maxTabWidth)
388             {
389                 maxTabWidth = mTabs[i].first->getWidth();
390             }
391             if (mTabs[i].first->getHeight() > maxTabHeight)
392             {
393                 maxTabHeight = mTabs[i].first->getHeight();
394             }
395         }
396 
397         if (getLayout() == Container::Vertical) {
398             mTabContainer->setSize(maxTabWidth, getHeight() - 2);
399             mWidgetContainer->setSize(getWidth() - maxTabWidth - 2, getHeight() - 2);
400             mWidgetContainer->setPosition(maxTabWidth + 1, 1);
401         } else if (getLayout() == Container::Horizontal) {
402             mTabContainer->setSize(getWidth() - 2, maxTabHeight);
403             mWidgetContainer->setSize(getWidth() - 2, getHeight() - maxTabHeight - 2);
404             mWidgetContainer->setPosition(1, maxTabHeight + 1);
405         }
406     }
407 
adjustTabPositions()408     void TabbedArea::adjustTabPositions()
409     {
410         int maxTabWidth = 0;
411         int maxTabHeight = 0;
412         unsigned int i;
413         for (i = 0; i < mTabs.size(); i++)
414         {
415             if (mTabs[i].first->getWidth() > maxTabWidth)
416             {
417                 maxTabWidth = mTabs[i].first->getWidth();
418             }
419             if (mTabs[i].first->getHeight() > maxTabHeight)
420             {
421                 maxTabHeight = mTabs[i].first->getHeight();
422             }
423         }
424 
425         if (getLayout() == Container::Vertical) {
426             int y = 0;
427             for (i = 0; i < mTabs.size(); i++)
428             {
429                 Tab* tab = mTabs[i].first;
430                 tab->setPosition(maxTabWidth - tab->getWidth(), y);
431                 y += tab->getHeight();
432             }
433         } else if (getLayout() == Container::Horizontal) {
434             int x = 0;
435             for (i = 0; i < mTabs.size(); i++)
436             {
437                 Tab* tab = mTabs[i].first;
438                 tab->setPosition(x, maxTabHeight - tab->getHeight());
439                 x += tab->getWidth();
440             }
441         }
442     }
443 
setWidth(int width)444     void TabbedArea::setWidth(int width)
445     {
446         // This may seem odd, but we want the TabbedArea to adjust
447         // it's size properly before we call Widget::setWidth as
448         // Widget::setWidth might distribute a resize event.
449         fcn::Rectangle dim = mDimension;
450         mDimension.width = width;
451         adjustSize();
452         mDimension = dim;
453         Widget::setWidth(width);
454     }
455 
setHeight(int height)456     void TabbedArea::setHeight(int height)
457     {
458         // This may seem odd, but we want the TabbedArea to adjust
459         // it's size properly before we call Widget::setHeight as
460         // Widget::setHeight might distribute a resize event.
461         fcn::Rectangle dim = mDimension;
462         mDimension.height = height;
463         adjustSize();
464         mDimension = dim;
465         Widget::setHeight(height);
466     }
467 
setSize(int width,int height)468     void TabbedArea::setSize(int width, int height)
469     {
470         // This may seem odd, but we want the TabbedArea to adjust
471         // it's size properly before we call Widget::setSize as
472         // Widget::setSize might distribute a resize event.
473         fcn::Rectangle dim = mDimension;
474         mDimension.width = width;
475         mDimension.height = height;
476         adjustSize();
477         mDimension = dim;
478         Widget::setSize(width, height);
479     }
480 
setDimension(const Rectangle & dimension)481     void TabbedArea::setDimension(const Rectangle& dimension)
482     {
483         // This may seem odd, but we want the TabbedArea to adjust
484         // it's size properly before we call Widget::setDimension as
485         // Widget::setDimension might distribute a resize event.
486         fcn::Rectangle dim = mDimension;
487         mDimension = dimension;
488         adjustSize();
489         mDimension = dim;
490         Widget::setDimension(dimension);
491     }
492 
keyPressed(KeyEvent & keyEvent)493     void TabbedArea::keyPressed(KeyEvent& keyEvent)
494     {
495         if (keyEvent.isConsumed() || !isFocused())
496         {
497             return;
498         }
499 
500         if (keyEvent.getKey().getValue() == Key::Left)
501         {
502             int index = getSelectedTabIndex();
503             index--;
504 
505             if (index < 0)
506             {
507                 return;
508             }
509             else
510             {
511                 setSelectedTab(mTabs[index].first);
512             }
513 
514             keyEvent.consume();
515         }
516         else if (keyEvent.getKey().getValue() == Key::Right)
517         {
518             int index = getSelectedTabIndex();
519             index++;
520 
521             if (index >= (int)mTabs.size())
522             {
523                 return;
524             }
525             else
526             {
527                 setSelectedTab(mTabs[index].first);
528             }
529 
530             keyEvent.consume();
531         }
532     }
533 
534 
mousePressed(MouseEvent & mouseEvent)535     void TabbedArea::mousePressed(MouseEvent& mouseEvent)
536     {
537         // we ignore that, otherwise the tab can not be pressed
538         // because the content consumed the event
539         //if (mouseEvent.isConsumed())
540         //{
541         //    return;
542         //}
543         if (mouseEvent.getButton() == MouseEvent::Left)
544         {
545             Widget* widget = mTabContainer->getWidgetAt(mouseEvent.getX(), mouseEvent.getY());
546             Tab* tab = dynamic_cast<Tab*>(widget);
547 
548             if (tab != NULL)
549             {
550                 setSelectedTab(tab);
551             }
552         }
553 
554         // Request focus only if the source of the event
555         // is not focusble. If the source of the event
556         // is focused we don't want to steal the focus.
557         if (!mouseEvent.getSource()->isFocusable())
558         {
559             requestFocus();
560         }
561     }
562 
death(const Event & event)563     void TabbedArea::death(const Event& event)
564     {
565         Tab* tab = dynamic_cast<Tab*>(event.getSource());
566 
567         if (tab != NULL)
568         {
569             removeTab(tab);
570         }
571         else
572         {
573             //BasicContainer::death(event);
574         }
575     }
576 
action(const ActionEvent & actionEvent)577     void TabbedArea::action(const ActionEvent& actionEvent)
578     {
579         Widget* source = actionEvent.getSource();
580         Tab* tab = dynamic_cast<Tab*>(source);
581 
582         if (tab == NULL)
583         {
584             throw FCN_EXCEPTION("Received an action from a widget that's not a tab!");
585         }
586 
587         setSelectedTab(tab);
588     }
589 
setBaseColor(const Color & color)590     void TabbedArea::setBaseColor(const Color& color)
591     {
592         Widget::setBaseColor(color);
593         mWidgetContainer->setBaseColor(color);
594         mTabContainer->setBaseColor(color);
595     }
596 
setLayout(Container::LayoutPolicy policy)597     void TabbedArea::setLayout(Container::LayoutPolicy policy) {
598         mTabContainer->setLayout(policy);
599     }
600 
getLayout() const601     Container::LayoutPolicy TabbedArea::getLayout() const {
602         return mTabContainer->getLayout();
603     }
604 
setUniformSize(bool uniform)605     void TabbedArea::setUniformSize(bool uniform) {
606         mTabContainer->setUniformSize(uniform);
607     }
608 
isUniformSize() const609     bool TabbedArea::isUniformSize() const {
610         return mTabContainer->isUniformSize();
611     }
612 
setVerticalSpacing(unsigned int spacing)613     void TabbedArea::setVerticalSpacing(unsigned int spacing) {
614         mTabContainer->setVerticalSpacing(spacing);
615     }
616 
getVerticalSpacing() const617     unsigned int TabbedArea::getVerticalSpacing() const {
618         return mTabContainer->getVerticalSpacing();
619     }
620 
setHorizontalSpacing(unsigned int spacing)621     void TabbedArea::setHorizontalSpacing(unsigned int spacing) {
622         mTabContainer->setHorizontalSpacing(spacing);
623     }
624 
getHorizontalSpacing() const625     unsigned int TabbedArea::getHorizontalSpacing() const {
626         return mTabContainer->getHorizontalSpacing();
627     }
628 }
629