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 #include "fifechan/widgets/dropdown.hpp" 66 67 #include "fifechan/exception.hpp" 68 #include "fifechan/font.hpp" 69 #include "fifechan/graphics.hpp" 70 #include "fifechan/key.hpp" 71 #include "fifechan/listmodel.hpp" 72 #include "fifechan/mouseinput.hpp" 73 #include "fifechan/widgets/listbox.hpp" 74 #include "fifechan/widgets/scrollarea.hpp" 75 76 namespace fcn 77 { DropDown(ListModel * listModel,ScrollArea * scrollArea,ListBox * listBox)78 DropDown::DropDown(ListModel *listModel, 79 ScrollArea *scrollArea, 80 ListBox *listBox) 81 { 82 setWidth(100); 83 setFocusable(true); 84 mDroppedDown = false; 85 mPushed = false; 86 mIsDragged = false; 87 88 setInternalFocusHandler(&mInternalFocusHandler); 89 90 mInternalScrollArea = (scrollArea == NULL); 91 mInternalListBox = (listBox == NULL); 92 93 if (mInternalScrollArea) 94 { 95 mScrollArea = new ScrollArea(); 96 } 97 else 98 { 99 mScrollArea = scrollArea; 100 } 101 102 if (mInternalListBox) 103 { 104 mListBox = new ListBox(); 105 } 106 else 107 { 108 mListBox = listBox; 109 } 110 111 mScrollArea->setContent(mListBox); 112 add(mScrollArea); 113 114 mListBox->addActionListener(this); 115 mListBox->addSelectionListener(this); 116 117 setListModel(listModel); 118 119 addMouseListener(this); 120 addKeyListener(this); 121 addFocusListener(this); 122 123 adjustHeight(); 124 } 125 ~DropDown()126 DropDown::~DropDown() 127 { 128 if (widgetExists(mListBox)) 129 { 130 mListBox->removeActionListener(this); 131 mListBox->removeSelectionListener(this); 132 } 133 134 if (mInternalScrollArea) 135 { 136 delete mScrollArea; 137 } 138 139 if (mInternalListBox) 140 { 141 delete mListBox; 142 } 143 144 setInternalFocusHandler(NULL); 145 } 146 draw(Graphics * graphics)147 void DropDown::draw(Graphics* graphics) 148 { 149 int h; 150 151 if (mDroppedDown) 152 { 153 h = mFoldedUpHeight; 154 } 155 else 156 { 157 h = getHeight(); 158 } 159 160 Color faceColor = getBaseColor(); 161 Color highlightColor, shadowColor; 162 int alpha = getBaseColor().a; 163 highlightColor = faceColor + 0x303030; 164 highlightColor.a = alpha; 165 shadowColor = faceColor - 0x303030; 166 shadowColor.a = alpha; 167 168 // Draw a border. 169 graphics->setColor(shadowColor); 170 graphics->drawLine(0, 0, getWidth() - 1, 0); 171 graphics->drawLine(0, 1, 0, h - 2); 172 graphics->setColor(highlightColor); 173 graphics->drawLine(getWidth() - 1, 1, getWidth() - 1, h - 1); 174 graphics->drawLine(0, h - 1, getWidth() - 1, h - 1); 175 176 // Push a clip area so the other drawings don't need to worry 177 // about the border. 178 graphics->pushClipArea(Rectangle(1, 1, getWidth() - 2, h - 2)); 179 const Rectangle currentClipArea = graphics->getCurrentClipArea(); 180 181 graphics->setColor(getBackgroundColor()); 182 graphics->fillRectangle(0, 0, currentClipArea.width, currentClipArea.height); 183 184 if (isFocused()) 185 { 186 graphics->setColor(getSelectionColor()); 187 graphics->fillRectangle(0, 188 0, 189 currentClipArea.width - currentClipArea.height, 190 currentClipArea.height); 191 graphics->setColor(getForegroundColor()); 192 } 193 194 if (mListBox->getListModel() 195 && mListBox->getSelected() >= 0) 196 { 197 graphics->setColor(getForegroundColor()); 198 graphics->setFont(getFont()); 199 200 graphics->drawText(mListBox->getListModel()->getElementAt(mListBox->getSelected()), 1, 0); 201 } 202 else if (mListBox->getListModel() 203 && mListBox->getSelected() < 0) 204 { 205 graphics->setColor(getForegroundColor()); 206 graphics->setFont(getFont()); 207 208 graphics->drawText("", 1, 0); 209 } 210 211 // Push a clip area before drawing the button. 212 graphics->pushClipArea(Rectangle(currentClipArea.width - currentClipArea.height, 213 0, 214 currentClipArea.height, 215 currentClipArea.height)); 216 drawButton(graphics); 217 graphics->popClipArea(); 218 graphics->popClipArea(); 219 220 if (mDroppedDown) 221 { 222 // Draw a border around the children. 223 graphics->setColor(shadowColor); 224 graphics->drawRectangle(0, 225 mFoldedUpHeight, 226 getWidth(), 227 getHeight() - mFoldedUpHeight); 228 //drawChildren(graphics); 229 } 230 } 231 drawButton(Graphics * graphics)232 void DropDown::drawButton(Graphics *graphics) 233 { 234 Color faceColor, highlightColor, shadowColor; 235 int offset; 236 int alpha = getBaseColor().a; 237 238 if (mPushed) 239 { 240 faceColor = getBaseColor() - 0x303030; 241 faceColor.a = alpha; 242 highlightColor = faceColor - 0x303030; 243 highlightColor.a = alpha; 244 shadowColor = faceColor + 0x303030; 245 shadowColor.a = alpha; 246 offset = 1; 247 } 248 else 249 { 250 faceColor = getBaseColor(); 251 faceColor.a = alpha; 252 highlightColor = faceColor + 0x303030; 253 highlightColor.a = alpha; 254 shadowColor = faceColor - 0x303030; 255 shadowColor.a = alpha; 256 offset = 0; 257 } 258 259 const Rectangle currentClipArea = graphics->getCurrentClipArea(); 260 graphics->setColor(highlightColor); 261 graphics->drawLine(0, 262 0, 263 currentClipArea.width - 1, 264 0); 265 graphics->drawLine(0, 266 1, 267 0, 268 currentClipArea.height - 1); 269 graphics->setColor(shadowColor); 270 graphics->drawLine(currentClipArea.width - 1, 271 1, 272 currentClipArea.width - 1, 273 currentClipArea.height - 1); 274 graphics->drawLine(1, 275 currentClipArea.height - 1, 276 currentClipArea.width - 2, 277 currentClipArea.height - 1); 278 279 graphics->setColor(faceColor); 280 graphics->fillRectangle(1, 281 1, 282 currentClipArea.width - 2, 283 currentClipArea.height - 2); 284 285 graphics->setColor(getForegroundColor()); 286 287 int i; 288 int n = currentClipArea.height / 3; 289 int dx = currentClipArea.height / 2; 290 int dy = (currentClipArea.height * 2) / 3; 291 for (i = 0; i < n; i++) 292 { 293 graphics->drawLine(dx - i + offset, 294 dy - i + offset, 295 dx + i + offset, 296 dy - i + offset); 297 } 298 } 299 getSelected() const300 int DropDown::getSelected() const 301 { 302 return mListBox->getSelected(); 303 } 304 setSelected(int selected)305 void DropDown::setSelected(int selected) 306 { 307 mListBox->setSelected(selected); 308 } 309 keyPressed(KeyEvent & keyEvent)310 void DropDown::keyPressed(KeyEvent& keyEvent) 311 { 312 if (keyEvent.isConsumed()) 313 return; 314 315 Key key = keyEvent.getKey(); 316 317 if ((key.getValue() == Key::Enter || key.getValue() == Key::Space) 318 && !mDroppedDown) 319 { 320 dropDown(); 321 keyEvent.consume(); 322 } 323 else if (key.getValue() == Key::Up) 324 { 325 setSelected(getSelected() - 1); 326 keyEvent.consume(); 327 } 328 else if (key.getValue() == Key::Down) 329 { 330 setSelected(getSelected() + 1); 331 keyEvent.consume(); 332 } 333 } 334 mousePressed(MouseEvent & mouseEvent)335 void DropDown::mousePressed(MouseEvent& mouseEvent) 336 { 337 // If we have a mouse press on the widget. 338 if (0 <= mouseEvent.getY() 339 && mouseEvent.getY() < getHeight() 340 && mouseEvent.getX() >= 0 341 && mouseEvent.getX() < getWidth() 342 && mouseEvent.getButton() == MouseEvent::Left 343 && !mDroppedDown 344 && mouseEvent.getSource() == this) 345 { 346 mPushed = true; 347 dropDown(); 348 requestModalMouseInputFocus(); 349 } 350 // Fold up the listbox if the upper part is clicked after fold down 351 else if (0 <= mouseEvent.getY() 352 && mouseEvent.getY() < mFoldedUpHeight 353 && mouseEvent.getX() >= 0 354 && mouseEvent.getX() < getWidth() 355 && mouseEvent.getButton() == MouseEvent::Left 356 && mDroppedDown 357 && mouseEvent.getSource() == this) 358 { 359 mPushed = false; 360 foldUp(); 361 releaseModalMouseInputFocus(); 362 } 363 // If we have a mouse press outside the widget 364 else if (0 > mouseEvent.getY() 365 || mouseEvent.getY() >= getHeight() 366 || mouseEvent.getX() < 0 367 || mouseEvent.getX() >= getWidth()) 368 { 369 mPushed = false; 370 foldUp(); 371 } 372 } 373 mouseReleased(MouseEvent & mouseEvent)374 void DropDown::mouseReleased(MouseEvent& mouseEvent) 375 { 376 if (mIsDragged) 377 { 378 mPushed = false; 379 } 380 381 // Released outside of widget. Can happen when we have modal input focus. 382 if ((0 > mouseEvent.getY() 383 || mouseEvent.getY() >= getHeight() 384 || mouseEvent.getX() < 0 385 || mouseEvent.getX() >= getWidth()) 386 && mouseEvent.getButton() == MouseEvent::Left 387 && isModalMouseInputFocused()) 388 { 389 releaseModalMouseInputFocus(); 390 391 if (mIsDragged) 392 { 393 foldUp(); 394 } 395 } 396 else if (mouseEvent.getButton() == MouseEvent::Left) 397 { 398 mPushed = false; 399 } 400 401 mIsDragged = false; 402 } 403 mouseDragged(MouseEvent & mouseEvent)404 void DropDown::mouseDragged(MouseEvent& mouseEvent) 405 { 406 mIsDragged = true; 407 408 mouseEvent.consume(); 409 } 410 setListModel(ListModel * listModel)411 void DropDown::setListModel(ListModel *listModel) 412 { 413 mListBox->setListModel(listModel); 414 415 adjustHeight(); 416 } 417 getListModel() const418 ListModel *DropDown::getListModel() const 419 { 420 return mListBox->getListModel(); 421 } 422 adjustHeight()423 void DropDown::adjustHeight() 424 { 425 if (mScrollArea == NULL) 426 { 427 throw FCN_EXCEPTION("Scroll area has been deleted."); 428 } 429 430 if (mListBox == NULL) 431 { 432 throw FCN_EXCEPTION("List box has been deleted."); 433 } 434 435 int listBoxHeight = mListBox->getHeight(); 436 437 // We add 2 for the border 438 int h2 = getFont()->getHeight() + 2; 439 440 setHeight(h2); 441 442 // The addition/subtraction of 2 compensates for the seperation lines 443 // seperating the selected element view and the scroll area. 444 445 if (mDroppedDown && getParent()) 446 { 447 int h = getParent()->getChildrenArea().height - getY(); 448 449 if (listBoxHeight > h - h2 - 2) 450 { 451 mScrollArea->setHeight(h - h2 - 2); 452 setHeight(h); 453 } 454 else 455 { 456 setHeight(listBoxHeight + h2 + 2); 457 mScrollArea->setHeight(listBoxHeight); 458 } 459 } 460 461 mScrollArea->setWidth(getWidth()); 462 // Resize the ListBox to exactly fit the ScrollArea. 463 mListBox->setWidth(mScrollArea->getChildrenArea().width); 464 mScrollArea->setPosition(0, 0); 465 } 466 resizeToContent(bool recursiv)467 void DropDown::resizeToContent(bool recursiv) { 468 if (mScrollArea != NULL) { 469 mScrollArea->resizeToContent(); 470 } 471 472 if (mListBox != NULL) { 473 mScrollArea->resizeToContent(); 474 } 475 adjustHeight(); 476 } 477 adjustSize()478 void DropDown::adjustSize() { 479 adjustHeight(); 480 } 481 dropDown()482 void DropDown::dropDown() 483 { 484 if (!mDroppedDown) 485 { 486 mDroppedDown = true; 487 mFoldedUpHeight = getHeight(); 488 adjustHeight(); 489 490 491 if (getParent()) 492 { 493 getParent()->moveToTop(this); 494 } 495 } 496 497 mListBox->requestFocus(); 498 } 499 foldUp()500 void DropDown::foldUp() 501 { 502 if (mDroppedDown) 503 { 504 mDroppedDown = false; 505 adjustHeight(); 506 mInternalFocusHandler.focusNone(); 507 } 508 } 509 focusLost(const Event & event)510 void DropDown::focusLost(const Event& event) 511 { 512 foldUp(); 513 mInternalFocusHandler.focusNone(); 514 } 515 516 death(const Event & event)517 void DropDown::death(const Event& event) 518 { 519 if (event.getSource() == mScrollArea) 520 { 521 mScrollArea = NULL; 522 } 523 } 524 action(const ActionEvent & actionEvent)525 void DropDown::action(const ActionEvent& actionEvent) 526 { 527 foldUp(); 528 releaseModalMouseInputFocus(); 529 distributeActionEvent(); 530 } 531 getChildrenArea()532 Rectangle DropDown::getChildrenArea() 533 { 534 if (mDroppedDown) 535 { 536 // Calculate the children area (with the one pixel border in mind) 537 return Rectangle(1, 538 mFoldedUpHeight + 1, 539 getWidth() - 2, 540 getHeight() - mFoldedUpHeight - 2); 541 } 542 543 return Rectangle(); 544 } 545 setBaseColor(const Color & color)546 void DropDown::setBaseColor(const Color& color) 547 { 548 if (mInternalScrollArea) 549 { 550 mScrollArea->setBaseColor(color); 551 } 552 553 if (mInternalListBox) 554 { 555 mListBox->setBaseColor(color); 556 } 557 558 Widget::setBaseColor(color); 559 } 560 setBackgroundColor(const Color & color)561 void DropDown::setBackgroundColor(const Color& color) 562 { 563 if (mInternalScrollArea) 564 { 565 mScrollArea->setBackgroundColor(color); 566 } 567 568 if (mInternalListBox) 569 { 570 mListBox->setBackgroundColor(color); 571 } 572 573 Widget::setBackgroundColor(color); 574 } 575 setForegroundColor(const Color & color)576 void DropDown::setForegroundColor(const Color& color) 577 { 578 if (mInternalScrollArea) 579 { 580 mScrollArea->setForegroundColor(color); 581 } 582 583 if (mInternalListBox) 584 { 585 mListBox->setForegroundColor(color); 586 } 587 588 Widget::setForegroundColor(color); 589 } 590 setFont(Font * font)591 void DropDown::setFont(Font *font) 592 { 593 if (mInternalScrollArea) 594 { 595 mScrollArea->setFont(font); 596 } 597 598 if (mInternalListBox) 599 { 600 mListBox->setFont(font); 601 } 602 603 Widget::setFont(font); 604 } 605 mouseWheelMovedUp(MouseEvent & mouseEvent)606 void DropDown::mouseWheelMovedUp(MouseEvent& mouseEvent) 607 { 608 if (isFocused() && mouseEvent.getSource() == this) 609 { 610 mouseEvent.consume(); 611 612 if (mListBox->getSelected() > 0) 613 { 614 mListBox->setSelected(mListBox->getSelected() - 1); 615 } 616 } 617 } 618 mouseWheelMovedDown(MouseEvent & mouseEvent)619 void DropDown::mouseWheelMovedDown(MouseEvent& mouseEvent) 620 { 621 if (isFocused() && mouseEvent.getSource() == this) 622 { 623 mouseEvent.consume(); 624 625 mListBox->setSelected(mListBox->getSelected() + 1); 626 } 627 } 628 setSelectionColor(const Color & color)629 void DropDown::setSelectionColor(const Color& color) 630 { 631 Widget::setSelectionColor(color); 632 633 if (mInternalListBox) 634 { 635 mListBox->setSelectionColor(color); 636 } 637 } 638 valueChanged(const SelectionEvent & event)639 void DropDown::valueChanged(const SelectionEvent& event) 640 { 641 distributeValueChangedEvent(); 642 } 643 addSelectionListener(SelectionListener * selectionListener)644 void DropDown::addSelectionListener(SelectionListener* selectionListener) 645 { 646 mSelectionListeners.push_back(selectionListener); 647 } 648 removeSelectionListener(SelectionListener * selectionListener)649 void DropDown::removeSelectionListener(SelectionListener* selectionListener) 650 { 651 mSelectionListeners.remove(selectionListener); 652 } 653 distributeValueChangedEvent()654 void DropDown::distributeValueChangedEvent() 655 { 656 SelectionListenerIterator iter; 657 658 for (iter = mSelectionListeners.begin(); iter != mSelectionListeners.end(); ++iter) 659 { 660 SelectionEvent event(this); 661 (*iter)->valueChanged(event); 662 } 663 } 664 } 665 666