001package votorola.s.gwt.stage.talk; // Copyright 2012, Christian Weilbach. 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 java.util.Date; 004import java.util.Iterator; 005 006import votorola.a.web.gwt.App; 007import votorola.g.hold.Hold; 008import votorola.g.hold.Spool; 009import votorola.g.hold.Spool1; 010import votorola.g.lang.Warning; 011import votorola.g.web.gwt.GWTX; 012import votorola.g.web.gwt.event.Change; 013import votorola.g.web.gwt.event.ChangeHandler; 014import votorola.g.web.gwt.event.PropertyChange; 015import votorola.g.web.gwt.event.PropertyChangeHandler; 016import votorola.s.gwt.stage.Stage; 017import votorola.s.gwt.stage.StageV; 018 019import com.google.gwt.core.client.GWT; 020import com.google.gwt.core.client.JsArray; 021import com.google.gwt.uibinder.client.UiBinder; 022import com.google.gwt.uibinder.client.UiFactory; 023import com.google.gwt.uibinder.client.UiField; 024import com.google.gwt.user.client.ui.Composite; 025import com.google.gwt.user.client.ui.HTMLPanel; 026import com.google.gwt.user.client.ui.Image; 027import com.google.gwt.user.client.ui.Widget; 028import com.google.web.bindery.event.shared.HandlerRegistration; 029 030/** 031 * A view for difference messages. Views are on a chain from right to left, 032 * clipped on the right side of C. When the view is filled with data, newest 033 * data is on the right between 0 and F. UPDATE_BUTTON is the inline button to 034 * kick the harvesters through {@linkplain votorola.s.wap.KickWAP}. 035 * 036 * <pre> 037 * VIEW_COUNT... clipped ... C ... hidden views ... F ... filled ... 1 0 UPDATE_BUTTON 038 * </pre> 039 * 040 */ 041public final class TalkTrackV extends Composite { 042 043 044 045 private static TalkTrackV instance; 046 { 047 if (instance != null) 048 throw new IllegalStateException(); // grep "instance" for reason 049 050 instance = TalkTrackV.this; 051 } 052 053 054 055 /** 056 * Returns the {@linkplain StageV staged instance} of PolltrackV, or null if 057 * none is staged. 058 */ 059 public static TalkTrackV i(final StageV stageV) { 060 final Iterator<Widget> ww = stageV.iterator(); 061 while (ww.hasNext()) { 062 final Widget w = ww.next(); 063 if (w instanceof TalkTrackV) 064 return (TalkTrackV) w; 065 } 066 067 return null; 068 } 069 070 071 072 // - M o d e l ------------------------------------------------------------ 073 074 /** 075 * The number of views created by the view on startup and used to fill in 076 * data. 077 */ 078 public final static int VIEW_COUNT = 200; 079 080 081 082 private final TalkTrack track; 083 084 085 086 private final Spool spool = new Spool1(); 087 088 089 090 private final ChangeHandler trackHandler = new ChangeHandler() 091 { 092 @Override 093 public void onChange(Change e) { stageUpdate(); } 094 095 }; 096 097 098 099 private final PropertyChangeHandler stageHandler = new PropertyChangeHandler() 100 { 101 102 @Override 103 public void onPropertyChange(PropertyChange e) 104 { 105 if (e.propertyName().equals("actorName")) { updateButton.reset(); } 106 107 } 108 109 }; 110 111 // - U i ------------------------------------------------------------ 112 113 114 private final TalkV[] talkVs = new TalkV[VIEW_COUNT]; 115 116 117 118 private final String historySeparatorUrl = App.getServletContextLocation() + "/stage/talk/historySeparator.png"; 119 120 121 122 private final class HistorySeparator extends Image 123 { 124 HistorySeparator() 125 { 126 super(historySeparatorUrl); 127 addStyleName("HistorySeparator"); 128 } 129 }; 130 131 132 133 private final HistorySeparator[] historySeparators = new HistorySeparator[VIEW_COUNT+6]; 134 135 136 137 @Warning("non-API") 138 interface UiBinderI extends UiBinder<HTMLPanel, TalkTrackV> { } 139 140 { 141 final UiBinderI uiBinder = GWT.create(UiBinderI.class); 142 initWidget(uiBinder.createAndBindUi(TalkTrackV.this)); 143 } 144 145 146 147 @UiField 148 @Warning("non-API") 149 HTMLPanel panel; 150 151 152 153 @UiField 154 @Warning("non-API") 155 UpdateButton updateButton; 156 157 158 159 @UiFactory 160 @Warning("non-API") 161 HeadsUpDisplay newHeadsUpDisplay() { return new HeadsUpDisplay( TalkTrackV.this ); } 162 163 164 165 @UiFactory 166 UpdateButton makeUpdateButton() { 167 return new UpdateButton(this); 168 } 169 170 /** 171 * Constructs a TalkTrackV. 172 * 173 */ 174 public TalkTrackV(final TalkTrack _track, final StageV _stageV) { 175 track = _track; 176 177 spool.add(new Hold() { 178 final HandlerRegistration hR = GWTX.i().bus() 179 .addHandlerToSource(Change.TYPE, /* source */ 180 _track, trackHandler); 181 182 public void release() { 183 hR.removeHandler(); 184 } 185 }); 186 187 spool.add(new Hold() { 188 final HandlerRegistration hR = GWTX.i().bus() 189 .addHandlerToSource(PropertyChange.TYPE, /* source */ 190 Stage.i(), stageHandler); 191 192 public void release() { 193 hR.removeHandler(); 194 } 195 }); 196 197 // allow two markers at the beginning if discussion is old 198 historySeparators[0] = new HistorySeparator(); 199 historySeparators[0].setVisible(false); 200 panel.add(historySeparators[0]); 201 202 historySeparators[1] = new HistorySeparator(); 203 historySeparators[1].setVisible(false); 204 panel.add(historySeparators[1]); 205 206 historySeparators[2] = new HistorySeparator(); 207 historySeparators[2].setVisible(false); 208 panel.add(historySeparators[2]); 209 210 // direction is rtl, so 0 is rightest. 211 for (int i = 0; i < VIEW_COUNT; i++) 212 { 213 historySeparators[i+3] = new HistorySeparator(); 214 historySeparators[i+3].setVisible(false); 215 panel.add(historySeparators[i+3]); 216 217 talkVs[i] = new TalkV(spool); 218 panel.add(talkVs[i]); 219 } 220 221 // allow two markers at the end if discussion is young 222 historySeparators[VIEW_COUNT] = new HistorySeparator(); 223 historySeparators[VIEW_COUNT].setVisible(false); 224 panel.add(historySeparators[VIEW_COUNT]); 225 226 historySeparators[VIEW_COUNT+1] = new HistorySeparator(); 227 historySeparators[VIEW_COUNT+1].setVisible(false); 228 panel.add(historySeparators[VIEW_COUNT+1]); 229 230 historySeparators[VIEW_COUNT+2] = new HistorySeparator(); 231 historySeparators[VIEW_COUNT+2].setVisible(false); 232 panel.add(historySeparators[VIEW_COUNT+2]); 233 } 234 235 236 237 protected @Override 238 void onUnload() { 239 spool.unwind(); 240 super.onUnload(); 241 } 242 243 244 245 private void stageUpdate() 246 { 247 JsArray<DiffMessageJS> dMsgs = track.messages(); 248 if (dMsgs == null) { 249 return; 250 } 251 final long day = 1000 * 60 * 60 * 24; 252 final long week = day * 7; 253 final long month = day * 30; 254 final long year = day * 365; 255 256 257 // manage history separators 258 boolean changed = false; 259 final long now = new Date().getTime(); 260 long prev = now; 261 int separatorsLeft = 3; 262 for (int i = 0; i < VIEW_COUNT; i++) 263 { 264 if (i >= dMsgs.length()) 265 { 266 talkVs[i].setMsg(null); 267 historySeparators[i].setVisible(false); 268 269 } else 270 { 271 final DiffMessageJS dMsg = dMsgs.get(i); 272 final long current = (long)dMsg.sentDate(); 273 274 if (i==0) { 275 // show separators at end if discussion is young 276 if(current < now-week) 277 { 278 historySeparators[0].setVisible(true); 279 separatorsLeft--; 280 } else { historySeparators[0].setVisible(false); } 281 282 if(current < now-month) 283 { 284 historySeparators[1].setVisible(true); 285 separatorsLeft--; 286 } else { historySeparators[1].setVisible(false); } 287 288 if(current < now-year) 289 { 290 historySeparators[2].setVisible(true); 291 separatorsLeft--; 292 } else { historySeparators[2].setVisible(false); } 293 294 } 295 296 297 if (!DiffMessageJS.EQUATOR.equals(talkVs[i].getMsg(), 298 dMsgs.get(i))) 299 { 300 talkVs[i].setMsg(dMsg); 301 changed = true; 302 } 303 304 305 if( prev > now-week && current < now-week && i!=0 // otherwise it is set in the block above 306 || prev > now-month && current < now-month && i!=0 307 || prev > now-year && current < now-year && i!=0 ) 308 { 309 historySeparators[i+3].setVisible(true); 310 separatorsLeft--; 311 } else { historySeparators[i+3].setVisible(false); } 312 prev = current; 313 314 } 315 316 } 317 318 // show separators at end if discussion is young 319 if(prev < now-week && separatorsLeft > 0) { historySeparators[VIEW_COUNT].setVisible(true); } 320 else { historySeparators[VIEW_COUNT].setVisible(false); } 321 322 323 if(prev < now-month && separatorsLeft > 0) { historySeparators[VIEW_COUNT].setVisible(true); } 324 else { historySeparators[VIEW_COUNT].setVisible(false); } 325 326 327 if(prev < now-year && separatorsLeft > 0) { historySeparators[VIEW_COUNT+1].setVisible(true); } 328 else { historySeparators[VIEW_COUNT+1].setVisible(false); } 329 330 331 updateButton.update(changed); 332 333 } 334 335}