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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361
  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, String newline) {
  59. return "";
  60. }
  61. });
  62. }
  63. @Override
  64. public void publish(LogRecord record) {
  65. if (!isLoggable(record)) {
  66. return;
  67. }
  68. // If no message is provided, record.getMessage will be null and so
  69. // the formatter.format will fail with NullPointerException (#12588)
  70. if (record.getMessage() == null) {
  71. record.setMessage("");
  72. }
  73. Formatter formatter = getFormatter();
  74. String msg = formatter.format(record);
  75. addRow(record.getLevel(), msg);
  76. }
  77. @Override
  78. public void close() {
  79. // Nothing to do
  80. }
  81. @Override
  82. public void flush() {
  83. // Nothing todo
  84. }
  85. }
  86. // If scroll is not locked, content will be scrolled after delay
  87. private static final int SCROLL_DELAY = 100;
  88. private Timer scrollTimer = null;
  89. // TODO should be persisted
  90. // log content limit
  91. private int limit = 500;
  92. private final DebugButton tabButton = new DebugButton(Icon.LOG,
  93. "Debug message log");
  94. private final HTML content = new HTML();
  95. private final Element contentElement;
  96. private final FlowPanel controls = new FlowPanel();
  97. private final Button clear = new DebugButton(Icon.CLEAR, "Clear log");
  98. private final Button reset = new DebugButton(Icon.RESET_TIMER,
  99. "Reset timer");
  100. private final Button scroll = new DebugButton(Icon.SCROLL_LOCK,
  101. "Scroll lock");
  102. public LogSection() {
  103. contentElement = content.getElement();
  104. content.setStylePrimaryName(VDebugWindow.STYLENAME + "-log");
  105. // clear log button
  106. controls.add(clear);
  107. clear.setStylePrimaryName(VDebugWindow.STYLENAME_BUTTON);
  108. clear.addClickHandler(new ClickHandler() {
  109. @Override
  110. public void onClick(ClickEvent event) {
  111. clear();
  112. }
  113. });
  114. // reset timer button
  115. controls.add(reset);
  116. reset.setStylePrimaryName(VDebugWindow.STYLENAME_BUTTON);
  117. reset.addClickHandler(new ClickHandler() {
  118. @Override
  119. public void onClick(ClickEvent event) {
  120. resetTimer();
  121. }
  122. });
  123. // scroll lock toggle
  124. controls.add(scroll);
  125. scroll.setStylePrimaryName(VDebugWindow.STYLENAME_BUTTON);
  126. scroll.addClickHandler(new ClickHandler() {
  127. @Override
  128. public void onClick(ClickEvent event) {
  129. toggleScrollLock();
  130. }
  131. });
  132. // select message if row is clicked
  133. content.addClickHandler(new ClickHandler() {
  134. @Override
  135. public void onClick(ClickEvent event) {
  136. Element el = Element
  137. .as(event.getNativeEvent().getEventTarget());
  138. while (!el.getClassName().contains(
  139. VDebugWindow.STYLENAME + "-message")) {
  140. if (el == contentElement) {
  141. // clicked something else
  142. return;
  143. }
  144. el = el.getParentElement();
  145. }
  146. selectText(el);
  147. }
  148. });
  149. // Add handler to the root logger
  150. Logger.getLogger("").addHandler(new LogSectionHandler());
  151. }
  152. /**
  153. * Toggles scroll lock, writes state to persistent storage.
  154. */
  155. void toggleScrollLock() {
  156. setScrollLock(scrollTimer != null);
  157. Storage storage = Storage.getLocalStorageIfSupported();
  158. if (storage == null) {
  159. return;
  160. }
  161. VDebugWindow.writeState(storage, "log-scrollLock", scrollTimer == null);
  162. }
  163. /**
  164. * Activates or deactivates scroll lock
  165. *
  166. * @param locked
  167. */
  168. void setScrollLock(boolean locked) {
  169. if (locked && scrollTimer != null) {
  170. scrollTimer.cancel();
  171. scrollTimer = null;
  172. } else if (!locked && scrollTimer == null) {
  173. scrollTimer = new Timer() {
  174. @Override
  175. public void run() {
  176. Element el = (Element) contentElement.getLastChild();
  177. if (el != null) {
  178. el = el.getFirstChildElement();
  179. if (el != null) {
  180. el.scrollIntoView();
  181. }
  182. }
  183. }
  184. };
  185. }
  186. scroll.setStyleDependentName(VDebugWindow.STYLENAME_ACTIVE, locked);
  187. }
  188. private native void selectText(Element el)
  189. /*-{
  190. if ($doc.selection && $doc.selection.createRange) {
  191. var r = $doc.selection.createRange();
  192. r.moveToElementText(el);
  193. r.select();
  194. } else if ($doc.createRange && $wnd.getSelection) {
  195. var r = $doc.createRange();
  196. r.selectNode(el);
  197. var selection = $wnd.getSelection();
  198. selection.removeAllRanges();
  199. selection.addRange(r);
  200. }
  201. }-*/;
  202. private void clear() {
  203. contentElement.setInnerText("");
  204. }
  205. private void applyLimit() {
  206. while (contentElement.getChildCount() > limit) {
  207. contentElement.removeChild(contentElement.getFirstChild());
  208. }
  209. }
  210. /**
  211. * Sets the log row limit.
  212. *
  213. * @param limit
  214. */
  215. public void setLimit(int limit) {
  216. this.limit = limit;
  217. applyLimit();
  218. // TODO shoud be persisted
  219. }
  220. /**
  221. * Gets the current log row limit.
  222. *
  223. * @return
  224. */
  225. public int getLimit() {
  226. // TODO should be read from persistent storage
  227. return limit;
  228. }
  229. @Override
  230. public DebugButton getTabButton() {
  231. return tabButton;
  232. }
  233. @Override
  234. public Widget getControls() {
  235. return controls;
  236. }
  237. @Override
  238. public Widget getContent() {
  239. return content;
  240. }
  241. @Override
  242. public void show() {
  243. Storage storage = Storage.getLocalStorageIfSupported();
  244. if (storage == null) {
  245. return;
  246. }
  247. setScrollLock(VDebugWindow.readState(storage, "log-scrollLock", false));
  248. }
  249. @Override
  250. public void hide() {
  251. // remove timer
  252. setScrollLock(true);
  253. }
  254. /**
  255. * Schedules a scoll if scroll lock is not active.
  256. */
  257. private void maybeScroll() {
  258. if (scrollTimer != null) {
  259. scrollTimer.cancel();
  260. scrollTimer.schedule(SCROLL_DELAY);
  261. }
  262. }
  263. /**
  264. * Resets the timer and inserts a log row indicating this.
  265. */
  266. private void resetTimer() {
  267. int sinceStart = VDebugWindow.getMillisSinceStart();
  268. int sinceReset = VDebugWindow.resetTimer();
  269. Element row = DOM.createDiv();
  270. row.addClassName(VDebugWindow.STYLENAME + "-reset");
  271. row.setInnerHTML(Icon.RESET_TIMER + " Timer reset");
  272. row.setTitle(VDebugWindow.getTimingTooltip(sinceStart, sinceReset));
  273. contentElement.appendChild(row);
  274. maybeScroll();
  275. }
  276. /**
  277. * Adds a row to the log, applies the log row limit by removing old rows if
  278. * needed, and scrolls new row into view if scroll lock is not active.
  279. *
  280. * @param level
  281. * @param msg
  282. * @return
  283. */
  284. private Element addRow(Level level, String msg) {
  285. int sinceReset = VDebugWindow.getMillisSinceReset();
  286. int sinceStart = VDebugWindow.getMillisSinceStart();
  287. Element row = DOM.createDiv();
  288. row.addClassName(VDebugWindow.STYLENAME + "-row");
  289. row.addClassName(level.getName());
  290. String inner = "<span class='" + VDebugWindow.STYLENAME + "-"
  291. + "'></span><span class='" + VDebugWindow.STYLENAME
  292. + "-time' title='"
  293. + VDebugWindow.getTimingTooltip(sinceStart, sinceReset) + "'>"
  294. + sinceReset + "ms</span><span class='"
  295. + VDebugWindow.STYLENAME + "-message'>" + msg + "</span>";
  296. row.setInnerHTML(inner);
  297. contentElement.appendChild(row);
  298. applyLimit();
  299. maybeScroll();
  300. return row;
  301. }
  302. @Override
  303. public void meta(ApplicationConnection ac, ValueMap meta) {
  304. addRow(Level.FINE, "Meta: " + meta.toSource());
  305. }
  306. @Override
  307. public void uidl(ApplicationConnection ac, ValueMap uidl) {
  308. addRow(Level.FINE, "UIDL: " + uidl.toSource());
  309. }
  310. }