You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ApplicationConnection.java 57KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633
  1. /*
  2. @ITMillApache2LicenseForJavaFiles@
  3. */
  4. package com.vaadin.terminal.gwt.client;
  5. import java.util.ArrayList;
  6. import java.util.Date;
  7. import java.util.HashMap;
  8. import java.util.HashSet;
  9. import java.util.Iterator;
  10. import java.util.Set;
  11. import java.util.Vector;
  12. import com.google.gwt.core.client.GWT;
  13. import com.google.gwt.core.client.JavaScriptObject;
  14. import com.google.gwt.http.client.Request;
  15. import com.google.gwt.http.client.RequestBuilder;
  16. import com.google.gwt.http.client.RequestCallback;
  17. import com.google.gwt.http.client.RequestException;
  18. import com.google.gwt.http.client.Response;
  19. import com.google.gwt.json.client.JSONArray;
  20. import com.google.gwt.json.client.JSONObject;
  21. import com.google.gwt.json.client.JSONParser;
  22. import com.google.gwt.json.client.JSONString;
  23. import com.google.gwt.json.client.JSONValue;
  24. import com.google.gwt.user.client.Command;
  25. import com.google.gwt.user.client.DOM;
  26. import com.google.gwt.user.client.DeferredCommand;
  27. import com.google.gwt.user.client.Element;
  28. import com.google.gwt.user.client.Event;
  29. import com.google.gwt.user.client.Timer;
  30. import com.google.gwt.user.client.Window;
  31. import com.google.gwt.user.client.impl.HTTPRequestImpl;
  32. import com.google.gwt.user.client.ui.FocusWidget;
  33. import com.google.gwt.user.client.ui.HasWidgets;
  34. import com.google.gwt.user.client.ui.Widget;
  35. import com.vaadin.terminal.gwt.client.RenderInformation.FloatSize;
  36. import com.vaadin.terminal.gwt.client.RenderInformation.Size;
  37. import com.vaadin.terminal.gwt.client.ui.Field;
  38. import com.vaadin.terminal.gwt.client.ui.VContextMenu;
  39. import com.vaadin.terminal.gwt.client.ui.VNotification;
  40. import com.vaadin.terminal.gwt.client.ui.VView;
  41. import com.vaadin.terminal.gwt.client.ui.VNotification.HideEvent;
  42. /**
  43. * Entry point classes define <code>onModuleLoad()</code>.
  44. */
  45. public class ApplicationConnection {
  46. private static final String MODIFIED_CLASSNAME = "i-modified";
  47. private static final String REQUIRED_CLASSNAME_EXT = "-required";
  48. private static final String ERROR_CLASSNAME_EXT = "-error";
  49. public static final String VAR_RECORD_SEPARATOR = "\u001e";
  50. public static final String VAR_FIELD_SEPARATOR = "\u001f";
  51. public static final String VAR_BURST_SEPARATOR = "\u001d";
  52. public static final String VAR_ARRAYITEM_SEPARATOR = "\u001c";
  53. public static final String UIDL_SECURITY_HEADER = "com.vaadin.seckey";
  54. public static final String PARAM_UNLOADBURST = "onunloadburst";
  55. private static String uidl_security_key = "init";
  56. private final HashMap<String, String> resourcesMap = new HashMap<String, String>();
  57. private static Console console;
  58. private final Vector<String> pendingVariables = new Vector<String>();
  59. private final ComponentDetailMap idToPaintableDetail = ComponentDetailMap
  60. .create();
  61. private final WidgetSet widgetSet;
  62. private VContextMenu contextMenu = null;
  63. private Timer loadTimer;
  64. private Timer loadTimer2;
  65. private Timer loadTimer3;
  66. private Element loadElement;
  67. private final VView view;
  68. private boolean applicationRunning = false;
  69. private int activeRequests = 0;
  70. /** Parameters for this application connection loaded from the web-page */
  71. private final ApplicationConfiguration configuration;
  72. /** List of pending variable change bursts that must be submitted in order */
  73. private final Vector<Vector<String>> pendingVariableBursts = new Vector<Vector<String>>();
  74. /** Timer for automatic refirect to SessionExpiredURL */
  75. private Timer redirectTimer;
  76. /** redirectTimer scheduling interval in seconds */
  77. private int sessionExpirationInterval;
  78. private ArrayList<Paintable> relativeSizeChanges = new ArrayList<Paintable>();;
  79. private ArrayList<Paintable> componentCaptionSizeChanges = new ArrayList<Paintable>();;
  80. private Date requestStartTime;
  81. private boolean validatingLayouts = false;
  82. private Set<Paintable> zeroWidthComponents = null;
  83. private Set<Paintable> zeroHeightComponents = null;
  84. public ApplicationConnection(WidgetSet widgetSet,
  85. ApplicationConfiguration cnf) {
  86. this.widgetSet = widgetSet;
  87. configuration = cnf;
  88. windowName = configuration.getInitialWindowName();
  89. if (isDebugMode()) {
  90. console = new VDebugConsole(this, cnf, !isQuietDebugMode());
  91. } else {
  92. console = new NullConsole();
  93. }
  94. ComponentLocator componentLocator = new ComponentLocator(this);
  95. String appRootPanelName = cnf.getRootPanelId();
  96. // remove the end (window name) of autogenarated rootpanel id
  97. appRootPanelName = appRootPanelName.replaceFirst("-\\d+$", "");
  98. initializeTestingToolsHooks(componentLocator, appRootPanelName);
  99. initializeClientHooks();
  100. view = new VView(cnf.getRootPanelId());
  101. showLoadingIndicator();
  102. }
  103. /**
  104. * Starts this application. Don't call this method directly - it's called by
  105. * {@link ApplicationConfiguration#startNextApplication()}, which should be
  106. * called once this application has started (first response received) or
  107. * failed to start. This ensures that the applications are started in order,
  108. * to avoid session-id problems.
  109. */
  110. void start() {
  111. makeUidlRequest("", true, false, false);
  112. }
  113. private native void initializeTestingToolsHooks(
  114. ComponentLocator componentLocator, String TTAppId)
  115. /*-{
  116. var ap = this;
  117. var client = {};
  118. client.isActive = function() {
  119. return ap.@com.vaadin.terminal.gwt.client.ApplicationConnection::hasActiveRequest()();
  120. }
  121. var vi = ap.@com.vaadin.terminal.gwt.client.ApplicationConnection::getVersionInfo()();
  122. if (vi) {
  123. client.getVersionInfo = function() {
  124. return vi;
  125. }
  126. }
  127. client.getElementByPath = function(id) {
  128. return componentLocator.@com.vaadin.terminal.gwt.client.ComponentLocator::getElementByPath(Ljava/lang/String;)(id);
  129. }
  130. client.getPathForElement = function(element) {
  131. return componentLocator.@com.vaadin.terminal.gwt.client.ComponentLocator::getPathForElement(Lcom/google/gwt/user/client/Element;)(element);
  132. }
  133. if(!$wnd.itmill.clients) {
  134. $wnd.itmill.clients = {};
  135. }
  136. $wnd.itmill.clients[TTAppId] = client;
  137. }-*/;
  138. /**
  139. * Helper for tt initialization
  140. */
  141. @SuppressWarnings("unused")
  142. private JavaScriptObject getVersionInfo() {
  143. return configuration.getVersionInfoJSObject();
  144. }
  145. /**
  146. * Publishes a JavaScript API for mash-up applications.
  147. * <ul>
  148. * <li><code>itmill.forceSync()</code> sends pending variable changes, in
  149. * effect synchronizing the server and client state. This is done for all
  150. * applications on host page.</li>
  151. * </ul>
  152. *
  153. * TODO make this multi-app aware
  154. */
  155. private native void initializeClientHooks()
  156. /*-{
  157. var app = this;
  158. var oldSync;
  159. if($wnd.itmill.forceSync) {
  160. oldSync = $wnd.itmill.forceSync;
  161. }
  162. $wnd.itmill.forceSync = function() {
  163. if(oldSync) {
  164. oldSync();
  165. }
  166. app.@com.vaadin.terminal.gwt.client.ApplicationConnection::sendPendingVariableChanges()();
  167. }
  168. var oldForceLayout;
  169. if($wnd.itmill.forceLayout) {
  170. oldForceLayout = $wnd.itmill.forceLayout;
  171. }
  172. $wnd.itmill.forceLayout = function() {
  173. if(oldForceLayout) {
  174. oldForceLayout();
  175. }
  176. app.@com.vaadin.terminal.gwt.client.ApplicationConnection::forceLayout()();
  177. }
  178. }-*/;
  179. public static Console getConsole() {
  180. return console;
  181. }
  182. /**
  183. * Checks if client side is in debug mode. Practically this is invoked by
  184. * adding ?debug parameter to URI.
  185. *
  186. * @return true if client side is currently been debugged
  187. */
  188. public native static boolean isDebugMode()
  189. /*-{
  190. if($wnd.itmill.debug) {
  191. var parameters = $wnd.location.search;
  192. var re = /debug[^\/]*$/;
  193. return re.test(parameters);
  194. } else {
  195. return false;
  196. }
  197. }-*/;
  198. private native static boolean isQuietDebugMode()
  199. /*-{
  200. var uri = $wnd.location;
  201. var re = /debug=q[^\/]*$/;
  202. return re.test(uri);
  203. }-*/;
  204. public String getAppUri() {
  205. return configuration.getApplicationUri();
  206. };
  207. public boolean hasActiveRequest() {
  208. return (activeRequests > 0);
  209. }
  210. private void makeUidlRequest(String requestData, boolean repaintAll,
  211. boolean forceSync, boolean analyzeLayouts) {
  212. startRequest();
  213. // Security: double cookie submission pattern
  214. requestData = uidl_security_key + VAR_BURST_SEPARATOR + requestData;
  215. console.log("Making UIDL Request with params: " + requestData);
  216. String uri = getAppUri() + "UIDL" + configuration.getPathInfo();
  217. if (repaintAll) {
  218. uri += "?repaintAll=1";
  219. if (analyzeLayouts) {
  220. uri += "&analyzeLayouts=1";
  221. }
  222. }
  223. if (windowName != null && windowName.length() > 0) {
  224. uri += (repaintAll ? "&" : "?") + "windowName=" + windowName;
  225. }
  226. if (!forceSync) {
  227. final RequestBuilder rb = new RequestBuilder(RequestBuilder.POST,
  228. uri);
  229. // TODO enable timeout
  230. // rb.setTimeoutMillis(timeoutMillis);
  231. rb.setHeader("Content-Type", "text/plain;charset=utf-8");
  232. try {
  233. rb.sendRequest(requestData, new RequestCallback() {
  234. public void onError(Request request, Throwable exception) {
  235. showCommunicationError(exception.getMessage());
  236. endRequest();
  237. if (!applicationRunning) {
  238. // start failed, let's try to start the next app
  239. ApplicationConfiguration.startNextApplication();
  240. }
  241. }
  242. public void onResponseReceived(Request request,
  243. Response response) {
  244. console.log("Server visit took "
  245. + String.valueOf((new Date()).getTime()
  246. - requestStartTime.getTime()) + "ms");
  247. switch (response.getStatusCode()) {
  248. case 0:
  249. showCommunicationError("Invalid status code 0 (server down?)");
  250. return;
  251. // TODO could add more cases
  252. }
  253. if ("init".equals(uidl_security_key)) {
  254. // Read security key
  255. String key = response
  256. .getHeader(UIDL_SECURITY_HEADER);
  257. if (null != key) {
  258. uidl_security_key = key;
  259. }
  260. }
  261. if (applicationRunning) {
  262. handleReceivedJSONMessage(response);
  263. } else {
  264. applicationRunning = true;
  265. handleWhenCSSLoaded(response);
  266. ApplicationConfiguration.startNextApplication();
  267. }
  268. }
  269. int cssWaits = 0;
  270. static final int MAX_CSS_WAITS = 20;
  271. private void handleWhenCSSLoaded(final Response response) {
  272. int heightOfLoadElement = DOM.getElementPropertyInt(
  273. loadElement, "offsetHeight");
  274. if (heightOfLoadElement == 0
  275. && cssWaits < MAX_CSS_WAITS) {
  276. (new Timer() {
  277. @Override
  278. public void run() {
  279. handleWhenCSSLoaded(response);
  280. }
  281. }).schedule(50);
  282. console
  283. .log("Assuming CSS loading is not complete, "
  284. + "postponing render phase. "
  285. + "(.i-loading-indicator height == 0)");
  286. cssWaits++;
  287. } else {
  288. handleReceivedJSONMessage(response);
  289. if (cssWaits >= MAX_CSS_WAITS) {
  290. console
  291. .error("CSS files may have not loaded properly.");
  292. }
  293. }
  294. }
  295. });
  296. } catch (final RequestException e) {
  297. ClientExceptionHandler.displayError(e);
  298. endRequest();
  299. }
  300. } else {
  301. // Synchronized call, discarded response
  302. syncSendForce(((HTTPRequestImpl) GWT.create(HTTPRequestImpl.class))
  303. .createXmlHTTPRequest(), uri + "&" + PARAM_UNLOADBURST
  304. + "=1", requestData);
  305. }
  306. }
  307. /**
  308. * Shows the communication error notification. The 'details' only go to the
  309. * console for now.
  310. *
  311. * @param details
  312. * Optional details for debugging.
  313. */
  314. private void showCommunicationError(String details) {
  315. console.error("Communication error: " + details);
  316. String html = "";
  317. if (configuration.getCommunicationErrorCaption() != null) {
  318. html += "<h1>" + configuration.getCommunicationErrorCaption()
  319. + "</h1>";
  320. }
  321. if (configuration.getCommunicationErrorMessage() != null) {
  322. html += "<p>" + configuration.getCommunicationErrorMessage()
  323. + "</p>";
  324. }
  325. if (html.length() > 0) {
  326. VNotification n = new VNotification(1000 * 60 * 45);
  327. n.addEventListener(new NotificationRedirect(configuration
  328. .getCommunicationErrorUrl()));
  329. n
  330. .show(html, VNotification.CENTERED_TOP,
  331. VNotification.STYLE_SYSTEM);
  332. } else {
  333. redirect(configuration.getCommunicationErrorUrl());
  334. }
  335. }
  336. private native void syncSendForce(JavaScriptObject xmlHttpRequest,
  337. String uri, String requestData)
  338. /*-{
  339. try {
  340. xmlHttpRequest.open("POST", uri, false);
  341. xmlHttpRequest.setRequestHeader("Content-Type", "text/plain;charset=utf-8");
  342. xmlHttpRequest.send(requestData);
  343. } catch (e) {
  344. // No errors are managed as this is synchronous forceful send that can just fail
  345. }
  346. }-*/;
  347. private void startRequest() {
  348. activeRequests++;
  349. requestStartTime = new Date();
  350. // show initial throbber
  351. if (loadTimer == null) {
  352. loadTimer = new Timer() {
  353. @Override
  354. public void run() {
  355. /*
  356. * IE7 does not properly cancel the event with
  357. * loadTimer.cancel() so we have to check that we really
  358. * should make it visible
  359. */
  360. if (loadTimer != null) {
  361. showLoadingIndicator();
  362. }
  363. }
  364. };
  365. // First one kicks in at 300ms
  366. }
  367. loadTimer.schedule(300);
  368. }
  369. private void endRequest() {
  370. if (applicationRunning) {
  371. checkForPendingVariableBursts();
  372. }
  373. activeRequests--;
  374. // deferring to avoid flickering
  375. DeferredCommand.addCommand(new Command() {
  376. public void execute() {
  377. if (activeRequests == 0) {
  378. hideLoadingIndicator();
  379. }
  380. }
  381. });
  382. }
  383. /**
  384. * This method is called after applying uidl change set to application.
  385. *
  386. * It will clean current and queued variable change sets. And send next
  387. * change set if it exists.
  388. */
  389. private void checkForPendingVariableBursts() {
  390. cleanVariableBurst(pendingVariables);
  391. if (pendingVariableBursts.size() > 0) {
  392. for (Iterator<Vector<String>> iterator = pendingVariableBursts
  393. .iterator(); iterator.hasNext();) {
  394. cleanVariableBurst(iterator.next());
  395. }
  396. Vector<String> nextBurst = pendingVariableBursts.firstElement();
  397. pendingVariableBursts.remove(0);
  398. buildAndSendVariableBurst(nextBurst, false);
  399. }
  400. }
  401. /**
  402. * Cleans given queue of variable changes of such changes that came from
  403. * components that do not exist anymore.
  404. *
  405. * @param variableBurst
  406. */
  407. private void cleanVariableBurst(Vector<String> variableBurst) {
  408. for (int i = 1; i < variableBurst.size(); i += 2) {
  409. String id = variableBurst.get(i);
  410. id = id.substring(0, id.indexOf(VAR_FIELD_SEPARATOR));
  411. if (!idToPaintableDetail.containsKey(id)) {
  412. // variable owner does not exist anymore
  413. variableBurst.remove(i - 1);
  414. variableBurst.remove(i - 1);
  415. i -= 2;
  416. ApplicationConnection.getConsole().log(
  417. "Removed variable from removed component: " + id);
  418. }
  419. }
  420. }
  421. private void showLoadingIndicator() {
  422. // show initial throbber
  423. if (loadElement == null) {
  424. loadElement = DOM.createDiv();
  425. DOM.setStyleAttribute(loadElement, "position", "absolute");
  426. DOM.appendChild(view.getElement(), loadElement);
  427. ApplicationConnection.getConsole().log("inserting load indicator");
  428. }
  429. DOM.setElementProperty(loadElement, "className", "i-loading-indicator");
  430. DOM.setStyleAttribute(loadElement, "display", "block");
  431. // Initialize other timers
  432. loadTimer2 = new Timer() {
  433. @Override
  434. public void run() {
  435. DOM.setElementProperty(loadElement, "className",
  436. "i-loading-indicator-delay");
  437. }
  438. };
  439. // Second one kicks in at 1500ms from request start
  440. loadTimer2.schedule(1200);
  441. loadTimer3 = new Timer() {
  442. @Override
  443. public void run() {
  444. DOM.setElementProperty(loadElement, "className",
  445. "i-loading-indicator-wait");
  446. }
  447. };
  448. // Third one kicks in at 5000ms from request start
  449. loadTimer3.schedule(4700);
  450. }
  451. private void hideLoadingIndicator() {
  452. if (loadTimer != null) {
  453. loadTimer.cancel();
  454. if (loadTimer2 != null) {
  455. loadTimer2.cancel();
  456. loadTimer3.cancel();
  457. }
  458. loadTimer = null;
  459. }
  460. if (loadElement != null) {
  461. DOM.setStyleAttribute(loadElement, "display", "none");
  462. }
  463. }
  464. private void handleReceivedJSONMessage(Response response) {
  465. final Date start = new Date();
  466. String jsonText = response.getText();
  467. // for(;;);[realjson]
  468. jsonText = jsonText.substring(9, jsonText.length() - 1);
  469. JSONValue json;
  470. try {
  471. json = JSONParser.parse(jsonText);
  472. } catch (final com.google.gwt.json.client.JSONException e) {
  473. endRequest();
  474. showCommunicationError(e.getMessage() + " - Original JSON-text:");
  475. console.log(jsonText);
  476. return;
  477. }
  478. // Handle redirect
  479. final JSONObject redirect = (JSONObject) ((JSONObject) json)
  480. .get("redirect");
  481. if (redirect != null) {
  482. final JSONString url = (JSONString) redirect.get("url");
  483. if (url != null) {
  484. console.log("redirecting to " + url.stringValue());
  485. redirect(url.stringValue());
  486. return;
  487. }
  488. }
  489. // Store resources
  490. final JSONObject resources = (JSONObject) ((JSONObject) json)
  491. .get("resources");
  492. for (final Iterator<String> i = resources.keySet().iterator(); i
  493. .hasNext();) {
  494. final String key = i.next();
  495. resourcesMap.put(key, ((JSONString) resources.get(key))
  496. .stringValue());
  497. }
  498. // Store locale data
  499. if (((JSONObject) json).containsKey("locales")) {
  500. final JSONArray l = (JSONArray) ((JSONObject) json).get("locales");
  501. for (int i = 0; i < l.size(); i++) {
  502. LocaleService.addLocale((JSONObject) l.get(i));
  503. }
  504. }
  505. JSONObject meta = null;
  506. if (((JSONObject) json).containsKey("meta")) {
  507. meta = ((JSONObject) json).get("meta").isObject();
  508. if (meta.containsKey("repaintAll")) {
  509. view.clear();
  510. idToPaintableDetail.clear();
  511. if (meta.containsKey("invalidLayouts")) {
  512. validatingLayouts = true;
  513. zeroWidthComponents = new HashSet<Paintable>();
  514. zeroHeightComponents = new HashSet<Paintable>();
  515. }
  516. }
  517. if (meta.containsKey("timedRedirect")) {
  518. final JSONObject timedRedirect = meta.get("timedRedirect")
  519. .isObject();
  520. redirectTimer = new Timer() {
  521. @Override
  522. public void run() {
  523. redirect(timedRedirect.get("url").isString()
  524. .stringValue());
  525. }
  526. };
  527. sessionExpirationInterval = Integer.parseInt(timedRedirect.get(
  528. "interval").toString());
  529. }
  530. }
  531. if (redirectTimer != null) {
  532. redirectTimer.schedule(1000 * sessionExpirationInterval);
  533. }
  534. // Process changes
  535. final JSONArray changes = (JSONArray) ((JSONObject) json)
  536. .get("changes");
  537. Vector<Paintable> updatedWidgets = new Vector<Paintable>();
  538. relativeSizeChanges.clear();
  539. componentCaptionSizeChanges.clear();
  540. for (int i = 0; i < changes.size(); i++) {
  541. try {
  542. final UIDL change = new UIDL((JSONArray) changes.get(i));
  543. try {
  544. console.dirUIDL(change);
  545. } catch (final Exception e) {
  546. ClientExceptionHandler.displayError(e);
  547. // TODO: dir doesn't work in any browser although it should
  548. // work (works in hosted mode)
  549. // it partially did at some part but now broken.
  550. }
  551. final UIDL uidl = change.getChildUIDL(0);
  552. // TODO optimize
  553. final Paintable paintable = getPaintable(uidl.getId());
  554. if (paintable != null) {
  555. paintable.updateFromUIDL(uidl, this);
  556. // paintable may have changed during render to another
  557. // implementation, use the new one for updated widgets map
  558. updatedWidgets.add(idToPaintableDetail.get(uidl.getId())
  559. .getComponent());
  560. } else {
  561. if (!uidl.getTag().equals("window")) {
  562. ClientExceptionHandler
  563. .displayError("Received update for "
  564. + uidl.getTag()
  565. + ", but there is no such paintable ("
  566. + uidl.getId() + ") rendered.");
  567. } else {
  568. view.updateFromUIDL(uidl, this);
  569. }
  570. }
  571. } catch (final Throwable e) {
  572. ClientExceptionHandler.displayError(e);
  573. }
  574. }
  575. // Check which widgets' size has been updated
  576. Set<Paintable> sizeUpdatedWidgets = new HashSet<Paintable>();
  577. updatedWidgets.addAll(relativeSizeChanges);
  578. sizeUpdatedWidgets.addAll(componentCaptionSizeChanges);
  579. for (Paintable paintable : updatedWidgets) {
  580. ComponentDetail detail = idToPaintableDetail.get(getPid(paintable));
  581. Widget widget = (Widget) paintable;
  582. Size oldSize = detail.getOffsetSize();
  583. Size newSize = new Size(widget.getOffsetWidth(), widget
  584. .getOffsetHeight());
  585. if (oldSize == null || !oldSize.equals(newSize)) {
  586. sizeUpdatedWidgets.add(paintable);
  587. detail.setOffsetSize(newSize);
  588. }
  589. }
  590. Util.componentSizeUpdated(sizeUpdatedWidgets);
  591. if (meta != null) {
  592. if (meta.containsKey("appError")) {
  593. JSONObject error = meta.get("appError").isObject();
  594. JSONValue val = error.get("caption");
  595. String html = "";
  596. if (val.isString() != null) {
  597. html += "<h1>" + val.isString().stringValue() + "</h1>";
  598. }
  599. val = error.get("message");
  600. if (val.isString() != null) {
  601. html += "<p>" + val.isString().stringValue() + "</p>";
  602. }
  603. val = error.get("url");
  604. String url = null;
  605. if (val.isString() != null) {
  606. url = val.isString().stringValue();
  607. }
  608. if (html.length() != 0) {
  609. /* 45 min */
  610. VNotification n = new VNotification(1000 * 60 * 45);
  611. n.addEventListener(new NotificationRedirect(url));
  612. n.show(html, VNotification.CENTERED_TOP,
  613. VNotification.STYLE_SYSTEM);
  614. } else {
  615. redirect(url);
  616. }
  617. applicationRunning = false;
  618. }
  619. if (validatingLayouts) {
  620. getConsole().printLayoutProblems(
  621. meta.get("invalidLayouts").isArray(), this,
  622. zeroHeightComponents, zeroWidthComponents);
  623. zeroHeightComponents = null;
  624. zeroWidthComponents = null;
  625. validatingLayouts = false;
  626. }
  627. }
  628. final long prosessingTime = (new Date().getTime()) - start.getTime();
  629. console.log(" Processing time was " + String.valueOf(prosessingTime)
  630. + "ms for " + jsonText.length() + " characters of JSON");
  631. console.log("Referenced paintables: " + idToPaintableDetail.size());
  632. endRequest();
  633. }
  634. /**
  635. * This method assures that all pending variable changes are sent to server.
  636. * Method uses synchronized xmlhttprequest and does not return before the
  637. * changes are sent. No UIDL updates are processed and thut UI is left in
  638. * inconsistent state. This method should be called only when closing
  639. * windows - normally sendPendingVariableChanges() should be used.
  640. */
  641. public void sendPendingVariableChangesSync() {
  642. if (applicationRunning) {
  643. pendingVariableBursts.add(pendingVariables);
  644. Vector<String> nextBurst = pendingVariableBursts.firstElement();
  645. pendingVariableBursts.remove(0);
  646. buildAndSendVariableBurst(nextBurst, true);
  647. }
  648. }
  649. // Redirect browser, null reloads current page
  650. private static native void redirect(String url)
  651. /*-{
  652. if (url) {
  653. $wnd.location = url;
  654. } else {
  655. $wnd.location.reload(false);
  656. }
  657. }-*/;
  658. public void registerPaintable(String id, Paintable paintable) {
  659. ComponentDetail componentDetail = new ComponentDetail();
  660. componentDetail.setComponent(paintable);
  661. idToPaintableDetail.put(id, componentDetail);
  662. setPid(((Widget) paintable).getElement(), id);
  663. }
  664. private native void setPid(Element el, String pid)
  665. /*-{
  666. el.tkPid = pid;
  667. }-*/;
  668. public String getPid(Paintable paintable) {
  669. return getPid(((Widget) paintable).getElement());
  670. }
  671. public native String getPid(Element el)
  672. /*-{
  673. return el.tkPid;
  674. }-*/;
  675. public Element getElementByPid(String pid) {
  676. return ((Widget) getPaintable(pid)).getElement();
  677. }
  678. public void unregisterPaintable(Paintable p) {
  679. if (p == null) {
  680. ApplicationConnection.getConsole().error(
  681. "WARN: Trying to unregister null paintable");
  682. return;
  683. }
  684. String id = getPid(p);
  685. idToPaintableDetail.remove(id);
  686. if (p instanceof HasWidgets) {
  687. unregisterChildPaintables((HasWidgets) p);
  688. }
  689. }
  690. public void unregisterChildPaintables(HasWidgets container) {
  691. final Iterator<Widget> it = container.iterator();
  692. while (it.hasNext()) {
  693. final Widget w = it.next();
  694. if (w instanceof Paintable) {
  695. unregisterPaintable((Paintable) w);
  696. } else if (w instanceof HasWidgets) {
  697. unregisterChildPaintables((HasWidgets) w);
  698. }
  699. }
  700. }
  701. /**
  702. * Returns Paintable element by its id
  703. *
  704. * @param id
  705. * Paintable ID
  706. */
  707. public Paintable getPaintable(String id) {
  708. ComponentDetail componentDetail = idToPaintableDetail.get(id);
  709. if (componentDetail == null) {
  710. return null;
  711. } else {
  712. return componentDetail.getComponent();
  713. }
  714. }
  715. private void addVariableToQueue(String paintableId, String variableName,
  716. String encodedValue, boolean immediate, char type) {
  717. final String id = paintableId + VAR_FIELD_SEPARATOR + variableName
  718. + VAR_FIELD_SEPARATOR + type;
  719. for (int i = 1; i < pendingVariables.size(); i += 2) {
  720. if ((pendingVariables.get(i)).equals(id)) {
  721. pendingVariables.remove(i - 1);
  722. pendingVariables.remove(i - 1);
  723. break;
  724. }
  725. }
  726. pendingVariables.add(encodedValue);
  727. pendingVariables.add(id);
  728. if (immediate) {
  729. sendPendingVariableChanges();
  730. }
  731. }
  732. /**
  733. * This method sends currently queued variable changes to server. It is
  734. * called when immediate variable update must happen.
  735. *
  736. * To ensure correct order for variable changes (due servers multithreading
  737. * or network), we always wait for active request to be handler before
  738. * sending a new one. If there is an active request, we will put varible
  739. * "burst" to queue that will be purged after current request is handled.
  740. *
  741. */
  742. @SuppressWarnings("unchecked")
  743. public void sendPendingVariableChanges() {
  744. if (applicationRunning) {
  745. if (hasActiveRequest()) {
  746. // skip empty queues if there are pending bursts to be sent
  747. if (pendingVariables.size() > 0
  748. || pendingVariableBursts.size() == 0) {
  749. Vector<String> burst = (Vector<String>) pendingVariables
  750. .clone();
  751. pendingVariableBursts.add(burst);
  752. pendingVariables.clear();
  753. }
  754. } else {
  755. buildAndSendVariableBurst(pendingVariables, false);
  756. }
  757. }
  758. }
  759. /**
  760. * Build the variable burst and send it to server.
  761. *
  762. * When sync is forced, we also force sending of all pending variable-bursts
  763. * at the same time. This is ok as we can assume that DOM will newer be
  764. * updated after this.
  765. *
  766. * @param pendingVariables
  767. * Vector of variablechanges to send
  768. * @param forceSync
  769. * Should we use synchronous request?
  770. */
  771. private void buildAndSendVariableBurst(Vector<String> pendingVariables,
  772. boolean forceSync) {
  773. final StringBuffer req = new StringBuffer();
  774. while (!pendingVariables.isEmpty()) {
  775. for (int i = 0; i < pendingVariables.size(); i++) {
  776. if (i > 0) {
  777. if (i % 2 == 0) {
  778. req.append(VAR_RECORD_SEPARATOR);
  779. } else {
  780. req.append(VAR_FIELD_SEPARATOR);
  781. }
  782. }
  783. req.append(pendingVariables.get(i));
  784. }
  785. pendingVariables.clear();
  786. // Append all the busts to this synchronous request
  787. if (forceSync && !pendingVariableBursts.isEmpty()) {
  788. pendingVariables = pendingVariableBursts.firstElement();
  789. pendingVariableBursts.remove(0);
  790. req.append(VAR_BURST_SEPARATOR);
  791. }
  792. }
  793. makeUidlRequest(req.toString(), false, forceSync, false);
  794. }
  795. public void updateVariable(String paintableId, String variableName,
  796. Paintable newValue, boolean immediate) {
  797. String pid = (newValue != null) ? getPid(newValue) : null;
  798. addVariableToQueue(paintableId, variableName, pid, immediate, 'p');
  799. }
  800. public void updateVariable(String paintableId, String variableName,
  801. String newValue, boolean immediate) {
  802. addVariableToQueue(paintableId, variableName, newValue, immediate, 's');
  803. }
  804. public void updateVariable(String paintableId, String variableName,
  805. int newValue, boolean immediate) {
  806. addVariableToQueue(paintableId, variableName, "" + newValue, immediate,
  807. 'i');
  808. }
  809. public void updateVariable(String paintableId, String variableName,
  810. long newValue, boolean immediate) {
  811. addVariableToQueue(paintableId, variableName, "" + newValue, immediate,
  812. 'l');
  813. }
  814. public void updateVariable(String paintableId, String variableName,
  815. float newValue, boolean immediate) {
  816. addVariableToQueue(paintableId, variableName, "" + newValue, immediate,
  817. 'f');
  818. }
  819. public void updateVariable(String paintableId, String variableName,
  820. double newValue, boolean immediate) {
  821. addVariableToQueue(paintableId, variableName, "" + newValue, immediate,
  822. 'd');
  823. }
  824. public void updateVariable(String paintableId, String variableName,
  825. boolean newValue, boolean immediate) {
  826. addVariableToQueue(paintableId, variableName, newValue ? "true"
  827. : "false", immediate, 'b');
  828. }
  829. public void updateVariable(String paintableId, String variableName,
  830. Object[] values, boolean immediate) {
  831. final StringBuffer buf = new StringBuffer();
  832. for (int i = 0; i < values.length; i++) {
  833. if (i > 0) {
  834. buf.append(VAR_ARRAYITEM_SEPARATOR);
  835. }
  836. buf.append(values[i].toString());
  837. }
  838. addVariableToQueue(paintableId, variableName, buf.toString(),
  839. immediate, 'a');
  840. }
  841. /**
  842. * Update generic component features.
  843. *
  844. * <h2>Selecting correct implementation</h2>
  845. *
  846. * <p>
  847. * The implementation of a component depends on many properties, including
  848. * styles, component features, etc. Sometimes the user changes those
  849. * properties after the component has been created. Calling this method in
  850. * the beginning of your updateFromUIDL -method automatically replaces your
  851. * component with more appropriate if the requested implementation changes.
  852. * </p>
  853. *
  854. * <h2>Caption, icon, error messages and description</h2>
  855. *
  856. * <p>
  857. * Component can delegate management of caption, icon, error messages and
  858. * description to parent layout. This is optional an should be decided by
  859. * component author
  860. * </p>
  861. *
  862. * <h2>Component visibility and disabling</h2>
  863. *
  864. * This method will manage component visibility automatically and if
  865. * component is an instanceof FocusWidget, also handle component disabling
  866. * when needed.
  867. *
  868. * @param component
  869. * Widget to be updated, expected to implement an instance of
  870. * Paintable
  871. * @param uidl
  872. * UIDL to be painted
  873. * @param manageCaption
  874. * True if you want to delegate caption, icon, description and
  875. * error message management to parent.
  876. *
  877. * @return Returns true iff no further painting is needed by caller
  878. */
  879. public boolean updateComponent(Widget component, UIDL uidl,
  880. boolean manageCaption) {
  881. String pid = getPid(component.getElement());
  882. if (pid == null) {
  883. getConsole().error(
  884. "Trying to update an unregistered component: "
  885. + Util.getSimpleName(component));
  886. return true;
  887. }
  888. ComponentDetail componentDetail = idToPaintableDetail.get(pid);
  889. if (componentDetail == null) {
  890. getConsole().error(
  891. "ComponentDetail not found for "
  892. + Util.getSimpleName(component) + " with PID "
  893. + pid + ". This should not happen.");
  894. return true;
  895. }
  896. // If the server request that a cached instance should be used, do
  897. // nothing
  898. if (uidl.getBooleanAttribute("cached")) {
  899. return true;
  900. }
  901. // Visibility
  902. boolean visible = !uidl.getBooleanAttribute("invisible");
  903. boolean wasVisible = component.isVisible();
  904. component.setVisible(visible);
  905. if (wasVisible != visible) {
  906. // Changed invisibile <-> visible
  907. if (wasVisible && manageCaption) {
  908. // Must hide caption when component is hidden
  909. final Container parent = Util.getLayout(component);
  910. if (parent != null) {
  911. parent.updateCaption((Paintable) component, uidl);
  912. }
  913. }
  914. }
  915. if (!visible) {
  916. // component is invisible, delete old size to notify parent, if
  917. // later make visible
  918. componentDetail.setOffsetSize(null);
  919. return true;
  920. }
  921. // Switch to correct implementation if needed
  922. if (!widgetSet.isCorrectImplementation(component, uidl)) {
  923. final Container parent = Util.getLayout(component);
  924. if (parent != null) {
  925. final Widget w = (Widget) widgetSet.createWidget(uidl);
  926. parent.replaceChildComponent(component, w);
  927. unregisterPaintable((Paintable) component);
  928. registerPaintable(uidl.getId(), (Paintable) w);
  929. ((Paintable) w).updateFromUIDL(uidl, this);
  930. return true;
  931. }
  932. }
  933. boolean enabled = !uidl.getBooleanAttribute("disabled");
  934. if (component instanceof FocusWidget) {
  935. FocusWidget fw = (FocusWidget) component;
  936. fw.setEnabled(enabled);
  937. if (uidl.hasAttribute("tabindex")) {
  938. fw.setTabIndex(uidl.getIntAttribute("tabindex"));
  939. }
  940. }
  941. StringBuffer styleBuf = new StringBuffer();
  942. final String primaryName = component.getStylePrimaryName();
  943. styleBuf.append(primaryName);
  944. // first disabling and read-only status
  945. if (!enabled) {
  946. styleBuf.append(" ");
  947. styleBuf.append("i-disabled");
  948. }
  949. if (uidl.getBooleanAttribute("readonly")) {
  950. styleBuf.append(" ");
  951. styleBuf.append("i-readonly");
  952. }
  953. // add additional styles as css classes, prefixed with component default
  954. // stylename
  955. if (uidl.hasAttribute("style")) {
  956. final String[] styles = uidl.getStringAttribute("style").split(" ");
  957. for (int i = 0; i < styles.length; i++) {
  958. styleBuf.append(" ");
  959. styleBuf.append(primaryName);
  960. styleBuf.append("-");
  961. styleBuf.append(styles[i]);
  962. styleBuf.append(" ");
  963. styleBuf.append(styles[i]);
  964. }
  965. }
  966. // add modified classname to Fields
  967. if (uidl.hasAttribute("modified") && component instanceof Field) {
  968. styleBuf.append(" ");
  969. styleBuf.append(MODIFIED_CLASSNAME);
  970. }
  971. TooltipInfo tooltipInfo = componentDetail.getTooltipInfo();
  972. if (uidl.hasAttribute("description")) {
  973. tooltipInfo.setTitle(uidl.getStringAttribute("description"));
  974. } else {
  975. tooltipInfo.setTitle(null);
  976. }
  977. // add error classname to components w/ error
  978. if (uidl.hasAttribute("error")) {
  979. styleBuf.append(" ");
  980. styleBuf.append(primaryName);
  981. styleBuf.append(ERROR_CLASSNAME_EXT);
  982. tooltipInfo.setErrorUidl(uidl.getErrors());
  983. } else {
  984. tooltipInfo.setErrorUidl(null);
  985. }
  986. // add required style to required components
  987. if (uidl.hasAttribute("required")) {
  988. styleBuf.append(" ");
  989. styleBuf.append(primaryName);
  990. styleBuf.append(REQUIRED_CLASSNAME_EXT);
  991. }
  992. // Styles + disabled & readonly
  993. component.setStyleName(styleBuf.toString());
  994. // Set captions
  995. if (manageCaption) {
  996. final Container parent = Util.getLayout(component);
  997. if (parent != null) {
  998. parent.updateCaption((Paintable) component, uidl);
  999. }
  1000. }
  1001. if (configuration.useDebugIdInDOM() && uidl.getId().startsWith("PID_S")) {
  1002. DOM.setElementProperty(component.getElement(), "id", uidl.getId()
  1003. .substring(5));
  1004. }
  1005. /*
  1006. * updateComponentSize need to be after caption update so caption can be
  1007. * taken into account
  1008. */
  1009. updateComponentSize(componentDetail, uidl);
  1010. return false;
  1011. }
  1012. private void updateComponentSize(ComponentDetail cd, UIDL uidl) {
  1013. String w = uidl.hasAttribute("width") ? uidl
  1014. .getStringAttribute("width") : "";
  1015. String h = uidl.hasAttribute("height") ? uidl
  1016. .getStringAttribute("height") : "";
  1017. float relativeWidth = Util.parseRelativeSize(w);
  1018. float relativeHeight = Util.parseRelativeSize(h);
  1019. // First update maps so they are correct in the setHeight/setWidth calls
  1020. if (relativeHeight >= 0.0 || relativeWidth >= 0.0) {
  1021. // One or both is relative
  1022. FloatSize relativeSize = new FloatSize(relativeWidth,
  1023. relativeHeight);
  1024. if (cd.getRelativeSize() == null && cd.getOffsetSize() != null) {
  1025. // The component has changed from absolute size to relative size
  1026. relativeSizeChanges.add(cd.getComponent());
  1027. }
  1028. cd.setRelativeSize(relativeSize);
  1029. } else if (relativeHeight < 0.0 && relativeWidth < 0.0) {
  1030. if (cd.getRelativeSize() != null) {
  1031. // The component has changed from relative size to absolute size
  1032. relativeSizeChanges.add(cd.getComponent());
  1033. }
  1034. cd.setRelativeSize(null);
  1035. }
  1036. Widget component = (Widget) cd.getComponent();
  1037. // Set absolute sizes
  1038. if (relativeHeight < 0.0) {
  1039. component.setHeight(h);
  1040. }
  1041. if (relativeWidth < 0.0) {
  1042. component.setWidth(w);
  1043. }
  1044. // Set relative sizes
  1045. if (relativeHeight >= 0.0 || relativeWidth >= 0.0) {
  1046. // One or both is relative
  1047. handleComponentRelativeSize(cd);
  1048. }
  1049. }
  1050. /**
  1051. * Traverses recursively child widgets until ContainerResizedListener child
  1052. * widget is found. They will delegate it further if needed.
  1053. *
  1054. * @param container
  1055. */
  1056. private boolean runningLayout = false;
  1057. public void runDescendentsLayout(HasWidgets container) {
  1058. if (runningLayout) {
  1059. // getConsole().log(
  1060. // "Already running descendents layout. Not running again for "
  1061. // + Util.getSimpleName(container));
  1062. return;
  1063. }
  1064. runningLayout = true;
  1065. internalRunDescendentsLayout(container);
  1066. runningLayout = false;
  1067. }
  1068. /**
  1069. * This will cause re-layouting of all components. Mainly used for
  1070. * development. Published to JavaScript.
  1071. */
  1072. public void forceLayout() {
  1073. Set<Paintable> set = new HashSet<Paintable>();
  1074. for (ComponentDetail cd : idToPaintableDetail.values()) {
  1075. set.add(cd.getComponent());
  1076. }
  1077. Util.componentSizeUpdated(set);
  1078. }
  1079. private void internalRunDescendentsLayout(HasWidgets container) {
  1080. // getConsole().log(
  1081. // "runDescendentsLayout(" + Util.getSimpleName(container) + ")");
  1082. final Iterator<Widget> childWidgets = container.iterator();
  1083. while (childWidgets.hasNext()) {
  1084. final Widget child = childWidgets.next();
  1085. if (child instanceof Paintable) {
  1086. if (handleComponentRelativeSize(child)) {
  1087. /*
  1088. * Only need to propagate event if "child" has a relative
  1089. * size
  1090. */
  1091. if (child instanceof ContainerResizedListener) {
  1092. ((ContainerResizedListener) child).iLayout();
  1093. }
  1094. if (child instanceof HasWidgets) {
  1095. final HasWidgets childContainer = (HasWidgets) child;
  1096. internalRunDescendentsLayout(childContainer);
  1097. }
  1098. }
  1099. } else if (child instanceof HasWidgets) {
  1100. // propagate over non Paintable HasWidgets
  1101. internalRunDescendentsLayout((HasWidgets) child);
  1102. }
  1103. }
  1104. }
  1105. /**
  1106. * Converts relative sizes into pixel sizes.
  1107. *
  1108. * @param child
  1109. * @return true if the child has a relative size
  1110. */
  1111. private boolean handleComponentRelativeSize(ComponentDetail cd) {
  1112. if (cd == null) {
  1113. return false;
  1114. }
  1115. boolean debugSizes = false;
  1116. FloatSize relativeSize = cd.getRelativeSize();
  1117. if (relativeSize == null) {
  1118. return false;
  1119. }
  1120. Widget widget = (Widget) cd.getComponent();
  1121. boolean horizontalScrollBar = false;
  1122. boolean verticalScrollBar = false;
  1123. Container parent = Util.getLayout(widget);
  1124. RenderSpace renderSpace;
  1125. // Parent-less components (like sub-windows) are relative to browser
  1126. // window.
  1127. if (parent == null) {
  1128. renderSpace = new RenderSpace(Window.getClientWidth(), Window
  1129. .getClientHeight());
  1130. } else {
  1131. renderSpace = parent.getAllocatedSpace(widget);
  1132. }
  1133. if (relativeSize.getHeight() >= 0) {
  1134. if (renderSpace != null) {
  1135. if (renderSpace.getScrollbarSize() > 0) {
  1136. if (relativeSize.getWidth() > 100) {
  1137. horizontalScrollBar = true;
  1138. } else if (relativeSize.getWidth() < 0
  1139. && renderSpace.getWidth() > 0) {
  1140. int offsetWidth = widget.getOffsetWidth();
  1141. int width = renderSpace.getWidth();
  1142. if (offsetWidth > width) {
  1143. horizontalScrollBar = true;
  1144. }
  1145. }
  1146. }
  1147. int height = renderSpace.getHeight();
  1148. if (horizontalScrollBar) {
  1149. height -= renderSpace.getScrollbarSize();
  1150. }
  1151. if (validatingLayouts && height <= 0) {
  1152. zeroHeightComponents.add(cd.getComponent());
  1153. }
  1154. height = (int) (height * relativeSize.getHeight() / 100.0);
  1155. if (height < 0) {
  1156. height = 0;
  1157. }
  1158. if (debugSizes) {
  1159. getConsole()
  1160. .log(
  1161. "Widget "
  1162. + Util.getSimpleName(widget)
  1163. + "/"
  1164. + getPid(widget.getElement())
  1165. + " relative height "
  1166. + relativeSize.getHeight()
  1167. + "% of "
  1168. + renderSpace.getHeight()
  1169. + "px (reported by "
  1170. + Util.getSimpleName(parent)
  1171. + "/"
  1172. + (parent == null ? "?" : parent
  1173. .hashCode()) + ") : "
  1174. + height + "px");
  1175. }
  1176. widget.setHeight(height + "px");
  1177. } else {
  1178. widget.setHeight(relativeSize.getHeight() + "%");
  1179. ApplicationConnection.getConsole().error(
  1180. Util.getLayout(widget).getClass().getName()
  1181. + " did not produce allocatedSpace for "
  1182. + widget.getClass().getName());
  1183. }
  1184. }
  1185. if (relativeSize.getWidth() >= 0) {
  1186. if (renderSpace != null) {
  1187. int width = renderSpace.getWidth();
  1188. if (renderSpace.getScrollbarSize() > 0) {
  1189. if (relativeSize.getHeight() > 100) {
  1190. verticalScrollBar = true;
  1191. } else if (relativeSize.getHeight() < 0
  1192. && renderSpace.getHeight() > 0
  1193. && widget.getOffsetHeight() > renderSpace
  1194. .getHeight()) {
  1195. verticalScrollBar = true;
  1196. }
  1197. }
  1198. if (verticalScrollBar) {
  1199. width -= renderSpace.getScrollbarSize();
  1200. }
  1201. if (validatingLayouts && width <= 0) {
  1202. zeroWidthComponents.add(cd.getComponent());
  1203. }
  1204. width = (int) (width * relativeSize.getWidth() / 100.0);
  1205. if (width < 0) {
  1206. width = 0;
  1207. }
  1208. if (debugSizes) {
  1209. getConsole().log(
  1210. "Widget " + Util.getSimpleName(widget) + "/"
  1211. + getPid(widget.getElement())
  1212. + " relative width "
  1213. + relativeSize.getWidth() + "% of "
  1214. + renderSpace.getWidth()
  1215. + "px (reported by "
  1216. + Util.getSimpleName(parent) + "/"
  1217. + (parent == null ? "?" : getPid(parent))
  1218. + ") : " + width + "px");
  1219. }
  1220. widget.setWidth(width + "px");
  1221. } else {
  1222. widget.setWidth(relativeSize.getWidth() + "%");
  1223. ApplicationConnection.getConsole().error(
  1224. Util.getLayout(widget).getClass().getName()
  1225. + " did not produce allocatedSpace for "
  1226. + widget.getClass().getName());
  1227. }
  1228. }
  1229. return true;
  1230. }
  1231. /**
  1232. * Converts relative sizes into pixel sizes.
  1233. *
  1234. * @param child
  1235. * @return true if the child has a relative size
  1236. */
  1237. public boolean handleComponentRelativeSize(Widget child) {
  1238. return handleComponentRelativeSize(idToPaintableDetail.get(getPid(child
  1239. .getElement())));
  1240. }
  1241. public FloatSize getRelativeSize(Widget widget) {
  1242. return idToPaintableDetail.get(getPid(widget.getElement()))
  1243. .getRelativeSize();
  1244. }
  1245. /**
  1246. * Get either existing or new Paintable for given UIDL.
  1247. *
  1248. * If corresponding Paintable has been previously painted, return it.
  1249. * Otherwise create and register a new Paintable from UIDL. Caller must
  1250. * update the returned Paintable from UIDL after it has been connected to
  1251. * parent.
  1252. *
  1253. * @param uidl
  1254. * UIDL to create Paintable from.
  1255. * @return Either existing or new Paintable corresponding to UIDL.
  1256. */
  1257. public Paintable getPaintable(UIDL uidl) {
  1258. final String id = uidl.getId();
  1259. Paintable w = getPaintable(id);
  1260. if (w != null) {
  1261. return w;
  1262. } else {
  1263. w = widgetSet.createWidget(uidl);
  1264. registerPaintable(id, w);
  1265. return w;
  1266. }
  1267. }
  1268. /**
  1269. * Returns a Paintable element by its root element
  1270. *
  1271. * @param element
  1272. * Root element of the paintable
  1273. */
  1274. public Paintable getPaintable(Element element) {
  1275. return getPaintable(getPid(element));
  1276. }
  1277. public String getResource(String name) {
  1278. return resourcesMap.get(name);
  1279. }
  1280. /**
  1281. * Singleton method to get instance of app's context menu.
  1282. *
  1283. * @return VContextMenu object
  1284. */
  1285. public VContextMenu getContextMenu() {
  1286. if (contextMenu == null) {
  1287. contextMenu = new VContextMenu();
  1288. DOM.setElementProperty(contextMenu.getElement(), "id",
  1289. "PID_TOOLKIT_CM");
  1290. }
  1291. return contextMenu;
  1292. }
  1293. /**
  1294. * Translates custom protocols in UIRL URI's to be recognizable by browser.
  1295. * All uri's from UIDL should be routed via this method before giving them
  1296. * to browser due URI's in UIDL may contain custom protocols like theme://.
  1297. *
  1298. * @param toolkitUri
  1299. * toolkit URI from uidl
  1300. * @return translated URI ready for browser
  1301. */
  1302. public String translateToolkitUri(String toolkitUri) {
  1303. if (toolkitUri == null) {
  1304. return null;
  1305. }
  1306. if (toolkitUri.startsWith("theme://")) {
  1307. final String themeUri = configuration.getThemeUri();
  1308. if (themeUri == null) {
  1309. console
  1310. .error("Theme not set: ThemeResource will not be found. ("
  1311. + toolkitUri + ")");
  1312. }
  1313. toolkitUri = themeUri + toolkitUri.substring(7);
  1314. }
  1315. return toolkitUri;
  1316. }
  1317. public String getThemeUri() {
  1318. return configuration.getThemeUri();
  1319. }
  1320. /**
  1321. * Listens for Notification hide event, and redirects. Used for system
  1322. * messages, such as session expired.
  1323. *
  1324. */
  1325. private class NotificationRedirect implements VNotification.EventListener {
  1326. String url;
  1327. NotificationRedirect(String url) {
  1328. this.url = url;
  1329. }
  1330. public void notificationHidden(HideEvent event) {
  1331. redirect(url);
  1332. }
  1333. }
  1334. /* Extended title handling */
  1335. /**
  1336. * Data showed in tooltips are stored centrilized as it may be needed in
  1337. * varios place: caption, layouts, and in owner components themselves.
  1338. *
  1339. * Updating TooltipInfo is done in updateComponent method.
  1340. *
  1341. */
  1342. public TooltipInfo getTitleInfo(Paintable titleOwner) {
  1343. if (null == titleOwner) {
  1344. return null;
  1345. }
  1346. ComponentDetail pd = idToPaintableDetail.get(getPid(titleOwner));
  1347. if (null != pd) {
  1348. return pd.getTooltipInfo();
  1349. } else {
  1350. return null;
  1351. }
  1352. }
  1353. private final VTooltip tooltip = new VTooltip(this);
  1354. /**
  1355. * Component may want to delegate Tooltip handling to client. Layouts add
  1356. * Tooltip (description, errors) to caption, but some components may want
  1357. * them to appear one other elements too.
  1358. *
  1359. * Events wanted by this handler are same as in Tooltip.TOOLTIP_EVENTS
  1360. *
  1361. * @param event
  1362. * @param owner
  1363. */
  1364. public void handleTooltipEvent(Event event, Paintable owner) {
  1365. tooltip.handleTooltipEvent(event, owner);
  1366. }
  1367. /**
  1368. * Adds PNG-fix conditionally (only for IE6) to the specified IMG -element.
  1369. *
  1370. * @param el
  1371. * the IMG element to fix
  1372. */
  1373. public void addPngFix(Element el) {
  1374. BrowserInfo b = BrowserInfo.get();
  1375. if (b.isIE6()) {
  1376. Util.addPngFix(el, getThemeUri()
  1377. + "/../default/common/img/blank.gif");
  1378. }
  1379. }
  1380. /*
  1381. * Helper to run layout functions triggered by child components with a
  1382. * decent interval.
  1383. */
  1384. private final Timer layoutTimer = new Timer() {
  1385. private boolean isPending = false;
  1386. @Override
  1387. public void schedule(int delayMillis) {
  1388. if (!isPending) {
  1389. super.schedule(delayMillis);
  1390. isPending = true;
  1391. }
  1392. }
  1393. @Override
  1394. public void run() {
  1395. getConsole().log(
  1396. "Running re-layout of " + view.getClass().getName());
  1397. runDescendentsLayout(view);
  1398. isPending = false;
  1399. }
  1400. };
  1401. /**
  1402. * Components can call this function to run all layout functions. This is
  1403. * usually done, when component knows that its size has changed.
  1404. */
  1405. public void requestLayoutPhase() {
  1406. layoutTimer.schedule(500);
  1407. }
  1408. private String windowName = null;
  1409. /**
  1410. * Reset the name of the current browser-window. This should reflect the
  1411. * window-name used in the server, but might be different from the
  1412. * window-object target-name on client.
  1413. *
  1414. * @param stringAttribute
  1415. * New name for the window.
  1416. */
  1417. public void setWindowName(String newName) {
  1418. windowName = newName;
  1419. }
  1420. public void captionSizeUpdated(Paintable component) {
  1421. componentCaptionSizeChanges.add(component);
  1422. }
  1423. public void analyzeLayouts() {
  1424. makeUidlRequest("", true, false, true);
  1425. }
  1426. public VView getView() {
  1427. return view;
  1428. }
  1429. }