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.

LogSection.java 10KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362
  1. /*
  2. * Copyright 2000-2014 Vaadin Ltd.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  5. * use this file except in compliance with the License. You may obtain a copy of
  6. * the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  12. * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. * License for the specific language governing permissions and limitations under
  14. * the License.
  15. */
  16. package com.vaadin.client.debug.internal;
  17. import java.util.logging.Formatter;
  18. import java.util.logging.Handler;
  19. import java.util.logging.Level;
  20. import java.util.logging.LogRecord;
  21. import java.util.logging.Logger;
  22. import com.google.gwt.dom.client.Element;
  23. import com.google.gwt.event.dom.client.ClickEvent;
  24. import com.google.gwt.event.dom.client.ClickHandler;
  25. import com.google.gwt.logging.client.HtmlLogFormatter;
  26. import com.google.gwt.storage.client.Storage;
  27. import com.google.gwt.user.client.DOM;
  28. import com.google.gwt.user.client.Timer;
  29. import com.google.gwt.user.client.ui.Button;
  30. import com.google.gwt.user.client.ui.FlowPanel;
  31. import com.google.gwt.user.client.ui.HTML;
  32. import com.google.gwt.user.client.ui.Widget;
  33. import com.vaadin.client.ApplicationConnection;
  34. import com.vaadin.client.ValueMap;
  35. /**
  36. * Displays the log messages.
  37. * <p>
  38. * Scroll lock state is persisted.
  39. * </p>
  40. *
  41. * @since 7.1
  42. * @author Vaadin Ltd
  43. */
  44. public class LogSection implements Section {
  45. private final class LogSectionHandler extends Handler {
  46. private LogSectionHandler() {
  47. setLevel(Level.ALL);
  48. setFormatter(new HtmlLogFormatter(true) {
  49. @Override
  50. protected String getHtmlPrefix(LogRecord event) {
  51. return "";
  52. }
  53. @Override
  54. protected String getHtmlSuffix(LogRecord event) {
  55. return "";
  56. }
  57. @Override
  58. protected String getRecordInfo(LogRecord event,
  59. String newline) {
  60. return "";
  61. }
  62. });
  63. }
  64. @Override
  65. public void publish(LogRecord record) {
  66. if (!isLoggable(record)) {
  67. return;
  68. }
  69. // If no message is provided, record.getMessage will be null and so
  70. // the formatter.format will fail with NullPointerException (#12588)
  71. if (record.getMessage() == null) {
  72. record.setMessage("");
  73. }
  74. Formatter formatter = getFormatter();
  75. String msg = formatter.format(record);
  76. addRow(record.getLevel(), msg);
  77. }
  78. @Override
  79. public void close() {
  80. // Nothing to do
  81. }
  82. @Override
  83. public void flush() {
  84. // Nothing todo
  85. }
  86. }
  87. // If scroll is not locked, content will be scrolled after delay
  88. private static final int SCROLL_DELAY = 100;
  89. private Timer scrollTimer = null;
  90. // TODO should be persisted
  91. // log content limit
  92. private int limit = 500;
  93. private final DebugButton tabButton = new DebugButton(Icon.LOG,
  94. "Debug message log");
  95. private final HTML content = new HTML();
  96. private final Element contentElement;
  97. private final FlowPanel controls = new FlowPanel();
  98. private final Button clear = new DebugButton(Icon.CLEAR, "Clear log");
  99. private final Button reset = new DebugButton(Icon.RESET_TIMER,
  100. "Reset timer");
  101. private final Button scroll = new DebugButton(Icon.SCROLL_LOCK,
  102. "Scroll lock");
  103. public LogSection() {
  104. contentElement = content.getElement();
  105. content.setStylePrimaryName(VDebugWindow.STYLENAME + "-log");
  106. // clear log button
  107. controls.add(clear);
  108. clear.setStylePrimaryName(VDebugWindow.STYLENAME_BUTTON);
  109. clear.addClickHandler(new ClickHandler() {
  110. @Override
  111. public void onClick(ClickEvent event) {
  112. clear();
  113. }
  114. });
  115. // reset timer button
  116. controls.add(reset);
  117. reset.setStylePrimaryName(VDebugWindow.STYLENAME_BUTTON);
  118. reset.addClickHandler(new ClickHandler() {
  119. @Override
  120. public void onClick(ClickEvent event) {
  121. resetTimer();
  122. }
  123. });
  124. // scroll lock toggle
  125. controls.add(scroll);
  126. scroll.setStylePrimaryName(VDebugWindow.STYLENAME_BUTTON);
  127. scroll.addClickHandler(new ClickHandler() {
  128. @Override
  129. public void onClick(ClickEvent event) {
  130. toggleScrollLock();
  131. }
  132. });
  133. // select message if row is clicked
  134. content.addClickHandler(new ClickHandler() {
  135. @Override
  136. public void onClick(ClickEvent event) {
  137. Element el = Element
  138. .as(event.getNativeEvent().getEventTarget());
  139. while (!el.getClassName()
  140. .contains(VDebugWindow.STYLENAME + "-message")) {
  141. if (el == contentElement) {
  142. // clicked something else
  143. return;
  144. }
  145. el = el.getParentElement();
  146. }
  147. selectText(el);
  148. }
  149. });
  150. // Add handler to the root logger
  151. Logger.getLogger("").addHandler(new LogSectionHandler());
  152. }
  153. /**
  154. * Toggles scroll lock, writes state to persistent storage.
  155. */
  156. void toggleScrollLock() {
  157. setScrollLock(scrollTimer != null);
  158. Storage storage = Storage.getLocalStorageIfSupported();
  159. if (storage == null) {
  160. return;
  161. }
  162. VDebugWindow.writeState(storage, "log-scrollLock", scrollTimer == null);
  163. }
  164. /**
  165. * Activates or deactivates scroll lock
  166. *
  167. * @param locked
  168. */
  169. void setScrollLock(boolean locked) {
  170. if (locked && scrollTimer != null) {
  171. scrollTimer.cancel();
  172. scrollTimer = null;
  173. } else if (!locked && scrollTimer == null) {
  174. scrollTimer = new Timer() {
  175. @Override
  176. public void run() {
  177. Element el = (Element) contentElement.getLastChild();
  178. if (el != null) {
  179. el = el.getFirstChildElement();
  180. if (el != null) {
  181. el.scrollIntoView();
  182. }
  183. }
  184. }
  185. };
  186. }
  187. scroll.setStyleDependentName(VDebugWindow.STYLENAME_ACTIVE, locked);
  188. }
  189. private native void selectText(Element el)
  190. /*-{
  191. if ($doc.selection && $doc.selection.createRange) {
  192. var r = $doc.selection.createRange();
  193. r.moveToElementText(el);
  194. r.select();
  195. } else if ($doc.createRange && $wnd.getSelection) {
  196. var r = $doc.createRange();
  197. r.selectNode(el);
  198. var selection = $wnd.getSelection();
  199. selection.removeAllRanges();
  200. selection.addRange(r);
  201. }
  202. }-*/;
  203. private void clear() {
  204. contentElement.setInnerText("");
  205. }
  206. private void applyLimit() {
  207. while (contentElement.getChildCount() > limit) {
  208. contentElement.removeChild(contentElement.getFirstChild());
  209. }
  210. }
  211. /**
  212. * Sets the log row limit.
  213. *
  214. * @param limit
  215. */
  216. public void setLimit(int limit) {
  217. this.limit = limit;
  218. applyLimit();
  219. // TODO shoud be persisted
  220. }
  221. /**
  222. * Gets the current log row limit.
  223. *
  224. * @return
  225. */
  226. public int getLimit() {
  227. // TODO should be read from persistent storage
  228. return limit;
  229. }
  230. @Override
  231. public DebugButton getTabButton() {
  232. return tabButton;
  233. }
  234. @Override
  235. public Widget getControls() {
  236. return controls;
  237. }
  238. @Override
  239. public Widget getContent() {
  240. return content;
  241. }
  242. @Override
  243. public void show() {
  244. Storage storage = Storage.getLocalStorageIfSupported();
  245. if (storage == null) {
  246. return;
  247. }
  248. setScrollLock(VDebugWindow.readState(storage, "log-scrollLock", false));
  249. }
  250. @Override
  251. public void hide() {
  252. // remove timer
  253. setScrollLock(true);
  254. }
  255. /**
  256. * Schedules a scoll if scroll lock is not active.
  257. */
  258. private void maybeScroll() {
  259. if (scrollTimer != null) {
  260. scrollTimer.cancel();
  261. scrollTimer.schedule(SCROLL_DELAY);
  262. }
  263. }
  264. /**
  265. * Resets the timer and inserts a log row indicating this.
  266. */
  267. private void resetTimer() {
  268. int sinceStart = VDebugWindow.getMillisSinceStart();
  269. int sinceReset = VDebugWindow.resetTimer();
  270. Element row = DOM.createDiv();
  271. row.addClassName(VDebugWindow.STYLENAME + "-reset");
  272. row.setInnerHTML(Icon.RESET_TIMER + " Timer reset");
  273. row.setTitle(VDebugWindow.getTimingTooltip(sinceStart, sinceReset));
  274. contentElement.appendChild(row);
  275. maybeScroll();
  276. }
  277. /**
  278. * Adds a row to the log, applies the log row limit by removing old rows if
  279. * needed, and scrolls new row into view if scroll lock is not active.
  280. *
  281. * @param level
  282. * @param msg
  283. * @return
  284. */
  285. private Element addRow(Level level, String msg) {
  286. int sinceReset = VDebugWindow.getMillisSinceReset();
  287. int sinceStart = VDebugWindow.getMillisSinceStart();
  288. Element row = DOM.createDiv();
  289. row.addClassName(VDebugWindow.STYLENAME + "-row");
  290. row.addClassName(level.getName());
  291. String inner = "<span class='" + VDebugWindow.STYLENAME + "-"
  292. + "'></span><span class='" + VDebugWindow.STYLENAME
  293. + "-time' title='"
  294. + VDebugWindow.getTimingTooltip(sinceStart, sinceReset) + "'>"
  295. + sinceReset + "ms</span><span class='" + VDebugWindow.STYLENAME
  296. + "-message'>" + msg + "</span>";
  297. row.setInnerHTML(inner);
  298. contentElement.appendChild(row);
  299. applyLimit();
  300. maybeScroll();
  301. return row;
  302. }
  303. @Override
  304. public void meta(ApplicationConnection ac, ValueMap meta) {
  305. addRow(Level.FINE, "Meta: " + meta.toSource());
  306. }
  307. @Override
  308. public void uidl(ApplicationConnection ac, ValueMap uidl) {
  309. addRow(Level.FINE, "UIDL: " + uidl.toSource());
  310. }
  311. }