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