Changeset 8619 in orxonox.OLD for trunk/src/lib/gui/gl/glgui_widget.cc
- Timestamp:
- Jun 20, 2006, 1:24:11 PM (18 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/lib/gui/gl/glgui_widget.cc
r8448 r8619 14 14 */ 15 15 16 #define DEBUG_SPECIAL_MODULE DEBUG_MODULE_GUI16 #define DEBUG_SPECIAL_MODULE 4//DEBUG_MODULE_GUI 17 17 18 18 #include "glgui_widget.h" … … 23 23 24 24 #include "debug.h" 25 26 #include "loading/load_param.h" 25 27 26 28 namespace OrxGui … … 39 41 40 42 /** 43 * @brief loads Parameters for a Style from XML 44 * @param root the XML-Element to load from. 45 */ 46 void GLGuiWidget::loadParams(const TiXmlElement* root) 47 { 48 49 /// STYLE 50 LoadParam(root, "border-left", this, GLGuiWidget, setBorderLeft); 51 LoadParam(root, "border-right", this, GLGuiWidget, setBorderRight); 52 LoadParam(root, "border-top", this, GLGuiWidget, setBorderTop); 53 LoadParam(root, "border-bottom", this, GLGuiWidget, setBorderBottom); 54 55 LoadParam(root, "text-size", this, GLGuiWidget, setTextSize); 56 LoadParam(root, "background-color", this, GLGuiWidget, setBackgroundColorS); 57 LoadParam(root, "foreground-color", this, GLGuiWidget, setForegroundColorS); 58 59 // LoadParamXML(root, "backmat", this, GLGuiWidget, loadBackgroundMaterial); 60 // LoadParamXML(root, "frontmat", this, GLGuiWidget, loadForegroundMaterial); 61 62 LoadParam(root, "feature-position", this, GLGuiWidget, setFeaturePositionS); 63 LoadParam(root, "Font", this, GLGuiWidget, setFont); 64 65 LoadParam(root, "animated-state-changes", this, GLGuiWidget, setAnimatedStateChanges); 66 } 67 68 69 /** 41 70 * @brief standard deconstructor 42 71 */ … … 45 74 if (this == GLGuiWidget::_focused) 46 75 GLGuiWidget::_focused = NULL; 47 48 if (this->_toFrontColor)49 delete this->_toFrontColor;50 76 } 51 77 … … 66 92 this->_clickable = false; 67 93 this->_pushed = false; 94 this->_state = OrxGui::Normal; 95 96 97 this->_font = NULL; 98 this->resetStyle(); 99 100 this->_animating = false; 101 this->_animationCycle = 0.0; 102 this->_animationDuration = 1.0; 103 104 105 this->setBackgroundColor(Color(.51, .3, .3, .5)); 106 this->setBackgroundColor(Color(.3, .5, .3, 1), OrxGui::Selected); 107 this->_style[0]._background.setBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 108 this->_style[1]._background.setBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 109 this->_style[2]._background.setBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 110 this->_style[3]._background.setBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 111 112 this->setForegroundColor(Color(1, 0, 0, 1), OrxGui::Normal); 113 this->setForegroundColor(Color(0, 0, 1, 1), OrxGui::Selected); 114 this->setForegroundColor(Color(0, 1, 0, 1), OrxGui::Focused); 115 this->setForegroundColor(Color(.1, .1, .1, 1), OrxGui::Insensitive); 68 116 69 117 this->setVisibility(GLGUI_WIDGET_DEFAULT_VISIBLE); 70 118 71 this->_backMat.setDiffuseColor(Color(1.0, 0.5, 0.4, 1.0)); 72 this->_backMat.setDiffuseMap("gui_element_background.png"); 73 this->_frontColor = Color(1.0, 0.0, 0.0); 74 this->_toFrontColor = NULL; 75 76 77 this->_borderLeft = 15.0; 78 this->_borderRight = 1.0; 79 this->_borderTop = 1.0; 80 this->_borderBottom = 1.0; 119 this->setBorderLeft(15); 120 this->setBackgroundTexture("gui_element_background.png"); 121 122 this->switchState(_state); 123 this->_currentStyle = this->_style[_state]; 81 124 } 82 125 … … 121 164 } 122 165 166 167 123 168 void GLGuiWidget::setFrontColor(const Color& frontColor, bool instantaniously) 124 169 { 125 if (instantaniously) 126 { 127 this->_frontColor = frontColor; 128 if (this->_toFrontColor != NULL) 129 { 130 delete this->_toFrontColor; 131 this->_toFrontColor = NULL; 132 } 133 } 134 else if (!this->_toFrontColor) 135 this->_toFrontColor = new Color(frontColor); 136 else 137 *this->_toFrontColor = frontColor; 138 //this->_frontColor = frontColor; 139 //this->updateFrontColor(); 170 this->_currentStyle._foreground.setDiffuseColor(frontColor); 171 this->animateBack(); 140 172 }; 141 142 143 void GLGuiWidget::setBorderSize(float borderSize)144 {145 this->_borderLeft = borderSize;146 this->_borderRight = borderSize;147 this->_borderTop = borderSize;148 this->_borderBottom = borderSize;149 this->resize();150 }151 152 void GLGuiWidget::setBorderLeft(float borderLeft)153 {154 this->_borderLeft = borderLeft;155 this->resize();156 }157 void GLGuiWidget::setBorderRight(float borderRight)158 {159 this->_borderRight = borderRight;160 this->resize();161 }162 void GLGuiWidget::setBorderTop(float borderTop)163 {164 this->_borderTop = borderTop;165 this->resize();166 }167 void GLGuiWidget::setBorderBottom(float borderBottom)168 {169 this->_borderBottom = borderBottom;170 this->resize();171 }172 173 173 174 174 … … 201 201 void GLGuiWidget::clicking(const Vector2D& pos) 202 202 { 203 this->setFrontColor(Color(0, 0, 1)); 204 203 this->switchState(OrxGui::Selected); 205 204 } 206 205 207 206 void GLGuiWidget::releasing(const Vector2D& pos) 208 207 { 209 this->setFrontColor(Color(0,1,0)); 210 208 this->switchState(OrxGui::Normal); 211 209 } 212 210 213 211 void GLGuiWidget::receivedFocus() 214 212 { 215 this->s etFrontColor(Color(0, 1, 0));213 this->switchState(OrxGui::Focused); 216 214 } 217 215 218 216 void GLGuiWidget::removedFocus() 219 217 { 220 this->s etFrontColor(Color(1, 0, 0));218 this->switchState(OrxGui::Normal); 221 219 222 220 } … … 267 265 } 268 266 269 void GLGuiWidget::tick(float dt) 270 { 271 if (this->_toFrontColor) 267 268 /** 269 * @brief resets the Style to the default Settings. 270 */ 271 void GLGuiWidget::resetStyle() 272 { 273 this->setBorderLeft(1.0); 274 this->setBorderRight(1.0); 275 this->setBorderTop(1.0); 276 this->setBorderBottom(1.0); 277 278 this->setTextSize(20.0); 279 this->setBackgroundColor(1.0); 280 this->setForegroundColor(1.0); 281 282 this->setFeaturePosition(FeatureLeft); 283 this->setFont(NULL); 284 285 this->setAnimatedStateChanges(true); 286 } 287 288 289 /** 290 * @brief sets the Width of the left border for all States 291 * @param value the borderWidth 292 */ 293 void GLGuiWidget::setBorderLeft(float value) 294 { 295 for (unsigned int i = 0; i < GLGUI_STATE_COUNT; ++i) 296 setBorderLeft(value, (OrxGui::State)i); 297 } 298 299 /** 300 * @brief sets the Width of the left border. 301 * @param value the borderWidth 302 * @param state the State to set the borderwidth to 303 */ 304 void GLGuiWidget::setBorderLeft(float value, OrxGui::State state) 305 { 306 _style[state]._borderLeft = value; 307 if (state == _state) 308 _currentStyle._borderLeft = value; 309 } 310 311 /** 312 * @brief sets the Width of the left border. 313 * @param value the borderWidth 314 * @param stateName the State to set the borderwidth to 315 */ 316 void GLGuiWidget::setBorderLeftS(float value, const std::string& stateName) 317 { 318 OrxGui::State state; 319 if (getState(stateName, &state)) 320 this->setBorderLeft(value, state); 321 else 322 this->setBorderLeft(value); 323 } 324 325 /** 326 * @brief sets the Width of the right border for all states. 327 * @param value the borderWidth 328 */ 329 void GLGuiWidget::setBorderRight(float value) 330 { 331 for (unsigned int i = 0; i < GLGUI_STATE_COUNT; ++i) 332 setBorderRight(value, (OrxGui::State)i); 333 } 334 335 /** 336 * @brief sets the Width of the right border. 337 * @param value the borderWidth 338 * @param state the State to setup. 339 */ 340 void GLGuiWidget::setBorderRight(float value, OrxGui::State state) 341 { 342 _style[state]._borderRight = value; 343 if (state == _state) 344 _currentStyle._borderRight = value; 345 } 346 347 /** 348 * @brief sets the Width of the right border. 349 * @param value the borderWidth 350 * @param stateName the State to setup. 351 */ 352 void GLGuiWidget::setBorderRightS(float value, const std::string& stateName) 353 { 354 OrxGui::State state; 355 if (getState(stateName, &state)) 356 this->setBorderRight(value, state); 357 else 358 this->setBorderRight(value); 359 } 360 361 362 /** 363 * @brief sets the Width of the top border for all states. 364 * @param value the borderWidth 365 */ 366 void GLGuiWidget::setBorderTop(float value) 367 { 368 for (unsigned int i = 0; i < GLGUI_STATE_COUNT; ++i) 369 setBorderTop(value, (OrxGui::State)i); 370 } 371 372 /** 373 * @brief sets the Width of the top border. 374 * @param value the borderWidth 375 * @param state the State to setup. 376 */ 377 void GLGuiWidget::setBorderTop(float value, OrxGui::State state) 378 { 379 _style[state]._borderTop = value; 380 if (state == _state) 381 _currentStyle._borderTop = value; 382 } 383 384 /** 385 * @brief sets the Width of the top border. 386 * @param value the borderWidth 387 * @param stateName the State to setup. 388 */ 389 void GLGuiWidget::setBorderTopS(float value, const std::string& stateName) 390 { 391 OrxGui::State state; 392 if (getState(stateName, &state)) 393 this->setBorderTop(value, state); 394 else 395 this->setBorderTop(value); 396 } 397 398 399 /** 400 * @brief sets the Width of the bottom border for all states. 401 * @param value the borderWidth 402 */ 403 void GLGuiWidget::setBorderBottom(float value) 404 { 405 for (unsigned int i = 0; i < GLGUI_STATE_COUNT; ++i) 406 setBorderBottom(value, (OrxGui::State)i); 407 } 408 409 /** 410 * @brief sets the Width of the bottom border. 411 * @param value the borderWidth 412 * @param state the State to setup. 413 */ 414 void GLGuiWidget::setBorderBottom(float value, OrxGui::State state) 415 { 416 _style[state]._borderBottom = value; 417 if (state == _state) 418 _currentStyle._borderBottom = value; 419 420 } 421 422 /** 423 * @brief sets the Width of the bottom border for all states. 424 * @param value the borderWidth 425 * @param stateName the State to setup. 426 */ 427 void GLGuiWidget::setBorderBottomS(float value, const std::string& stateName) 428 { 429 OrxGui::State state; 430 if (getState(stateName, &state)) 431 this->setBorderBottom(value, state); 432 else 433 this->setBorderBottom(value); 434 } 435 436 437 /** 438 * @brief sets the TextSize for all states. 439 * @param value the TextSize 440 */ 441 void GLGuiWidget::setTextSize(float value) 442 { 443 for (unsigned int i = 0; i < GLGUI_STATE_COUNT; ++i) 444 setTextSize(value, (OrxGui::State)i); 445 } 446 447 /** 448 * @brief sets the TextSize. 449 * @param value the TextSize. 450 * @param state: the State to setup 451 */ 452 void GLGuiWidget::setTextSize(float value, OrxGui::State state) 453 { 454 _style[state]._textSize = value; 455 if (state == _state) 456 _currentStyle._textSize = value; 457 } 458 459 /** 460 * @brief sets the TextSize. 461 * @param value the TextSize. 462 * @param stateName: the State to setup 463 */ 464 void GLGuiWidget::setTextSizeS(float value, const std::string& stateName) 465 { 466 OrxGui::State state; 467 if (getState(stateName, &state)) 468 this->setTextSize(value, state); 469 else 470 this->setTextSize(value); 471 } 472 473 474 /** 475 * @brief sets the Background Color for all States. 476 * @param color the Color. 477 */ 478 void GLGuiWidget::setBackgroundColor(const Color& color) 479 { 480 for (unsigned int i = 0; i < GLGUI_STATE_COUNT; ++i) 481 setBackgroundColor(color, (OrxGui::State)i); 482 } 483 484 /** 485 * @brief sets the Background Color. 486 * @param color the Color. 487 * @param state: the State to setup 488 */ 489 void GLGuiWidget::setBackgroundColor(const Color& color, OrxGui::State state) 490 { 491 _style[state]._background.setDiffuseColor(color); 492 if (state == _state) 493 _currentStyle._background.setDiffuseColor(color); 494 495 } 496 497 /** 498 * @brief sets the Background Color. 499 * @param r the Color's red part. 500 * @param g the Color's green part. 501 * @param b the Color's blue part. 502 * @param a the Color's alpha part. 503 * @param stateName: the State to setup 504 */ 505 void GLGuiWidget::setBackgroundColorS(float r, float g, float b, float a, const std::string& stateName) 506 { 507 OrxGui::State state; 508 if (getState(stateName, &state)) 509 this->setBackgroundColor(Color(r,g,b,a), state); 510 else 511 this->setBackgroundColor(Color(r,g,b,a)); 512 } 513 514 515 /** 516 * @brief sets the Background Texture for all States. 517 * @param texture the Texture. 518 */ 519 void GLGuiWidget::setBackgroundTexture(const Texture& texture) 520 { 521 for (unsigned int i = 0; i < GLGUI_STATE_COUNT; ++i) 522 setBackgroundTexture(texture, (OrxGui::State)i); 523 } 524 525 /** 526 * @brief sets the Background Texture to all States. 527 * @param textureName the Texture's fileName. 528 */ 529 void GLGuiWidget::setBackgroundTexture(const std::string& textureName) 530 { 531 for (unsigned int i = 0; i < GLGUI_STATE_COUNT; ++i) 532 _style[i]._background.setDiffuseMap(textureName); 533 this->_currentStyle._background.setDiffuseMap(textureName); 534 } 535 536 /** 537 * @brief sets the Background Texture. 538 * @param texture the Texture. 539 * @param state the State to setup. 540 */ 541 void GLGuiWidget::setBackgroundTexture(const Texture& texture, OrxGui::State state) 542 { 543 _style[state]._background.setDiffuseMap(texture); 544 if (state == _state) 545 _currentStyle._background.setDiffuseMap(texture); 546 } 547 548 549 550 /** 551 * @brief sets the Background Texture. 552 * @param texture the Texture. 553 * @param stateName the State to setup. 554 */ 555 void GLGuiWidget::setBackgroundTexture(const std::string& textureName, const std::string& stateName) 556 { 557 OrxGui::State state; 558 if (getState(stateName, &state)) 559 ; /// FIXME this->setBackgroundTexture(textureName, state); 560 else 561 ; /// this->setBackgroundTexture(textureName); 562 } 563 564 565 /** 566 * @brief sets the Foreground Color for all States. 567 * @param color the Color. 568 */ 569 void GLGuiWidget::setForegroundColor(const Color& color) 570 { 571 for (unsigned int i = 0; i < GLGUI_STATE_COUNT; ++i) 572 setForegroundColor(color, (OrxGui::State)i); 573 } 574 575 /** 576 * @brief sets the Foreground Color. 577 * @param color the Color. 578 * @param state the State to setup 579 */ 580 void GLGuiWidget::setForegroundColor(const Color& color, OrxGui::State state) 581 { 582 _style[state]._foreground.setDiffuseColor(color); 583 if (state == _state) 584 _currentStyle._foreground.setDiffuseColor(color); 585 586 } 587 588 /** 589 * @brief sets the Foreground Color. 590 * @param r the Color's red part. 591 * @param g the Color's green part. 592 * @param b the Color's blue part. 593 * @param a the Color's alpha part. 594 * @param stateName: the State to setup 595 */ 596 void GLGuiWidget::setForegroundColorS(float r, float g, float b, float a, const std::string& stateName) 597 { 598 OrxGui::State state; 599 if (getState(stateName, &state)) 600 this->setForegroundColor(Color(r,g,b,a), state); 601 else 602 this->setForegroundColor(Color(r,g,b,a)); 603 } 604 605 606 void GLGuiWidget::loadBackgroundMaterial(const Material& material) 607 { 608 for (unsigned int i = 0; i < GLGUI_STATE_COUNT; ++i) 609 this->loadForegroundMaterial(material, (OrxGui::State)i); 610 } 611 612 void GLGuiWidget::loadBackgroundMaterial(const Material& material, OrxGui::State state) 613 { 614 this->_style[state]._background = material; 615 if (state == _state) 616 _currentStyle._background = material; 617 618 } 619 620 void GLGuiWidget::loadBackgroundMaterial(const TiXmlElement* element) 621 { 622 for (unsigned int i = 0; i < GLGUI_STATE_COUNT; ++i) 623 this->loadBackgroundMaterial(element, (OrxGui::State)i); 624 } 625 626 void GLGuiWidget::loadBackgroundMaterial(const TiXmlElement* element, OrxGui::State state) 627 { 628 this->_style[state]._background.loadParams(element); 629 if (state == _state) 630 this->_currentStyle._background = _style[state]._background; 631 } 632 633 void GLGuiWidget::loadBackgroundMaterialS(const TiXmlElement* element, const std::string& stateName) 634 { 635 OrxGui::State state; 636 if (getState(stateName, &state)) 637 this->loadBackgroundMaterial(element, state); 638 else 639 this->loadBackgroundMaterial(element); 640 } 641 642 void GLGuiWidget::loadForegroundMaterial(const Material& material) 643 {} 644 void GLGuiWidget::loadForegroundMaterial(const Material& material, OrxGui::State state) 645 {} 646 void GLGuiWidget::loadForegroundMaterial(const TiXmlElement* element, OrxGui::State state) 647 {} 648 void GLGuiWidget::loadForegroundMaterialS(const TiXmlElement* element, const std::string& stateName) 649 {} 650 651 652 /** 653 * @brief sets the Feature-Position. 654 * @param featurePosition the Feature-Position. 655 */ 656 void GLGuiWidget::setFeaturePosition(FeaturePosition featurePosition) 657 { 658 this->_featurePosition = featurePosition; 659 } 660 661 /** 662 * @brief sets the Feature-Position by converting from a String. 663 * @param featurePosition the Feature-Position. 664 */ 665 void GLGuiWidget::setFeaturePositionS(const std::string& featurePosition) 666 { 667 for (unsigned int i = 0; i < 4; ++i) 272 668 { 273 this->_frontColor.slerp(*_toFrontColor, dt*3.0); 274 this->updateFrontColor(); 275 if (this->_frontColor.dist(*_toFrontColor) < .1) 669 if (featurePosition == FeaturePositionString[i]) 276 670 { 277 delete _toFrontColor; 278 _toFrontColor = NULL; 671 this->setFeaturePosition((FeaturePosition)i); 279 672 } 280 673 } 281 674 } 282 675 676 /** 677 * @brief sets the Font. 678 * @param font the Font. 679 */ 680 void GLGuiWidget::setFont(Font* font) 681 { 682 this->_font = font; 683 } 684 685 /** 686 * @brief sets the font from a Font-Name. 687 * @param fontName the FileName of the Font. 688 */ 689 void GLGuiWidget::setFont(const std::string& fontName) 690 { 691 //this->font = new Font(fontName); 692 } 693 694 /** 695 * @brief sets the AnimatedState. 696 * @param animated: it states-changes should animate true, otherwise false. 697 */ 698 void GLGuiWidget::setAnimatedStateChanges(bool animated) 699 { 700 this->_animatedStateChanges = animated; 701 } 702 703 704 void GLGuiWidget::switchState(OrxGui::State state) 705 { 706 //this->_currentStyle = this->_style[state]; 707 this->_state = state; 708 PRINTF(3)("Switching to state %s\n", OrxGui::StateString[state].c_str()); 709 710 this->animateBack(); 711 } 712 713 714 void GLGuiWidget::animateBack() 715 { 716 this->_animating = true; 717 this->_animationCycle = 0.0f; 718 } 719 720 721 void GLGuiWidget::tick(float dt) 722 { 723 if (this->_animating) 724 { 725 this->foregroundColor(); 726 727 _animationCycle += dt / _animationDuration; 728 if (_animationCycle >= 1.0) 729 { 730 _currentStyle._foreground.diffuseColor() = this->foregroundColor(_state); 731 _currentStyle._background.diffuseColor() = this->backgroundColor(_state); 732 _animating = false; 733 } 734 else 735 { 736 _currentStyle._foreground.diffuseColor().slerp(this->foregroundColor(_state), _animationCycle); 737 _currentStyle._background.diffuseColor().slerp(this->backgroundColor(_state), _animationCycle); 738 } 739 this->updateFrontColor(); 740 } 741 } 742 283 743 284 744 /** … … 287 747 void GLGuiWidget::draw() const 288 748 { 289 this->back Material().select();749 this->background().select(); 290 750 this->drawRect(this->backRect()); 291 this->backMaterial().unselect(); 292 } 751 this->background().unselect(); 752 } 753 754 755 /** 756 * @param stateName the Name of a State. 757 * @param state the found State is returned here if found. 758 * @returns true if String is found, false otherwise. 759 */ 760 bool GLGuiWidget::getState(const std::string& stateName, OrxGui::State* state) 761 { 762 for (unsigned int i = 0; i < GLGUI_STATE_COUNT; ++i) 763 if (stateName == OrxGui::StateString[i]) 764 { 765 *state = (OrxGui::State)i; 766 return true; 767 } 768 return false; 769 } 770 771 /** 772 * @brief print out some nice debug output about the Widget. 773 */ 774 void GLGuiWidget::debug() const 775 { 776 PRINT(0)("Debug of %s::%s - WidgetPart ", this->getClassName(), this->getName()); 777 if (_parent != NULL) 778 PRINT(0)("- Parent %s::%s ", _parent->getClassName(), _parent->getName()); 779 PRINT(0)("- State: %s", StateString[_state].c_str()); 780 781 if (_focusable) 782 PRINT(0)("- focusable "); 783 if (_clickable) 784 PRINT(0)("- Clickable "); 785 if (_pushed) 786 PRINT(0)("- Pushed "); 787 PRINT(0)("\n"); 788 789 790 PRINT(0)("Minimum Size %0.2f %0.2f ", _minSize.x, _minSize.y); 791 PRINT(0)("Back Rect: "); 792 _backRect.debug(); 793 PRINT(0)("Style:\n"); 794 795 for (unsigned int i = 0; i < GLGUI_STATE_COUNT; ++i) 796 { 797 PRINT(0)("In State %s: \n", StateString[i].c_str()); 798 799 PRINT(0)(" Borders: Left: %0.2f, Right: %0.2f, Top: %0.2f, Bottom %0.2f\n", 800 _style[i]._borderLeft, _style[i]._borderRight, _style[i]._borderTop, _style[i]._borderBottom); 801 PRINT(0)(" TextSize %0.2f\n", _style[i]._textSize); 802 PRINT(0)(" BackgroundColor: "); _style[i]._background.diffuseColor().debug(); 803 PRINT(0)(" ForegroundColor: "); _style[i]._foreground.diffuseColor().debug(); 804 PRINT(0)("\n"); 805 } 806 807 808 PRINT(0)(" Feature at %s ", FeaturePositionString[_featurePosition].c_str()); 809 /// TODO PRINT(0)(""); Font* _font; //!< The Font used in the current Widget. 810 811 if (_animatedStateChanges) 812 PRINT(0)("- AnimatedStateChanges"); 813 PRINT(0)("\n"); 814 815 /* 816 if (_animating) 817 PRINT(0)("- Animated "); 818 819 // 820 float _animationCycle; 821 float _animationDuration; 822 StatedStyle _currentStyle; 823 OrxGui::State _currentState; 824 */ 825 } 826 293 827 294 828 }
Note: See TracChangeset
for help on using the changeset viewer.