001 /*
002 * Copyright 2006 Mat Gessel <mat.gessel@gmail.com>
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
005 * use this file except in compliance with the License. You may obtain a copy of
006 * the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
012 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
013 * License for the specific language governing permissions and limitations under
014 * the License.
015 */
016 package asquare.gwt.debug.client;
017
018 import com.google.gwt.user.client.*;
019 import com.google.gwt.user.client.ui.*;
020
021 /**
022 * An in-browser debug console. By default you can press <code>'w'</code>
023 * twice to disable this console without disabling other debug output. Uses a
024 * {@link asquare.gwt.debug.client.DebugEventListener DebugEventListener} to
025 * listen for the enabler key. See DebugEventListener for potential conflicts
026 * with popup/dialog event filtering.
027 * <p>
028 * It is preferable to print debug statements through
029 * {@link asquare.gwt.debug.client.Debug Debug} since a stub implementation is
030 * provided to remove it from the deliverable.
031 * </p>
032 * <p>
033 * Opera stops displaying text after a while. This may be due to a maximum text node size limitation:
034 * <a href="http://www.quirksmode.org/bugreports/archives/2004/12/text_node_maxim.html">QuirksMode issue</a>.
035 * </p>
036 *
037 * <h3>CSS Style Rules</h3>
038 * <ul class='css'>
039 * <li>.tk-DebugConsole { }</li>
040 * </ul>
041 *
042 * @see asquare.gwt.debug.client.Debug#print(String)
043 * @see asquare.gwt.debug.client.Debug#println(String)
044 * @see asquare.gwt.debug.client.DebugEventListener
045 */
046 public class DebugConsole extends DialogBox
047 {
048 public static final char DEFAULT_ENABLE_KEY = 'w';
049
050 private static DebugConsole s_instance = null;
051
052 private final HTML m_content = new HTML();
053 private final Enabler m_enabler = new Enabler(DEFAULT_ENABLE_KEY);
054 private final Button m_disableButton = new Button();
055
056 private boolean m_initialized = false;
057 private boolean m_enabled = true;
058
059 /**
060 * Creates the console, installs the enabler key listener.
061 * The console is not attached to the DOM yet.
062 */
063 protected DebugConsole()
064 {
065 setStyleName("tk-DebugConsole");
066 DOM.setStyleAttribute(getElement(), "border", "solid black 1px");
067 DOM.setStyleAttribute(getElement(), "background", "white");
068
069 setHTML("<div style='margin: 2px; padding: 3px; background-color: rgb(195, 217, 255); font-weight: bold; font-size: smaller; cursor: default;'>Debug Console</div>");
070
071 m_content.setWordWrap(false);
072 DOM.setStyleAttribute(m_content.getElement(), "margin", "2px");
073 DOM.setStyleAttribute(m_content.getElement(), "padding", "3px");
074
075 VerticalPanel outer = new VerticalPanel();
076
077 ScrollPanel scrollPanel = new ScrollPanel(m_content);
078 scrollPanel.setAlwaysShowScrollBars(true);
079 scrollPanel.setSize("40em", "20em");
080 outer.add(scrollPanel);
081
082 HorizontalPanel controls = new HorizontalPanel();
083 DOM.setStyleAttribute(controls.getElement(), "margin", "2px");
084 controls.setWidth("100%");
085 outer.add(controls);
086
087 HorizontalPanel controlsLeft = new HorizontalPanel();
088 controls.add(controlsLeft);
089 controls.setCellHorizontalAlignment(controlsLeft, HorizontalPanel.ALIGN_LEFT);
090
091 HorizontalPanel controlsRight = new HorizontalPanel();
092 controls.add(controlsRight);
093 controls.setCellHorizontalAlignment(controlsRight, HorizontalPanel.ALIGN_RIGHT);
094
095 final Button taggleDebugButton = new Button("Toggle Debug");
096 DOM.setAttribute(taggleDebugButton.getElement(), "title", "Toggles output of debug statements");
097 controlsLeft.add(taggleDebugButton);
098
099 updateDisableButtonText();
100 DOM.setAttribute(m_disableButton.getElement(), "title", "Prevents this console from appearing when debug statements are printed");
101 controlsLeft.add(m_disableButton);
102
103 final Button clearButton = new Button("Clear");
104 DOM.setAttribute(clearButton.getElement(), "title", "Clears all messages in the console");
105 controlsRight.add(clearButton);
106
107 final Button hideButton = new Button("Hide");
108 DOM.setStyleAttribute(hideButton.getElement(), "text-align", "right");
109 controlsRight.add(hideButton);
110
111 setWidget(outer);
112 int x = Window.getClientWidth() / 2 - 640 / 2;
113 int y = Window.getClientHeight() / 2;
114 setPopupPosition(x, y);
115
116 m_enabler.install();
117
118 ClickListener controller = new ClickListener()
119 {
120 public void onClick(Widget sender)
121 {
122 if (sender == clearButton)
123 {
124 clearMessages();
125 }
126 else if (sender == hideButton)
127 {
128 hide();
129 }
130 else if (sender == m_disableButton)
131 {
132 disable();
133 }
134 else if (sender == taggleDebugButton)
135 {
136 if (Debug.isEnabled())
137 {
138 Debug.disable();
139 }
140 else
141 {
142 Debug.enable();
143 }
144 }
145 else
146 {
147 assert false;
148 }
149 }
150 };
151 taggleDebugButton.addClickListener(controller);
152 m_disableButton.addClickListener(controller);
153 clearButton.addClickListener(controller);
154 hideButton.addClickListener(controller);
155 }
156
157 /**
158 * Get the sole instance of the console, creating if necessary.
159 */
160 public static DebugConsole getInstance()
161 {
162 if (s_instance == null)
163 {
164 s_instance = new DebugConsole();
165 }
166 return s_instance;
167 }
168
169 private void updateDisableButtonText()
170 {
171 m_disableButton.setHTML("Disable Console (" + m_enabler.getEnableKey() + ")");
172 }
173
174 /**
175 * Set the key which is pressed twice to enable/disable the console.
176 *
177 * @param keyCode
178 */
179 public void setEnableKey(char keyCode)
180 {
181 m_enabler.setEnableKey(keyCode);
182 updateDisableButtonText();
183 }
184
185 /**
186 * Enables the console. It will be shown when the next message is printed.
187 */
188 public void enable()
189 {
190 m_enabled = true;
191 }
192
193 /**
194 * Disables and hides the console.
195 */
196 public void disable()
197 {
198 m_enabled = false;
199 hide();
200 }
201
202 /**
203 * Clears all messages from the console.
204 */
205 public void clearMessages()
206 {
207 m_content.setHTML("<PRE style='padding: 0px; margin: 0px'></PRE>");
208 }
209
210 /**
211 * Prints a string to the console.
212 * <em>Direct use of this method is discouraged.</em>
213 *
214 * @param text
215 * @see Debug#print(String)
216 */
217 public void print(String text)
218 {
219 if (m_enabled)
220 {
221 if (! m_initialized)
222 {
223 clearMessages();
224 m_initialized = true;
225 }
226 appendText(m_content.getElement(), text, true);
227 show();
228 }
229 }
230
231 /**
232 * Prints a string to the console, followed by a "\r\n".
233 * <em>Direct use of this method is discouraged.</em>
234 *
235 * @param text
236 * @see Debug#println(String)
237 */
238 public void println(String text)
239 {
240 if (m_enabled)
241 {
242 print(text);
243 print("\r\n");
244 }
245 }
246
247 /*
248 * Copied from JSUtil to eliminate dependency
249 */
250 private static void appendText(Element element, String text, boolean create)
251 {
252 /*
253 * Catch as many error conditions as possible in Java since JSNI error handling sucks
254 */
255 if (element == null)
256 throw new NullPointerException("element cannot be null");
257
258 if (! hasChildNodes(element) && ! create)
259 throw new IllegalArgumentException("element has no child nodes");
260
261 // assume element is now a #text node
262 appendText0(element, text, create);
263 }
264
265 /*
266 * Copied from JSUtil to eliminate dependency
267 */
268 private static native boolean hasChildNodes(Element element) /*-{
269 return element != null && element.hasChildNodes();
270 }-*/;
271
272 /*
273 * Copied from JSUtil to eliminate dependency
274 */
275 private static native void appendText0(Element element, String text, boolean create) /*-{
276 var TEXT_NODE = 3;
277 var node = element;
278 var textNode = null;
279 while(node.firstChild)
280 {
281 if (node.firstChild.nodeType == TEXT_NODE)
282 {
283 textNode = node.firstChild;
284 break;
285 }
286 node = node.firstChild;
287 }
288
289 if (textNode == null)
290 {
291 if (create)
292 {
293 textNode = node.ownerDocument.createTextNode(text);
294 node.appendChild(textNode);
295 return;
296 }
297 else
298 {
299 throw new Error("Couldn't find node of type #text");
300 }
301 }
302
303 textNode.appendData(text);
304 }-*/;
305
306 /**
307 * Overrides {@link PopupPanel#show() popup's} implementation to prevent event filtering ala
308 * {@link com.google.gwt.user.client.EventPreview EventPreview}
309 */
310 public void show()
311 {
312 if (m_enabled && ! isAttached())
313 {
314 RootPanel.get().add(this);
315 }
316 }
317
318 /**
319 * Overrides popup's implementation to prevent event filtering ala
320 * {@link com.google.gwt.user.client.EventPreview EventPreview}
321 */
322 public void hide()
323 {
324 if (isAttached())
325 {
326 RootPanel.get().remove(this);
327 }
328 }
329
330 /**
331 * Listens for the enable key and enables/disables the console.
332 */
333 private final class Enabler extends DebugEventListener
334 {
335 Enabler(char defaultEnableKey)
336 {
337 super(defaultEnableKey, 0, "Debug Console enabler");
338 }
339
340 protected void doDisabled()
341 {
342 DebugConsole.this.disable();
343 }
344
345 protected void doEnabled()
346 {
347 DebugConsole.this.enable();
348 DebugConsole.this.show();
349 }
350
351 protected void doEvent(Event event)
352 {
353 // NOOP
354 }
355 }
356 }