001package votorola.s.gwt.stage.link; // Copyright 2011-2013, Michael Allan. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Votorola Software"), to deal in the Votorola Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicence, and/or sell copies of the Votorola Software, and to permit persons to whom the Votorola Software is furnished to do so, subject to the following conditions: The preceding copyright notice and this permission notice shall be included in all copies or substantial portions of the Votorola Software. THE VOTOROLA SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE VOTOROLA SOFTWARE OR THE USE OR OTHER DEALINGS IN THE VOTOROLA SOFTWARE. 002 003import com.google.gwt.core.client.*; 004import com.google.gwt.dom.client.*; 005import com.google.gwt.http.client.URL; 006import com.google.gwt.uibinder.client.*; 007import com.google.gwt.user.client.ui.*; 008import com.google.web.bindery.event.shared.HandlerRegistration; 009import java.util.Iterator; 010import java.util.regex.*; 011import votorola.a.diff.*; 012import votorola.a.position.*; 013import votorola.a.voter.*; 014import votorola.a.web.gwt.*; 015import votorola.g.hold.*; 016import votorola.g.lang.*; 017import votorola.g.web.gwt.*; 018import votorola.g.web.gwt.event.*; 019import votorola.s.gwt.stage.*; 020 021 022/** A view of a link track. The view is divided horizontally into two sides and a middle. 023 * The two sides are occupied by links and other tools, while the middle is occupied by 024 * two captions for the display of text. The two captions may overlap, the left being 025 * partly eclipsed by the right whenever the combined content of both is too wide.<pre 026 * 027 *> +-----------------------------------------------------------+ 028 * | side | caption caption | side | 029 * +-----------------------------------------------------------+</pre> 030 * 031 * @see <a href='../../../../../../../s/gwt/stage/link/LinkTrackV.ui.xml' 032 * >LinkTrackV.ui.xml</a> 033 */ 034public final class LinkTrackV extends Composite 035{ 036 037 038 /** Constructs a LinkTrackV. 039 */ 040 LinkTrackV() 041 { 042 DraftPageType type = DraftPageType.nonDraft; // till proven otherwise 043 final JsArrayString categories = WindowX.js()._get( "wgCategories" ); 044 if( categories != null ) for( int c = categories.length() - 1; c >= 0; --c ) 045 { 046 final String cat = categories.get( c ); 047 if( "Remote draft".equals( cat )) 048 { 049 type = DraftPageType.remoteDraft; 050 break; 051 } 052 else if( "Draft".equals( cat )) 053 { 054 type = DraftPageType.localDraft; // assuming this is pollwiki, per s/gwt/pollwiki 055 break; 056 } 057 } 058 // else not a MediaWiki page 059 draftPageType = type; 060 } 061 062 063 064 /** Returns the {@linkplain StageV staged instance} of LinkTrackV, or null if none is 065 * staged. 066 */ 067 public static LinkTrackV i( final StageV stageV ) 068 { 069 final Iterator<Widget> ww = stageV.iterator(); 070 while( ww.hasNext() ) 071 { 072 final Widget w = ww.next(); 073 if( w instanceof LinkTrackV ) return (LinkTrackV)w; 074 } 075 076 return null; 077 } 078 079 080 081 // ` e a r l y ```````````````````````````````````````````````````````````````````````` 082 083 084 private final Spool spool = new Spool1(); 085 086 087 088 @Warning("non-API") interface UiBinderI extends UiBinder<Widget,LinkTrackV> {} 089 090 { 091 final UiBinderI uiBinder = GWT.create( UiBinderI.class ); 092 initWidget( uiBinder.createAndBindUi( LinkTrackV.this )); 093 } 094 095 096 097 // ------------------------------------------------------------------------------------ 098 099 100 /** The link to the {@linkplain Stage#getActorName actor}'s user page in the pollwiki. 101 */ 102 public ActorLink actorLink() { return actorLink; } 103 104 105 private final ActorLink actorLink; 106 107 108 @UiField @Warning("non-API") AnchorElement actorA; 109 110 { 111 final String imageName = "actor"; 112 init( actorA, imageName, App.i().mesS().gwt_stage_LinkTrack_actorTitle() ); 113 actorLink = new ActorLink( actorA, imageName ); 114 new Targeter1( actorA, "actorName" ) 115 { 116 @Override void retarget() 117 { 118 final String p = Stage.i().getActorName(); 119 setTarget( p == null? null: 120 adjustedTarget(App.i().pollwiki().encodePageSpecifier("User:"+p).toString()) ); 121 } 122 }; 123 } 124 125 126 127 /** Adds a tool to the left side of this view. This is only for temporary support of 128 * configuration item {@linkplain votorola.s.gwt.scene.Scenes#toUseRAC() toUseRAC}. 129 */ 130 public @Warning("non-API") void addLeftTool( final Widget tool ) 131 { 132 ((HTMLPanel)getWidget()).add( tool, /*containing element ID*/"LinkTrack-left" ); 133 } 134 135 136 137 /** The link to the {@linkplain votorola.s.wic.diff.WP_D bridge scene} for the staged 138 * {@linkplain Stage#getActorName difference}. 139 */ 140 public ActorLink diffLink() { return diffLink; } 141 142 143 private final ActorLink diffLink; 144 145 146 @UiField @Warning("non-API") AnchorElement diffA; 147 148 { 149 final String imageName = "diff"; 150 init( diffA, imageName, App.i().mesS().gwt_stage_LinkTrack_diffTitle() ); 151 diffLink = new ActorLink( diffA, imageName ); 152 if( NominalDifferenceTargeter.isEnabled() ) 153 { 154 new NominalDifferenceTargeter( LinkTrackV.this, diffA ); 155 } 156 else new DifferenceTargeter( LinkTrackV.this, diffA ); 157 } 158 159 160 161 /** The link to the {@linkplain Stage#getActorName actor}'s position draft. 162 */ 163 public ActorLink draftLink() { return draftLink; } 164 165 166 private final ActorLink draftLink; 167 168 169 @UiField @Warning("non-API") AnchorElement draftA; 170 171 { 172 final String imageName = "draft"; 173 init( draftA, imageName, App.i().mesS().gwt_stage_LinkTrack_draftTitle() ); 174 draftLink = new ActorLink( draftA, imageName ); 175 new TargeterP( draftA ) 176 { 177 @Override void retarget() 178 { 179 final Stage stage = Stage.i(); 180 final String actorName = stage.getActorName(); 181 final String pollName = stage.getPollName(); 182 if( pollName == null || actorName == null ) setTarget( null ); 183 else 184 { 185 final String contextPersonName; 186 if( draftPageType == DraftPageType.localDraft ) 187 { 188 final PositionID position = App.i().pollwiki().identifyAsPosition( 189 Document.get() ); 190 contextPersonName = position.personName(); 191 if( position != null && actorName.equals(contextPersonName) 192 && pollName.equals(position.pollName()) ) 193 { 194 setTarget( null ); // already on actor's local draft page 195 return; 196 } 197 } 198 else if( draftPageType == DraftPageType.remoteDraft ) 199 { 200 final Document doc = Document.get(); 201 Element marker; 202 marker = doc.getElementById( "voDraft-author" ); 203 if( marker != null ) 204 { 205 contextPersonName = IDPair.normalUsername( marker.getTitle() ); 206 if( actorName.equals( contextPersonName )) 207 { 208 marker = doc.getElementById( "voDraft-poll" ); 209 if( marker != null && pollName.equals( marker.getTitle() )) 210 { 211 setTarget( null ); // already on actor's remote draft page 212 return; 213 } 214 } 215 } 216 else contextPersonName = stage.getDefaultActorName(); 217 } 218 else contextPersonName = stage.getDefaultActorName(); 219 final StringBuilder b = GWTX.stringBuilderClear(); 220 b.append( App.getServletContextLocation() ); 221 b.append( "/w/Draft?p=" ); 222 b.append( pollName.replace( '/', '!' )); 223 b.append( "&u=" ); 224 b.append( URL.encodeQueryString( actorName )); 225 if( !( contextPersonName == null || contextPersonName.equals(actorName) )) 226 { 227 b.append( "&cP=" ); 228 b.append( URL.encodeQueryString( contextPersonName )); 229 } 230 setTarget( adjustedTarget( b.toString() )); 231 } 232 } 233 }; 234 } 235 236 237 238 /** The style variant for actor dependent links, or null if the ordinary style is 239 * used. 240 * 241 * @see #clearActorLinkVariant(String) 242 * @see #setActorLinkVariant(String) 243 */ 244 public String getActorLinkVariant() { return actorLinkVariant; } 245 246 247 private String actorLinkVariant; 248 249 250 /** Clears the current style variant if it equals the one specified. 251 * 252 * @see #getActorLinkVariant() 253 */ 254 public void clearActorLinkVariant( final String variant ) 255 { 256 if( actorLinkVariant == null || !actorLinkVariant.equals( variant )) return; 257 // clobber guard 258 259 ElementX.replaceNullClassName( actorLinkVariant, /*new*/null, getWidget().getElement() ); 260 actorLinkVariant = null; 261 syncActorLinkVariant(); 262 } 263 264 265 /** Sets the style variant for actor dependent links. A null value forcefully 266 * clears the variant. Consider using {@linkplain #clearActorLinkVariant(String) 267 * clearActorLinkVariant} instead to guard against prematurely clearing what was 268 * set by another caller. 269 * 270 * @see #getActorLinkVariant() 271 * @see ActorLink#resetVariant() 272 */ 273 public void setActorLinkVariant( final String variant ) 274 { 275 ElementX.replaceNullClassName( actorLinkVariant, /*new*/variant, 276 getWidget().getElement() ); /* could OPT by returning if not actually 277 replaced and we knew no resetVariant had been called */ 278 279 actorLinkVariant = variant; 280 syncActorLinkVariant(); 281 } 282 283 284 private void syncActorLinkVariant() 285 { 286 src( actorA, "actor", actorLinkVariant ); 287 src( diffA, "diff", actorLinkVariant ); 288 src( draftA, "draft", actorLinkVariant ); 289 } 290 291 292 293 // - W i d g e t ---------------------------------------------------------------------- 294 295 296 protected @Override void onUnload() 297 { 298 super.onUnload(); 299 spool.unwind(); 300 } 301 302 303 304 // ==================================================================================== 305 306 307 /** An actor dependent link, the target of which typically depends on the staged 308 * actor. 309 */ 310 public final class ActorLink 311 { 312 313 private ActorLink( AnchorElement _a, String _imageName ) 314 { 315 a = _a; 316 imageName = _imageName; 317 } 318 319 320 private final AnchorElement a; 321 322 323 private final String imageName; 324 325 326 // -------------------------------------------------------------------------------- 327 328 329 /** Forcefully clears the style variant for this actor link. 330 * 331 * @see #getActorLinkVariant() 332 */ 333 public void resetVariant() { src( a, imageName, /*variant*/null ); } 334 335 } 336 337 338 339 // ==================================================================================== 340 341 342 abstract class StageListener extends TheatreInitializer0 implements PropertyChangeHandler 343 { 344 345 StageListener() 346 { 347 Scheduler.get().scheduleFinally( new Scheduler.ScheduledCommand() 348 { 349 public void execute() 350 { 351 // delayed else init may be called before subclasses constructed 352 Stage.i().addInitializer( StageListener.this ); // auto-removed 353 } 354 }); 355 } 356 357 358 /** Called when the stage is completely initialized, adds listeners and 359 * initializes state. 360 * 361 * returns false if init is aborted because the spool is unwinding, true 362 * otherwise. 363 */ 364 boolean init() 365 { 366 if( spool.isUnwinding() ) return false; 367 368 spool.add( new Hold() 369 { 370 final HandlerRegistration hR = GWTX.i().bus().addHandlerToSource( 371 PropertyChange.TYPE, /*source*/Stage.i(), StageListener.this ); 372 public void release() { hR.removeHandler(); } 373 }); 374 return true; 375 } 376 377 378 // - P r o p e r t y - C h a n g e - H a n d l e r -------------------------------- 379 380 381 public void onPropertyChange( PropertyChange _e ) 382 { 383 // GWT BUG (2.4). Method must be defined here in base class, else 384 // NoSuchMethodError may occur on page load (at least in devmode), though that 385 // should be impossible. 386 throw new IllegalStateException(); // always overridden by subclass 387 } 388 389 390 // - T h e a t r e - I n i t i a l i z e r ---------------------------------------- 391 392 393 public @Override final void initFromComplete( Stage _s, boolean _rPending ) { init(); } 394 395 396 public @Override final void initToComplete( Stage _s, boolean _rPending ) { init(); } 397 398 } 399 400 401 402 // ==================================================================================== 403 404 405 /** An agent to adjust the target and enabled state of a link based on one or more 406 * stage properties. 407 */ 408 abstract class Targeter extends StageListener 409 { 410 411 Targeter( AnchorElement _link ) { link = _link; } 412 413 414 @Override boolean init() 415 { 416 if( !super.init() ) return false; 417 418 retarget(); // init state 419 return true; 420 } 421 422 423 // -------------------------------------------------------------------------------- 424 425 426 /** Returns the href adjusted by (a) nulling if it matches the current document 427 * location; or (b) insertion of dev mode parameter if dev mode is in effect. 428 */ 429 final String adjustedTarget( final String href ) 430 { 431 if( href == null ) return null; 432 433 String loc = Document.get().getURL(); 434 final com.google.gwt.regexp.shared.MatchResult devModeLoc; 435 if( GWT.isProdMode() ) devModeLoc = null; 436 else // probably dev mode 437 { 438 devModeLoc = GWTX.DEV_MODE_LOCATION_PATTERN.exec( loc ); 439 if( devModeLoc != null ) // then remove the devmode parameter 440 { 441 final String trailer = devModeLoc.getGroup( 3 ); 442 if( trailer == null || trailer.length() == 0 ) 443 { 444 final String leader = devModeLoc.getGroup( 1 ); 445 loc = leader.substring( 0, leader.length() - 1 ); // chop trailing ?|& 446 } 447 else loc = devModeLoc.getGroup(1) + trailer; 448 } 449 } 450 final String hrefA; // adjusted 451 if( href.equals( loc )) hrefA = null; 452 else 453 { 454 if( devModeLoc == null ) hrefA = href; 455 else if( href.lastIndexOf('#') > 0 ) hrefA = href; // cannot yet handle fragment 456 else 457 { 458 final char delimeter = href.lastIndexOf('?') > 0? '&': '?'; 459 hrefA = href + delimeter + "gwt.codesvr=" + devModeLoc.getGroup( 2 ); 460 } 461 } 462 return hrefA; 463 } 464 465 466 private final AnchorElement link; 467 468 469 abstract void retarget(); 470 471 472 final void setTarget( final String href ) 473 { 474 if( href == null ) 475 { 476 link.addClassName( "disabled" ); 477 link.removeClassName( "enabled" ); 478 link.removeAttribute( "href" ); // actually disable it 479 } 480 else 481 { 482 link.addClassName( "enabled" ); 483 link.removeClassName( "disabled" ); 484 link.setAttribute( "href", href ); 485 } 486 } 487 488 } 489 490 491 492 // ==================================================================================== 493 494 495 /** A targeter that depends on a single stage property. 496 */ 497 abstract class Targeter1 extends Targeter 498 { 499 500 Targeter1( AnchorElement _link, String _propertyName ) 501 { 502 super( _link ); 503 propertyName = _propertyName; 504 } 505 506 507 private final String propertyName; 508 509 510 // - P r o p e r t y - C h a n g e - H a n d l e r -------------------------------- 511 512 513 public final void onPropertyChange( final PropertyChange e ) 514 { 515 if( e.propertyName().equals( propertyName )) { retarget(); } 516 } 517 518 } 519 520 521 522 // ==================================================================================== 523 524 525 /** A targeter that depends on multiple stage properties. 526 */ 527 abstract class TargeterN extends Targeter implements Scheduler.ScheduledCommand 528 { 529 530 TargeterN( AnchorElement _link ) { super( _link ); } 531 532 533 final CoalescingSchedulerS coalescer = new CoalescingSchedulerS( 534 CoalescingSchedulerS.FINALLY, TargeterN.this ); 535 536 537 // - S c h e d u l e r . S c h e d u l e d - C o m m a n d ------------------------ 538 539 540 public final void execute() { retarget(); } 541 542 } 543 544 545 546//// P r i v a t e /////////////////////////////////////////////////////////////////////// 547 548 549 @UiField @Warning("non-API") AnchorElement diffMeA; 550 551 { 552 init( diffMeA, "diff", App.i().mesS().gwt_stage_LinkTrack_diffMeTitle() ); 553 new TargeterP( diffMeA ) 554 { 555 @Override boolean init() 556 { 557 if( !super.init() ) return false; 558 559 final TargeterP handler = this; 560 spool.add( new Hold() 561 { 562 final HandlerRegistration hR = GWTX.i().bus().addHandlerToSource( 563 PropertyChange.TYPE, /*source*/App.i(), handler ); 564 public void release() { hR.removeHandler(); } 565 }); 566 return true; 567 } 568 public @Override void onPropertyChange( final PropertyChange e ) 569 { 570 if( e.getSource() == App.i() ) 571 { 572 if( e.propertyName().equals( "username" )) coalescer.schedule(); 573 // which then calls retarget 574 } 575 else super.onPropertyChange( e ); 576 } 577 @Override void retarget() 578 { 579 final String pollName = Stage.i().getPollName(); 580 final String actorName = Stage.i().getActorName(); 581 if( pollName == null || actorName == null 582 || actorName.equals( App.getUsername() )) // avoid self diff 583 { 584 setTarget( null ); 585 } 586 else 587 { 588 setTarget( adjustedTarget( App.getServletContextLocation() 589 + "/w/D?alterAuthor=" + URL.encodeQueryString(actorName) 590 + "&poll=" + URL.encodeQueryString(pollName) )); 591 } 592 } 593 }; 594 } 595 596 597 598 @UiField @Warning("non-API") AnchorElement draftMeA; 599 600 { 601 init( draftMeA, "draft", App.i().mesS().gwt_stage_LinkTrack_draftMeTitle() ); 602 new TargeterN( draftMeA ) 603 { 604 @Override boolean init() 605 { 606 if( !super.init() ) return false; 607 608 final TargeterN handler = this; 609 spool.add( new Hold() 610 { 611 final HandlerRegistration hR = GWTX.i().bus().addHandlerToSource( 612 PropertyChange.TYPE, /*source*/App.i(), handler ); 613 public void release() { hR.removeHandler(); } 614 }); 615 return true; 616 } 617 public @Override void onPropertyChange( final PropertyChange e ) 618 { 619 final String name = e.propertyName(); 620 if( e.getSource() == App.i() ) 621 { 622 if( name.equals( "username" )) coalescer.schedule(); // calls retarget 623 } 624 else if( name.equals( "pollName" )) coalescer.schedule(); // calls retarget 625 } 626 @Override void retarget() 627 { 628 final String pollName = Stage.i().getPollName(); 629 if( pollName == null ) setTarget( null ); 630 else 631 { 632 if( draftPageType == DraftPageType.localDraft ) 633 { 634 final String username = App.getUsername(); 635 if( username != null ) 636 { 637 final PositionID position = App.i().pollwiki().identifyAsPosition( 638 Document.get() ); 639 if( position != null && username.equals(position.personName()) 640 && pollName.equals(position.pollName()) ) 641 { 642 setTarget( null ); // already on user's local draft page 643 return; 644 } 645 } 646 } 647 // Else maybe remoteDraft. Cannot currently ID user there. Might 648 // allow username in user CSS along with customization of stage 649 // layout (per s.gwt.mediawiki.MediaWikiIn); but single sign on is 650 // planned, and it's maybe better to wait and see the shape of it. 651 setTarget( adjustedTarget( App.getServletContextLocation() + "/w/MyDraft?p=" 652 + pollName.replace('/','!') )); 653 } 654 } 655 }; 656 } 657 658 659 660 private final DraftPageType draftPageType; 661 662 663 664 private static void init( final AnchorElement a, final String imageName, final String title ) 665 { 666 src( a, imageName, /*variant*/null ); 667 a.setTitle( title ); 668 } 669 670 671 672 @UiField @Warning("non-API") SpanElement leftCaption; 673 674 675 676 private final Text leftText = Document.get().createTextNode( "" ); 677 678 { 679 leftCaption.appendChild( leftText ); 680 new LeftCaptioner(); 681 } 682 683 684 685 @UiField @Warning("non-API") AnchorElement messageA; 686 687 { 688 init( messageA, "message", App.i().mesS().gwt_stage_LinkTrack_messageTitle() ); 689 new Targeter1( messageA, "message" ) 690 { 691 @Override void retarget() 692 { 693 final Message m = Stage.i().getMessage(); 694 setTarget( m == null? null: m.location() ); 695 } 696 }; 697 } 698 699 700 701 @UiField @Warning("non-API") AnchorElement pollA; 702 703 { 704 init( pollA, "poll", App.i().mesS().gwt_stage_LinkTrack_pollTitle() ); 705 new Targeter1( pollA, "pollName" ) 706 { 707 @Override void retarget() 708 { 709 final String p = Stage.i().getPollName(); 710 setTarget( p == null? null: adjustedTarget( 711 App.i().pollwiki().encodePageSpecifier(p).toString()) ); 712 } 713 }; 714 } 715 716 717 718 @UiField @Warning("non-API") SpanElement rightCaption; 719 720 721 722 private final Text rightText = Document.get().createTextNode( "" ); 723 724 { 725 rightCaption.appendChild( rightText ); 726 new RightCaptioner(); 727 } 728 729 730 731 private static void src( final AnchorElement a, final String imageName, final String variant ) 732 { 733 final ImageElement img = a.getFirstChildElement().cast(); 734 final StringBuilder b = GWTX.stringBuilderClear(); 735 b.append( App.i().staticContextLocation() ); 736 b.append( "/stage/link/" ); 737 b.append( imageName ); 738 if( variant != null ) 739 { 740 assert variant.length() > 0; 741 b.append( '-' ); 742 b.append( variant ); 743 } 744 b.append( ".png" ); 745 img.setAttribute( "src", b.toString() ); 746 } 747 748 749 750 @UiField @Warning("non-API") AnchorElement voteA; 751 752 { 753 init( voteA, "vote", App.i().mesS().gwt_stage_LinkTrack_voteTitle() ); 754 new TargeterP( voteA ) 755 { 756 @Override void retarget() 757 { 758 final String pollName = Stage.i().getPollName(); 759 if( pollName == null ) setTarget( null ); 760 else 761 { 762 String href = App.getServletContextLocation() + "/w/Votespace?p=" 763 + pollName.replace( '/', '!' ); 764 final String actorName = Stage.i().getActorName(); 765 if( actorName != null ) href += "&u=" + URL.encodeQueryString( actorName ); 766 setTarget( adjustedTarget( href )); 767 } 768 } 769 }; 770 } 771 772 773 774 // ==================================================================================== 775 776 777 private static enum DraftPageType { localDraft, nonDraft, remoteDraft } 778 // http://reluk.ca/w/Category:Draft 779 780 781 782 // ==================================================================================== 783 784 785 private final class LeftCaptioner extends StageListener 786 { 787 788 @Override boolean init() 789 { 790 if( !super.init() ) return false; 791 792 recaption(); // init state 793 return true; 794 } 795 796 797 private final @Warning("init call") void recaption() 798 { 799 final Stage s = Stage.i(); 800 final Message m = s.getMessage(); // message takes priority 801 final String c = m == null? s.getPollName(): m.content(); // then poll 802 if( c == null ) leftText.deleteData( 0, leftText.getLength() ); 803 else leftText.setData( c ); 804 } 805 806 807 // - P r o p e r t y - C h a n g e - H a n d l e r -------------------------------- 808 809 810 public final void onPropertyChange( final PropertyChange e ) 811 { 812 final String pName = e.propertyName(); 813 if( pName.equals("message") || pName.equals("pollName") ) { recaption(); } 814 } 815 816 } 817 818 819 820 // ==================================================================================== 821 822 823 private final class RightCaptioner extends StageListener 824 { 825 826 @Override boolean init() 827 { 828 if( !super.init() ) return false; 829 830 recaption(); // init state 831 return true; 832 } 833 834 835 private final @Warning("init call") void recaption() 836 { 837 final String actorName = Stage.i().getActorName(); 838 if( actorName == null ) rightText.deleteData( 0, rightText.getLength() ); 839 else rightText.setData( actorName ); 840 } 841 842 843 // - P r o p e r t y - C h a n g e - H a n d l e r -------------------------------- 844 845 846 public final void onPropertyChange( final PropertyChange e ) 847 { 848 if( e.propertyName().equals( "actorName" )) { recaption(); } 849 } 850 851 } 852 853 854 855 // ==================================================================================== 856 857 858 /** A targeter that depends on what position is shown on stage. 859 */ 860 private abstract class TargeterP extends TargeterN 861 { 862 863 TargeterP( AnchorElement _link ) { super( _link ); } 864 865 866 // - P r o p e r t y - C h a n g e - H a n d l e r -------------------------------- 867 868 869 public void onPropertyChange( final PropertyChange e ) 870 { 871 final String pName = e.propertyName(); 872 if( pName.equals("pollName") || pName.equals("actorName") ) coalescer.schedule(); 873 // which then calls retarget 874 } 875 876 } 877 878 879}