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&nbsp;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&nbsp;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    }