Browse Source

Initial import of web UI

Change-Id: Ia9638fb1a3c2c286af6b6d87e293ebef884778cc
Reviewed-on: http://gerrit.dmdirc.com/608
Automatic-Compile: Gregory Holmes <greg@dmdirc.com>
Reviewed-by: Gregory Holmes <greg@dmdirc.com>
tags/0.6.3
Chris Smith 14 years ago
parent
commit
91f162643d
43 changed files with 11784 additions and 0 deletions
  1. BIN
      lib/commons-lang-2.4.jar
  2. BIN
      lib/jetty-6.1.22.jar
  3. BIN
      lib/jetty-util-6.1.22.jar
  4. BIN
      lib/servlet-api-2.5-20081211.jar
  5. 112
    0
      src/com/dmdirc/addons/ui_web/Client.java
  6. 84
    0
      src/com/dmdirc/addons/ui_web/DMDircRequestHandler.java
  7. 367
    0
      src/com/dmdirc/addons/ui_web/DynamicRequestHandler.java
  8. 50
    0
      src/com/dmdirc/addons/ui_web/Event.java
  9. 49
    0
      src/com/dmdirc/addons/ui_web/Message.java
  10. 61
    0
      src/com/dmdirc/addons/ui_web/RootRequestHandler.java
  11. 87
    0
      src/com/dmdirc/addons/ui_web/StaticRequestHandler.java
  12. 75
    0
      src/com/dmdirc/addons/ui_web/WebInterfacePlugin.java
  13. 285
    0
      src/com/dmdirc/addons/ui_web/WebInterfaceUI.java
  14. 77
    0
      src/com/dmdirc/addons/ui_web/WebPrincipal.java
  15. 143
    0
      src/com/dmdirc/addons/ui_web/WebUserRealm.java
  16. 28
    0
      src/com/dmdirc/addons/ui_web/plugin.config
  17. 39
    0
      src/com/dmdirc/addons/ui_web/res/default.html
  18. BIN
      src/com/dmdirc/addons/ui_web/res/images/background.png
  19. BIN
      src/com/dmdirc/addons/ui_web/res/images/dmdirc.gif
  20. BIN
      src/com/dmdirc/addons/ui_web/res/images/dmdirc.png
  21. 136
    0
      src/com/dmdirc/addons/ui_web/res/javascript/builder.js
  22. 965
    0
      src/com/dmdirc/addons/ui_web/res/javascript/controls.js
  23. 538
    0
      src/com/dmdirc/addons/ui_web/res/javascript/dmdirc.js
  24. 974
    0
      src/com/dmdirc/addons/ui_web/res/javascript/dragdrop.js
  25. 1122
    0
      src/com/dmdirc/addons/ui_web/res/javascript/effects.js
  26. 4170
    0
      src/com/dmdirc/addons/ui_web/res/javascript/prototype.js
  27. 58
    0
      src/com/dmdirc/addons/ui_web/res/javascript/scriptaculous.js
  28. 275
    0
      src/com/dmdirc/addons/ui_web/res/javascript/slider.js
  29. 55
    0
      src/com/dmdirc/addons/ui_web/res/javascript/sound.js
  30. 568
    0
      src/com/dmdirc/addons/ui_web/res/javascript/unittest.js
  31. 42
    0
      src/com/dmdirc/addons/ui_web/res/newserverdialog.html
  32. 243
    0
      src/com/dmdirc/addons/ui_web/res/style.css
  33. 38
    0
      src/com/dmdirc/addons/ui_web/res/webuistatus.html
  34. 86
    0
      src/com/dmdirc/addons/ui_web/uicomponents/WebChannelWindow.java
  35. 89
    0
      src/com/dmdirc/addons/ui_web/uicomponents/WebFrameManager.java
  36. 148
    0
      src/com/dmdirc/addons/ui_web/uicomponents/WebInputField.java
  37. 106
    0
      src/com/dmdirc/addons/ui_web/uicomponents/WebInputHandler.java
  38. 111
    0
      src/com/dmdirc/addons/ui_web/uicomponents/WebInputWindow.java
  39. 90
    0
      src/com/dmdirc/addons/ui_web/uicomponents/WebMainWindow.java
  40. 42
    0
      src/com/dmdirc/addons/ui_web/uicomponents/WebQueryWindow.java
  41. 43
    0
      src/com/dmdirc/addons/ui_web/uicomponents/WebServerWindow.java
  42. 98
    0
      src/com/dmdirc/addons/ui_web/uicomponents/WebStatusBar.java
  43. 330
    0
      src/com/dmdirc/addons/ui_web/uicomponents/WebWindow.java

BIN
lib/commons-lang-2.4.jar View File


BIN
lib/jetty-6.1.22.jar View File


BIN
lib/jetty-util-6.1.22.jar View File


BIN
lib/servlet-api-2.5-20081211.jar View File


+ 112
- 0
src/com/dmdirc/addons/ui_web/Client.java View File

@@ -0,0 +1,112 @@
1
+/*
2
+ * Copyright (c) 2006-2010 Chris Smith, Shane Mc Cormack, Gregory Holmes
3
+ *
4
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ * of this software and associated documentation files (the "Software"), to deal
6
+ * in the Software without restriction, including without limitation the rights
7
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ * copies of the Software, and to permit persons to whom the Software is
9
+ * furnished to do so, subject to the following conditions:
10
+ *
11
+ * The above copyright notice and this permission notice shall be included in
12
+ * all copies or substantial portions of the Software.
13
+ *
14
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ * SOFTWARE.
21
+ */
22
+
23
+package com.dmdirc.addons.ui_web;
24
+
25
+import com.dmdirc.ui.WindowManager;
26
+import com.dmdirc.ui.interfaces.Window;
27
+import com.dmdirc.addons.ui_web.uicomponents.WebWindow;
28
+import java.util.LinkedList;
29
+import java.util.List;
30
+import org.mortbay.util.ajax.Continuation;
31
+
32
+/**
33
+ *
34
+ * @author chris
35
+ */
36
+public class Client {
37
+    
38
+    private long lastSeenTime = System.currentTimeMillis();
39
+
40
+    private Continuation continuation;
41
+    
42
+    private final String ip;
43
+    
44
+    private final List<Event> events = new LinkedList<Event>();
45
+
46
+    public Client(final String ip) {
47
+        events.add(new Event("statusbar", "Welcome to the DMDirc web interface"));
48
+        
49
+        this.ip = ip;
50
+        
51
+        final List<Window> added = new LinkedList<Window>();
52
+        final List<Window> queued = new LinkedList<Window>(WebWindow.getWindows());
53
+
54
+        while (!queued.isEmpty()) {
55
+            final Window window = queued.remove(0);
56
+            final Window parent = WindowManager.getParent(window);
57
+            
58
+            if (parent == null) {
59
+                events.add(new Event("newwindow", window));
60
+                added.add(window);
61
+            } else if (added.contains(parent)) {
62
+                events.add(new Event("newchildwindow", new Object[]{parent, window}));
63
+                added.add(window);
64
+            } else {
65
+                queued.add(window);
66
+            }
67
+        }
68
+    }
69
+    
70
+    public String getIp() {
71
+        return ip;
72
+    }
73
+    
74
+    public long getTime() {
75
+        return System.currentTimeMillis() - lastSeenTime;
76
+    }
77
+
78
+    public Object getMutex() {
79
+        return events;
80
+    }
81
+
82
+    public void setContinuation(Continuation continuation) {
83
+        this.continuation = continuation;
84
+    }
85
+    
86
+    public void touch() {
87
+        lastSeenTime = System.currentTimeMillis();
88
+    }
89
+    
90
+    public void addEvent(final Event event) {
91
+        synchronized (events) {
92
+            events.add(event);
93
+
94
+            if (continuation != null) {
95
+                continuation.resume();
96
+            }
97
+        }
98
+    }
99
+    
100
+    public List<Event> retrieveEvents() {
101
+        synchronized (events) {
102
+            final List<Event> res = new LinkedList<Event>(events);
103
+            events.clear();
104
+            return res;
105
+        }
106
+    }
107
+    
108
+    public int getEventCount() {
109
+        return events.size();
110
+    }
111
+
112
+}

+ 84
- 0
src/com/dmdirc/addons/ui_web/DMDircRequestHandler.java View File

@@ -0,0 +1,84 @@
1
+/*
2
+ * Copyright (c) 2006-2010 Chris Smith, Shane Mc Cormack, Gregory Holmes
3
+ *
4
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ * of this software and associated documentation files (the "Software"), to deal
6
+ * in the Software without restriction, including without limitation the rights
7
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ * copies of the Software, and to permit persons to whom the Software is
9
+ * furnished to do so, subject to the following conditions:
10
+ *
11
+ * The above copyright notice and this permission notice shall be included in
12
+ * all copies or substantial portions of the Software.
13
+ *
14
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ * SOFTWARE.
21
+ */
22
+
23
+package com.dmdirc.addons.ui_web;
24
+
25
+import com.dmdirc.util.resourcemanager.ResourceManager;
26
+
27
+import java.io.IOException;
28
+
29
+import javax.servlet.http.HttpServletRequest;
30
+import javax.servlet.http.HttpServletResponse;
31
+
32
+import org.mortbay.jetty.HttpConnection;
33
+import org.mortbay.jetty.Request;
34
+import org.mortbay.jetty.handler.AbstractHandler;
35
+
36
+/**
37
+ * Handles requests for DMDirc resources (URLs starting with /dmdirc/).
38
+ *
39
+ * @author chris
40
+ */
41
+public class DMDircRequestHandler extends AbstractHandler {
42
+
43
+    /** The resource manager used for DMDirc resources. */
44
+    private ResourceManager rm;
45
+
46
+    /**
47
+     * Creates a new instance of DMDircRequestHandler.
48
+     */
49
+    public DMDircRequestHandler() {
50
+        super();
51
+    }
52
+
53
+    /**
54
+     * {@inheritDoc}
55
+     *
56
+     * @throws IOException If unable to write the response
57
+     */
58
+    @Override
59
+    public void handle(final String target, final HttpServletRequest request,
60
+            final HttpServletResponse response, final int dispatch)
61
+            throws IOException {
62
+
63
+        if (rm == null) {
64
+            rm = ResourceManager.getResourceManager();
65
+        }
66
+        
67
+        if (((request instanceof Request) ? (Request) request
68
+                : HttpConnection.getCurrentConnection().getRequest()).isHandled()) {
69
+            return;
70
+        }
71
+
72
+        if (target.startsWith("/dmdirc/")) {
73
+            final String path = "com/dmdirc/res/" + target.substring(8);
74
+
75
+            if (rm.resourceExists(path)) {
76
+                response.setStatus(HttpServletResponse.SC_OK);
77
+                response.getOutputStream().write(rm.getResourceBytes(path));
78
+                ((request instanceof Request) ? (Request) request
79
+                        : HttpConnection.getCurrentConnection().getRequest())
80
+                        .setHandled(true);
81
+            }
82
+        }
83
+    }
84
+}

+ 367
- 0
src/com/dmdirc/addons/ui_web/DynamicRequestHandler.java View File

@@ -0,0 +1,367 @@
1
+/*
2
+ * Copyright (c) 2006-2010 Chris Smith, Shane Mc Cormack, Gregory Holmes
3
+ *
4
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ * of this software and associated documentation files (the "Software"), to deal
6
+ * in the Software without restriction, including without limitation the rights
7
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ * copies of the Software, and to permit persons to whom the Software is
9
+ * furnished to do so, subject to the following conditions:
10
+ *
11
+ * The above copyright notice and this permission notice shall be included in
12
+ * all copies or substantial portions of the Software.
13
+ *
14
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ * SOFTWARE.
21
+ */
22
+
23
+package com.dmdirc.addons.ui_web;
24
+
25
+import com.dmdirc.Server;
26
+import com.dmdirc.config.Identity;
27
+import com.dmdirc.config.IdentityManager;
28
+import com.dmdirc.parser.interfaces.ChannelClientInfo;
29
+import com.dmdirc.addons.ui_web.uicomponents.WebChannelWindow;
30
+import com.dmdirc.addons.ui_web.uicomponents.WebInputHandler;
31
+import com.dmdirc.addons.ui_web.uicomponents.WebInputWindow;
32
+import com.dmdirc.addons.ui_web.uicomponents.WebWindow;
33
+
34
+import java.io.IOException;
35
+import java.net.URI;
36
+import java.net.URISyntaxException;
37
+import java.util.ArrayList;
38
+import java.util.HashMap;
39
+import java.util.List;
40
+import java.util.Map;
41
+import java.util.Timer;
42
+import java.util.TimerTask;
43
+
44
+import javax.servlet.http.HttpServletRequest;
45
+import javax.servlet.http.HttpServletResponse;
46
+
47
+import org.mortbay.jetty.HttpConnection;
48
+import org.mortbay.jetty.Request;
49
+import org.mortbay.jetty.handler.AbstractHandler;
50
+import org.mortbay.util.ajax.Continuation;
51
+import org.mortbay.util.ajax.ContinuationSupport;
52
+import org.mortbay.util.ajax.JSON;
53
+import org.mortbay.util.ajax.JSONObjectConvertor;
54
+
55
+/**
56
+ * Handles requests for dynamic resources (prefixed with /dynamic/).
57
+ * 
58
+ * @author chris
59
+ */
60
+public class DynamicRequestHandler extends AbstractHandler {
61
+    
62
+    /** Number of milliseconds before a client is timed out. */
63
+    private static long TIMEOUT = 1000 * 60 * 2; // Two minutes
64
+
65
+    /** The last time each client was seen. */
66
+    private static final Map<String, Client> clients = new HashMap<String, Client>();
67
+
68
+    /**
69
+     * Creates a new instance of DynamicRequestHandler. Registers object
70
+     * convertors with the JSON serialiser.
71
+     */
72
+    public DynamicRequestHandler() {
73
+        super();
74
+
75
+        JSON.registerConvertor(Event.class, new JSONObjectConvertor());
76
+        JSON.registerConvertor(WebWindow.class, new JSONObjectConvertor());
77
+        JSON.registerConvertor(Message.class, new JSONObjectConvertor());
78
+        JSON.registerConvertor(Client.class, new JSONObjectConvertor());
79
+        
80
+        new Timer().schedule(new TimerTask() {
81
+            @Override
82
+            public void run() {
83
+                synchronized (clients) {
84
+                    for (Map.Entry<String, Client> entry 
85
+                            : new HashMap<String, Client>(clients).entrySet()) {
86
+                        if (entry.getValue().getTime() > TIMEOUT) {
87
+                            clients.remove(entry.getKey());
88
+                        }
89
+                    }
90
+                }
91
+            }
92
+        }, ERROR, ERROR);
93
+    }
94
+
95
+    /**
96
+     * {@inheritDoc}
97
+     * 
98
+     * @throws IOException If unable to write response
99
+     */
100
+    @Override
101
+    public void handle(final String target, final HttpServletRequest request,
102
+            final HttpServletResponse response, final int dispatch)
103
+            throws IOException {
104
+
105
+        if (request.getParameter("clientID") != null) {
106
+            final String clientID = request.getParameter("clientID");
107
+            
108
+            if (!clients.containsKey(clientID)) {
109
+                clients.put(clientID, new Client(request.getRemoteHost()));
110
+            }
111
+
112
+            synchronized (clients) {
113
+                clients.get(clientID).touch();
114
+            }
115
+        }
116
+        
117
+        if (((request instanceof Request) ? (Request) request
118
+                : HttpConnection.getCurrentConnection().getRequest()).isHandled()) {
119
+            return;
120
+        }
121
+
122
+        if (target.equals("/dynamic/feed")) {
123
+            doFeed(request, response);
124
+            handled(request);
125
+        } else if (target.equals("/dynamic/getprofiles")) {
126
+            doProfiles(response);
127
+            handled(request);
128
+        } else if (target.equals("/dynamic/newserver")) {
129
+            doNewServer(request, response);
130
+            handled(request);
131
+        } else if (target.equals("/dynamic/windowrefresh")) {
132
+            doWindowRefresh(request, response);
133
+            handled(request);
134
+        } else if (target.equals("/dynamic/input")) {
135
+            doInput(request, response);
136
+            handled(request);
137
+        } else if (target.equals("/dynamic/nicklistrefresh")) {
138
+            doNicklist(request, response);
139
+            handled(request);
140
+        } else if (target.equals("/dynamic/tab")) {
141
+            doTab(request, response);
142
+            handled(request);
143
+        } else if (target.equals("/dynamic/keyup") || target.equals("/dynamic/keydown")) {
144
+            doKeyUpDown(target.equals("/dynamic/keyup"), request, response);
145
+            handled(request);
146
+        } else if (target.equals("/dynamic/key")) {
147
+            doKey(request, response);
148
+            handled(request);
149
+        } else if (target.equals("/dynamic/clients")) {
150
+            doClients(request, response);
151
+            handled(request);
152
+        } else if (target.equals("/dynamic/joinchannel")) {
153
+            doJoinChannel(request, response);
154
+            handled(request);
155
+        } else if (target.equals("/dynamic/openquery")) {
156
+            doOpenQuery(request, response);
157
+            handled(request);
158
+        }
159
+    }
160
+
161
+    /**
162
+     * Handles a request for the event feed.
163
+     * 
164
+     * @param request The servlet request that is being handled
165
+     * @param response The servlet response object to write to
166
+     * @throws IOException If unable to write the response
167
+     */
168
+    private void doFeed(final HttpServletRequest request,
169
+            final HttpServletResponse response) throws IOException {
170
+        response.setStatus(HttpServletResponse.SC_OK);
171
+        response.setContentType("application/json");
172
+
173
+        final Client client = clients.get(request.getParameter("clientID"));
174
+
175
+        synchronized (client.getMutex()) {
176
+            List<Event> myEvents = client.retrieveEvents();
177
+
178
+            if (myEvents.isEmpty()) {
179
+                Continuation continuation = ContinuationSupport.getContinuation(request,
180
+                        client.getMutex());
181
+                client.setContinuation(continuation);
182
+                continuation.suspend(30000l);
183
+
184
+                myEvents = client.retrieveEvents();
185
+            }
186
+
187
+            client.setContinuation(null);
188
+
189
+            final String json = JSON.toString(myEvents.toArray());
190
+            response.getWriter().write(json);
191
+        }
192
+    }
193
+
194
+    private void doInput(final HttpServletRequest request,
195
+            final HttpServletResponse response) throws IOException {
196
+        if (WebInterfaceUI.active instanceof WebInputWindow) {
197
+            final WebInputWindow wiw = (WebInputWindow) WebInterfaceUI.active;
198
+            wiw.getInputHandler(request.getParameter("clientID")).enterPressed(
199
+                    request.getParameter("input"));
200
+        }
201
+    }
202
+
203
+    private void doKey(final HttpServletRequest request,
204
+            final HttpServletResponse response) throws IOException {
205
+        if (WebInterfaceUI.active instanceof WebInputWindow) {
206
+            final WebInputWindow wiw = (WebInputWindow) WebInterfaceUI.active;
207
+
208
+            try {
209
+                ((WebInputHandler) wiw.getInputHandler(
210
+                        request.getParameter("clientID"),
211
+                        request.getParameter("input"),
212
+                        request.getParameter("selstart"),
213
+                        request.getParameter("selend"))).handleKeyPressed(
214
+                        request.getParameter("input"),
215
+                        Integer.parseInt(request.getParameter("key")),
216
+                        Boolean.parseBoolean(request.getParameter("shift")),
217
+                        Boolean.parseBoolean(request.getParameter("ctrl")));
218
+            } catch (NumberFormatException ex) {
219
+                // Do nothing
220
+            }
221
+        }
222
+    }
223
+
224
+    private void doTab(HttpServletRequest request, HttpServletResponse response)
225
+            throws IOException {
226
+        if (WebInterfaceUI.active instanceof WebInputWindow) {
227
+            final WebInputWindow wiw = (WebInputWindow) WebInterfaceUI.active;
228
+            ((WebInputHandler) wiw.getInputHandler(request.getParameter("clientID"),
229
+                    request.getParameter("input"), request.getParameter("selstart"),
230
+                    request.getParameter("selend"))).doTabCompletion();
231
+        }
232
+    }
233
+
234
+    private void doKeyUpDown(final boolean up, HttpServletRequest request,
235
+            HttpServletResponse response) throws IOException {
236
+        if (WebInterfaceUI.active instanceof WebInputWindow) {
237
+            final WebInputWindow wiw = (WebInputWindow) WebInterfaceUI.active;
238
+            final WebInputHandler wih = ((WebInputHandler) wiw.getInputHandler(
239
+                    request.getParameter("clientID"),
240
+                    request.getParameter("input"),
241
+                    request.getParameter("selstart"),
242
+                    request.getParameter("selend")));
243
+
244
+            if (up) {
245
+                wih.doBufferUp();
246
+            } else {
247
+                wih.doBufferDown();
248
+            }
249
+        }
250
+    }
251
+
252
+    private void doNewServer(final HttpServletRequest request,
253
+            final HttpServletResponse response) throws
254
+            IOException {
255
+        try {
256
+            new Server(new URI("irc://" + request.getParameter("password") + "@"
257
+                    + request.getParameter("server") + ":"
258
+                    + request.getParameter("port")),
259
+                    findProfile(request.getParameter("profile"))).connect();
260
+        } catch (URISyntaxException ex) {
261
+            // Ugh.
262
+        }
263
+    }
264
+
265
+    private void doNicklist(HttpServletRequest request,
266
+            HttpServletResponse response) throws IOException {
267
+        response.setStatus(HttpServletResponse.SC_OK);
268
+        response.setContentType("application/json");
269
+
270
+        final List<Event> nickEvents = new ArrayList<Event>();
271
+
272
+        nickEvents.add(new Event("clearnicklist", false));
273
+
274
+        for (ChannelClientInfo cci : ((WebChannelWindow) WebInterfaceUI.active)
275
+                .getChannel().getChannelInfo().getChannelClients()) {
276
+            nickEvents.add(new Event("addnicklist",
277
+                    cci.getClient().getNickname()));
278
+        }
279
+
280
+        response.getWriter().write(JSON.toString(nickEvents.toArray()));
281
+    }
282
+
283
+    private void doProfiles(final HttpServletResponse response) throws
284
+            IOException {
285
+        response.setStatus(HttpServletResponse.SC_OK);
286
+        response.setContentType("application/json");
287
+
288
+        final List<Event> profileEvents = new ArrayList<Event>();
289
+
290
+        profileEvents.add(new Event("clearprofiles", null));
291
+
292
+        for (Identity identity : IdentityManager.getProfiles()) {
293
+            profileEvents.add(new Event("addprofile", identity.getName()));
294
+        }
295
+
296
+        response.getWriter().write(JSON.toString(profileEvents.toArray()));
297
+    }
298
+
299
+    private void doWindowRefresh(HttpServletRequest request,
300
+            HttpServletResponse response) throws IOException {
301
+        response.setStatus(HttpServletResponse.SC_OK);
302
+        response.setContentType("application/json");
303
+
304
+        final List<Event> windowEvents = new ArrayList<Event>();
305
+
306
+        final WebWindow window = WebWindow.getWindow(request.getParameter(
307
+                "window"));
308
+
309
+        windowEvents.add(new Event("clearwindow", window.getId()));
310
+
311
+        for (String line : window.getMessages()) {
312
+            windowEvents.add(new Event("lineadded", new Message(line, window)));
313
+        }
314
+
315
+        response.getWriter().write(JSON.toString(windowEvents.toArray()));
316
+    }
317
+    
318
+    private void doClients(final HttpServletRequest request,
319
+            final HttpServletResponse response) throws IOException {
320
+        response.setStatus(HttpServletResponse.SC_OK);
321
+        response.setContentType("application/json");
322
+        response.getWriter().write(JSON.toString(clients.values().toArray()));
323
+    }
324
+    
325
+    private void doJoinChannel(final HttpServletRequest request,
326
+            final HttpServletResponse response) throws IOException {
327
+        final String windowID = request.getParameter("source");
328
+        final WebWindow window = WebWindow.getWindow(windowID);
329
+        window.getContainer().getServer().join(request.getParameter("channel"));
330
+    }
331
+    
332
+    private void doOpenQuery(final HttpServletRequest request,
333
+            final HttpServletResponse response) throws IOException {
334
+        final String windowID = request.getParameter("source");
335
+        final WebWindow window = WebWindow.getWindow(windowID);
336
+        window.getContainer().getServer().addQuery(request.getParameter("target"));
337
+    }
338
+
339
+    private Identity findProfile(final String parameter) {
340
+        for (Identity identity : IdentityManager.getProfiles()) {
341
+            if (identity.getName().equals(parameter)) {
342
+                return identity;
343
+            }
344
+        }
345
+
346
+        return null;
347
+    }
348
+
349
+    private void handled(final HttpServletRequest request) {
350
+        ((request instanceof Request) ? (Request) request
351
+                : HttpConnection.getCurrentConnection().getRequest()).setHandled(
352
+                true);
353
+    }
354
+
355
+    public static void addEvent(final Event event) {
356
+        synchronized (clients) {
357
+            for (Client client : clients.values()) {
358
+                client.addEvent(event);
359
+            }
360
+        }
361
+    }
362
+
363
+    public static void addEvent(String clientID, Event event) {
364
+        clients.get(clientID).addEvent(event);
365
+    }
366
+
367
+}

+ 50
- 0
src/com/dmdirc/addons/ui_web/Event.java View File

@@ -0,0 +1,50 @@
1
+/*
2
+ * Copyright (c) 2006-2010 Chris Smith, Shane Mc Cormack, Gregory Holmes
3
+ *
4
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ * of this software and associated documentation files (the "Software"), to deal
6
+ * in the Software without restriction, including without limitation the rights
7
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ * copies of the Software, and to permit persons to whom the Software is
9
+ * furnished to do so, subject to the following conditions:
10
+ *
11
+ * The above copyright notice and this permission notice shall be included in
12
+ * all copies or substantial portions of the Software.
13
+ *
14
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ * SOFTWARE.
21
+ */
22
+
23
+package com.dmdirc.addons.ui_web;
24
+
25
+/**
26
+ * Details an event that occured.
27
+ * 
28
+ * @author chris
29
+ */
30
+public class Event {
31
+   
32
+    /** The type of event that occurred. */
33
+    private String type;
34
+    /** The argument(s) for the event. */
35
+    private Object arg1;
36
+
37
+    public Event(String type, Object arg1) {
38
+        this.type = type;
39
+        this.arg1 = arg1;
40
+    }
41
+
42
+    public Object getArg1() {
43
+        return arg1;
44
+    }
45
+
46
+    public String getType() {
47
+        return type;
48
+    }
49
+
50
+}

+ 49
- 0
src/com/dmdirc/addons/ui_web/Message.java View File

@@ -0,0 +1,49 @@
1
+/*
2
+ * Copyright (c) 2006-2010 Chris Smith, Shane Mc Cormack, Gregory Holmes
3
+ *
4
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ * of this software and associated documentation files (the "Software"), to deal
6
+ * in the Software without restriction, including without limitation the rights
7
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ * copies of the Software, and to permit persons to whom the Software is
9
+ * furnished to do so, subject to the following conditions:
10
+ *
11
+ * The above copyright notice and this permission notice shall be included in
12
+ * all copies or substantial portions of the Software.
13
+ *
14
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ * SOFTWARE.
21
+ */
22
+
23
+package com.dmdirc.addons.ui_web;
24
+
25
+import com.dmdirc.addons.ui_web.uicomponents.WebWindow;
26
+
27
+/**
28
+ *
29
+ * @author chris
30
+ */
31
+public class Message {
32
+    
33
+    private String message;
34
+    private WebWindow window;
35
+
36
+    public Message(String message, WebWindow aThis) {
37
+        this.message = message;
38
+        this.window = aThis;
39
+    }
40
+    
41
+    public String getMessage() {
42
+        return message;
43
+    }
44
+    
45
+    public String getWindow() {
46
+        return window.getId();
47
+    }
48
+
49
+}

+ 61
- 0
src/com/dmdirc/addons/ui_web/RootRequestHandler.java View File

@@ -0,0 +1,61 @@
1
+/*
2
+ * Copyright (c) 2006-2010 Chris Smith, Shane Mc Cormack, Gregory Holmes
3
+ *
4
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ * of this software and associated documentation files (the "Software"), to deal
6
+ * in the Software without restriction, including without limitation the rights
7
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ * copies of the Software, and to permit persons to whom the Software is
9
+ * furnished to do so, subject to the following conditions:
10
+ *
11
+ * The above copyright notice and this permission notice shall be included in
12
+ * all copies or substantial portions of the Software.
13
+ *
14
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ * SOFTWARE.
21
+ */
22
+
23
+package com.dmdirc.addons.ui_web;
24
+
25
+import java.io.IOException;
26
+
27
+import javax.servlet.ServletException;
28
+import javax.servlet.http.HttpServletRequest;
29
+import javax.servlet.http.HttpServletResponse;
30
+
31
+import org.mortbay.jetty.HttpConnection;
32
+import org.mortbay.jetty.Request;
33
+import org.mortbay.jetty.handler.AbstractHandler;
34
+
35
+public class RootRequestHandler extends AbstractHandler {
36
+
37
+    public RootRequestHandler() {
38
+        super();
39
+    }
40
+
41
+    /** {@inheritDoc} */
42
+    @Override
43
+    public void handle(final String target, final HttpServletRequest request,
44
+            final HttpServletResponse response, final int dispatch) 
45
+            throws IOException, ServletException {
46
+        
47
+        if (((request instanceof Request) ? (Request) request
48
+                : HttpConnection.getCurrentConnection().getRequest()).isHandled()) {
49
+            return;
50
+        }
51
+        
52
+        if (target.equals("/")) {
53
+            response.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY);
54
+            response.setHeader("location", "/static/default.html");
55
+            
56
+            ((request instanceof Request) ? (Request) request
57
+                    : HttpConnection.getCurrentConnection().getRequest())
58
+                    .setHandled(true);            
59
+        }
60
+    }
61
+}

+ 87
- 0
src/com/dmdirc/addons/ui_web/StaticRequestHandler.java View File

@@ -0,0 +1,87 @@
1
+/*
2
+ * Copyright (c) 2006-2010 Chris Smith, Shane Mc Cormack, Gregory Holmes
3
+ *
4
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ * of this software and associated documentation files (the "Software"), to deal
6
+ * in the Software without restriction, including without limitation the rights
7
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ * copies of the Software, and to permit persons to whom the Software is
9
+ * furnished to do so, subject to the following conditions:
10
+ *
11
+ * The above copyright notice and this permission notice shall be included in
12
+ * all copies or substantial portions of the Software.
13
+ *
14
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ * SOFTWARE.
21
+ */
22
+
23
+package com.dmdirc.addons.ui_web;
24
+
25
+import com.dmdirc.plugins.PluginManager;
26
+import com.dmdirc.util.resourcemanager.ResourceManager;
27
+
28
+import java.io.IOException;
29
+
30
+import javax.servlet.ServletException;
31
+import javax.servlet.http.HttpServletRequest;
32
+import javax.servlet.http.HttpServletResponse;
33
+
34
+import org.mortbay.jetty.HttpConnection;
35
+import org.mortbay.jetty.Request;
36
+import org.mortbay.jetty.handler.AbstractHandler;
37
+
38
+public class StaticRequestHandler extends AbstractHandler {
39
+    
40
+    private ResourceManager rm;
41
+    
42
+    public StaticRequestHandler() {
43
+        super();
44
+    }
45
+
46
+    /** {@inheritDoc} */
47
+    @Override
48
+    public void handle(final String target, final HttpServletRequest request,
49
+            final HttpServletResponse response, final int dispatch) 
50
+            throws IOException, ServletException {
51
+        
52
+        if (rm == null) {
53
+            try {
54
+                rm = PluginManager.getPluginManager()
55
+                    .getPluginInfoByName("ui_web").getResourceManager();
56
+            } catch (IOException ex) {
57
+                // Die horribly
58
+            }            
59
+        }
60
+        
61
+        if (((request instanceof Request) ? (Request) request
62
+                : HttpConnection.getCurrentConnection().getRequest()).isHandled()) {
63
+            return;
64
+        }
65
+        
66
+        if (target.startsWith("/static/")) {
67
+            final String path = "com/dmdirc/addons/ui_web/res/" + target.substring(8);
68
+            
69
+            if (rm.resourceExists(path)) {
70
+                if (target.endsWith(".html")) {
71
+                    response.setContentType("text/html");
72
+                }
73
+                
74
+                response.setStatus(HttpServletResponse.SC_OK);
75
+                response.getOutputStream().write(rm.getResourceBytes(path));
76
+                ((request instanceof Request) ? (Request) request
77
+                        : HttpConnection.getCurrentConnection().getRequest())
78
+                        .setHandled(true);                
79
+            }
80
+            
81
+            /*response.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY);
82
+            response.setHeader("location", "/static/default");
83
+            
84
+            */
85
+        }
86
+    }
87
+}

+ 75
- 0
src/com/dmdirc/addons/ui_web/WebInterfacePlugin.java View File

@@ -0,0 +1,75 @@
1
+/*
2
+ * Copyright (c) 2006-2010 Chris Smith, Shane Mc Cormack, Gregory Holmes
3
+ *
4
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ * of this software and associated documentation files (the "Software"), to deal
6
+ * in the Software without restriction, including without limitation the rights
7
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ * copies of the Software, and to permit persons to whom the Software is
9
+ * furnished to do so, subject to the following conditions:
10
+ *
11
+ * The above copyright notice and this permission notice shall be included in
12
+ * all copies or substantial portions of the Software.
13
+ *
14
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ * SOFTWARE.
21
+ */
22
+
23
+package com.dmdirc.addons.ui_web;
24
+
25
+import com.dmdirc.Main;
26
+import com.dmdirc.plugins.Plugin;
27
+
28
+import org.mortbay.jetty.Handler;
29
+
30
+/**
31
+ * The main web interface plugin.
32
+ * 
33
+ * @author chris
34
+ */
35
+public class WebInterfacePlugin extends Plugin {
36
+    
37
+    /** The UI that we're using. */
38
+    private WebInterfaceUI ui;
39
+
40
+    /**
41
+     * Creates a new WebInterfacePlugin, and registers the web UI as the
42
+     * main client UI.
43
+     */
44
+    public WebInterfacePlugin() {
45
+    }
46
+
47
+    /** {@inheritDoc} */
48
+    @Override
49
+    public void onLoad() {   
50
+        if (ui == null) {
51
+             ui = new WebInterfaceUI(this);
52
+        }
53
+        
54
+        Main.setUI(ui);
55
+    }
56
+
57
+    /** {@inheritDoc} */
58
+    @Override
59
+    public void onUnload() {
60
+        // Do nothing
61
+    }
62
+    
63
+    /**
64
+     * Adds the specified handler to the WebInterface's web server.
65
+     * 
66
+     * @param newHandler The handler to be added
67
+     */
68
+    public void addWebHandler(final Handler newHandler) {
69
+        if (ui == null) {
70
+             ui = new WebInterfaceUI(this);
71
+        }
72
+        
73
+        ui.addWebHandler(newHandler);
74
+    }
75
+}

+ 285
- 0
src/com/dmdirc/addons/ui_web/WebInterfaceUI.java View File

@@ -0,0 +1,285 @@
1
+/*
2
+ * Copyright (c) 2006-2010 Chris Smith, Shane Mc Cormack, Gregory Holmes
3
+ *
4
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ * of this software and associated documentation files (the "Software"), to deal
6
+ * in the Software without restriction, including without limitation the rights
7
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ * copies of the Software, and to permit persons to whom the Software is
9
+ * furnished to do so, subject to the following conditions:
10
+ *
11
+ * The above copyright notice and this permission notice shall be included in
12
+ * all copies or substantial portions of the Software.
13
+ *
14
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ * SOFTWARE.
21
+ */
22
+
23
+package com.dmdirc.addons.ui_web;
24
+
25
+import com.dmdirc.Channel;
26
+import com.dmdirc.FrameContainer;
27
+import com.dmdirc.Query;
28
+import com.dmdirc.Server;
29
+import com.dmdirc.WritableFrameContainer;
30
+import com.dmdirc.commandparser.parsers.CommandParser;
31
+import com.dmdirc.config.prefs.PreferencesInterface;
32
+import com.dmdirc.ui.core.dialogs.sslcertificate.SSLCertificateDialogModel;
33
+import com.dmdirc.ui.interfaces.ChannelWindow;
34
+import com.dmdirc.ui.interfaces.InputWindow;
35
+import com.dmdirc.ui.interfaces.MainWindow;
36
+import com.dmdirc.ui.interfaces.QueryWindow;
37
+import com.dmdirc.ui.interfaces.ServerWindow;
38
+import com.dmdirc.ui.interfaces.StatusBar;
39
+import com.dmdirc.ui.interfaces.UIController;
40
+import com.dmdirc.ui.interfaces.UpdaterDialog;
41
+import com.dmdirc.ui.interfaces.Window;
42
+import com.dmdirc.ui.WindowManager;
43
+import com.dmdirc.updater.Update;
44
+
45
+import com.dmdirc.addons.ui_web.uicomponents.WebChannelWindow;
46
+import com.dmdirc.addons.ui_web.uicomponents.WebFrameManager;
47
+import com.dmdirc.addons.ui_web.uicomponents.WebInputWindow;
48
+import com.dmdirc.addons.ui_web.uicomponents.WebMainWindow;
49
+import com.dmdirc.addons.ui_web.uicomponents.WebQueryWindow;
50
+import com.dmdirc.addons.ui_web.uicomponents.WebServerWindow;
51
+import com.dmdirc.addons.ui_web.uicomponents.WebStatusBar;
52
+import com.dmdirc.addons.ui_web.uicomponents.WebWindow;
53
+
54
+import java.net.URI;
55
+import java.util.List;
56
+
57
+import org.mortbay.jetty.Handler;
58
+import org.mortbay.jetty.security.Constraint;
59
+import org.mortbay.jetty.security.ConstraintMapping;
60
+import org.mortbay.jetty.security.SecurityHandler;
61
+
62
+/**
63
+ * Creates and manages the web server and handles UI-wide events.
64
+ * 
65
+ * @author chris
66
+ */
67
+public class WebInterfaceUI implements UIController {
68
+    
69
+    /** The domain used for config settings. */
70
+    public static final String DOMAIN = "plugin-webui";
71
+   
72
+    /**
73
+     * The active window.
74
+     * @Deprecated Should be done client side
75
+     */
76
+    public static WebWindow active;
77
+    
78
+    /** The web server we're using. */
79
+    private final org.mortbay.jetty.Server webServer;
80
+
81
+    /**
82
+     * Creates a new WebInterfaceUI belonging to the specified plugin.
83
+     * 
84
+     * @param plugin The plugin which owns this Web UI
85
+     */
86
+    public WebInterfaceUI(final WebInterfacePlugin plugin) {
87
+        WindowManager.addFrameListener(new WebFrameManager());
88
+        
89
+        final SecurityHandler sh = new SecurityHandler();
90
+        final Constraint constraint = new Constraint();
91
+        final ConstraintMapping cm = new ConstraintMapping();
92
+        
93
+        constraint.setName("DMDirc Web UI");
94
+        constraint.setRoles(new String[]{"user"});
95
+        constraint.setAuthenticate(true);
96
+        
97
+        cm.setConstraint(constraint);
98
+        cm.setPathSpec("/*");
99
+        
100
+        sh.setUserRealm(new WebUserRealm());
101
+        sh.setConstraintMappings(new ConstraintMapping[]{cm});
102
+        
103
+        webServer = new org.mortbay.jetty.Server(5978);
104
+
105
+        webServer.setHandlers(new Handler[]{
106
+            sh,
107
+            new RootRequestHandler(),
108
+            new StaticRequestHandler(),
109
+            new DMDircRequestHandler(),
110
+            new DynamicRequestHandler(),
111
+        });
112
+        
113
+        try {
114
+            webServer.start();
115
+        } catch (Exception ex) {
116
+            // Break horribly!
117
+        }
118
+    }
119
+    
120
+    /**
121
+     * Adds the specified handler to the webserver.
122
+     * 
123
+     * @param newHandler The handler to add.
124
+     */
125
+    public void addWebHandler(final Handler newHandler) {
126
+        webServer.addHandler(newHandler);
127
+    }
128
+
129
+    /** {@inheritDoc} */
130
+    @Override
131
+    public MainWindow getMainWindow() {
132
+        return new WebMainWindow();
133
+    }
134
+
135
+    /** {@inheritDoc} */
136
+    @Override
137
+    public StatusBar getStatusBar() {
138
+        return new WebStatusBar();
139
+    }
140
+
141
+    /** {@inheritDoc} */
142
+    @Override
143
+    public ChannelWindow getChannel(final Channel channel) {
144
+        return new WebChannelWindow(channel);
145
+    }
146
+
147
+    /** {@inheritDoc} */
148
+    @Override
149
+    public ServerWindow getServer(final Server server) {
150
+        return new WebServerWindow(server);
151
+    }
152
+
153
+    /** {@inheritDoc} */
154
+    @Override
155
+    public QueryWindow getQuery(final Query query) {
156
+        return new WebQueryWindow(query);
157
+    }
158
+
159
+    /** {@inheritDoc} */
160
+    @Override
161
+    public Window getWindow(final FrameContainer owner) {
162
+        return new WebWindow(owner);
163
+    }
164
+
165
+    /** {@inheritDoc} */
166
+    @Override
167
+    public InputWindow getInputWindow(final WritableFrameContainer owner,
168
+            final CommandParser commandParser) {
169
+        return new WebInputWindow(owner, commandParser);
170
+    }
171
+
172
+    /** {@inheritDoc} */
173
+    @Override
174
+    public UpdaterDialog getUpdaterDialog(final List<Update> updates) {
175
+        //TODO FIXME
176
+        throw new UnsupportedOperationException("Not supported yet.");
177
+    }
178
+
179
+    /** {@inheritDoc} */
180
+    @Override
181
+    public void showFirstRunWizard() {
182
+        //TODO FIXME
183
+        throw new UnsupportedOperationException("Not supported yet.");
184
+    }
185
+
186
+    /** {@inheritDoc} */
187
+    @Override
188
+    public void showMigrationWizard() {
189
+        //TODO FIXME
190
+        throw new UnsupportedOperationException("Not supported yet.");
191
+    }
192
+
193
+    /** {@inheritDoc} */
194
+    @Override
195
+    public void showChannelSettingsDialog(final Channel channel) {
196
+        //TODO FIXME
197
+        throw new UnsupportedOperationException("Not supported yet.");
198
+    }
199
+
200
+    /** {@inheritDoc} */
201
+    @Override
202
+    public void showServerSettingsDialog(final Server server) {
203
+        //TODO FIXME
204
+        throw new UnsupportedOperationException("Not supported yet.");
205
+    }
206
+
207
+    /** {@inheritDoc} */
208
+    @Override
209
+    public void initUISettings() {
210
+        // Do nothing
211
+    }
212
+
213
+    /** {@inheritDoc} */
214
+    @Override
215
+    public Window getActiveWindow() {
216
+        return active;
217
+    }
218
+
219
+    /** {@inheritDoc} */
220
+    @Override
221
+    public Server getActiveServer() {
222
+        //TODO FIXME
223
+        throw new UnsupportedOperationException("Not supported yet.");
224
+    }
225
+
226
+    /** {@inheritDoc} */
227
+    @Override
228
+    public void showURLDialog(final URI url) {
229
+        //TODO FIXME
230
+        throw new UnsupportedOperationException("Not supported yet.");
231
+    }
232
+
233
+    /** {@inheritDoc} */
234
+    @Override
235
+    public void showFeedbackNag() {
236
+        //TODO FIXME
237
+        throw new UnsupportedOperationException("Not supported yet.");
238
+    }
239
+
240
+    /** {@inheritDoc} */
241
+    @Override
242
+    public void showMessageDialog(final String title, final String message) {
243
+        //TODO FIXME
244
+        throw new UnsupportedOperationException("Not supported yet.");
245
+    }
246
+
247
+    /** {@inheritDoc} */
248
+    @Override
249
+    public String getUserInput(final String prompt) {
250
+        //TODO FIXME
251
+        throw new UnsupportedOperationException("Not supported yet.");
252
+    }
253
+
254
+    /** {@inheritDoc} */
255
+    @Override
256
+    public void showSSLCertificateDialog(final SSLCertificateDialogModel model) {
257
+        //TODO FIXME
258
+        throw new UnsupportedOperationException("Not supported yet.");
259
+    }
260
+
261
+    @Override
262
+    public PreferencesInterface getPluginPrefsPanel() {
263
+        //TODO FIXME
264
+        throw new UnsupportedOperationException("Not supported yet.");
265
+    }
266
+
267
+    @Override
268
+    public PreferencesInterface getUpdatesPrefsPanel() {
269
+        //TODO FIXME
270
+        throw new UnsupportedOperationException("Not supported yet.");
271
+    }
272
+
273
+    @Override
274
+    public PreferencesInterface getUrlHandlersPrefsPanel() {
275
+        //TODO FIXME
276
+        throw new UnsupportedOperationException("Not supported yet.");
277
+    }
278
+
279
+    @Override
280
+    public PreferencesInterface getThemesPrefsPanel() {
281
+        //TODO FIXME
282
+        throw new UnsupportedOperationException("Not supported yet.");
283
+    }
284
+
285
+}

+ 77
- 0
src/com/dmdirc/addons/ui_web/WebPrincipal.java View File

@@ -0,0 +1,77 @@
1
+/*
2
+ * Copyright (c) 2006-2010 Chris Smith, Shane Mc Cormack, Gregory Holmes
3
+ *
4
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ * of this software and associated documentation files (the "Software"), to deal
6
+ * in the Software without restriction, including without limitation the rights
7
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ * copies of the Software, and to permit persons to whom the Software is
9
+ * furnished to do so, subject to the following conditions:
10
+ *
11
+ * The above copyright notice and this permission notice shall be included in
12
+ * all copies or substantial portions of the Software.
13
+ *
14
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ * SOFTWARE.
21
+ */
22
+
23
+package com.dmdirc.addons.ui_web;
24
+
25
+import java.security.Principal;
26
+
27
+/**
28
+ *
29
+ * @author chris
30
+ */
31
+public class WebPrincipal implements Principal {
32
+    
33
+    private final String username;
34
+
35
+    public WebPrincipal(final String username) {
36
+        this.username = username;
37
+    }
38
+
39
+    /** {@inheritDoc} */
40
+    @Override
41
+    public String getName() {
42
+        return username;
43
+    }
44
+
45
+    /** {@inheritDoc} */
46
+    @Override
47
+    public boolean equals(Object obj) {
48
+        if (obj == null) {
49
+            return false;
50
+        }
51
+        if (getClass() != obj.getClass()) {
52
+            return false;
53
+        }
54
+        final WebPrincipal other = (WebPrincipal) obj;
55
+        if (this.username != other.username &&
56
+            (this.username == null || !this.username.equals(other.username))) {
57
+            return false;
58
+        }
59
+        return true;
60
+    }
61
+
62
+    /** {@inheritDoc} */
63
+    @Override
64
+    public int hashCode() {
65
+        int hash = 7;
66
+        hash =
67
+        97 * hash + (this.username != null ? this.username.hashCode() : 0);
68
+        return hash;
69
+    }
70
+
71
+    /** {@inheritDoc} */
72
+    @Override
73
+    public String toString() {
74
+        return getName();
75
+    }
76
+
77
+}

+ 143
- 0
src/com/dmdirc/addons/ui_web/WebUserRealm.java View File

@@ -0,0 +1,143 @@
1
+/*
2
+ * Copyright (c) 2006-2010 Chris Smith, Shane Mc Cormack, Gregory Holmes
3
+ *
4
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ * of this software and associated documentation files (the "Software"), to deal
6
+ * in the Software without restriction, including without limitation the rights
7
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ * copies of the Software, and to permit persons to whom the Software is
9
+ * furnished to do so, subject to the following conditions:
10
+ *
11
+ * The above copyright notice and this permission notice shall be included in
12
+ * all copies or substantial portions of the Software.
13
+ *
14
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ * SOFTWARE.
21
+ */
22
+
23
+package com.dmdirc.addons.ui_web;
24
+
25
+import com.dmdirc.config.ConfigManager;
26
+import com.dmdirc.config.IdentityManager;
27
+
28
+import java.math.BigInteger;
29
+import java.security.MessageDigest;
30
+import java.security.NoSuchAlgorithmException;
31
+import java.security.Principal;
32
+import java.util.ArrayList;
33
+import java.util.HashMap;
34
+import java.util.List;
35
+import java.util.Map;
36
+import org.mortbay.jetty.Request;
37
+import org.mortbay.jetty.security.UserRealm;
38
+
39
+/**
40
+ * Describes the users allowed to access the web UI.
41
+ * 
42
+ * @author chris
43
+ */
44
+public class WebUserRealm implements UserRealm {
45
+    
46
+    private final Map<String, Principal> principals = new HashMap<String, Principal>();
47
+    
48
+    private final ConfigManager config = IdentityManager.getGlobalConfig();
49
+
50
+    /** {@inheritDoc} */
51
+    @Override
52
+    public String getName() {
53
+        if (config.hasOptionString(WebInterfaceUI.DOMAIN, "users")) {
54
+            return "DMDirc web UI";
55
+        } else {
56
+            return "DMDirc web UI first run -- " 
57
+                    + "enter the username and password you wish to use in the future";
58
+        }
59
+    }
60
+
61
+    /** {@inheritDoc} */
62
+    @Override
63
+    public Principal getPrincipal(final String username) {
64
+        return principals.get(username);
65
+    }
66
+
67
+    /** {@inheritDoc} */
68
+    @Override
69
+    public Principal authenticate(final String username, final Object credentials,
70
+            final Request request) {
71
+        if (!config.hasOptionString(WebInterfaceUI.DOMAIN, "users")) {
72
+            final List<String> users = new ArrayList<String>();
73
+            users.add(username + ":" + getHash(username, credentials));
74
+            IdentityManager.getConfigIdentity().setOption(WebInterfaceUI.DOMAIN,
75
+                    "users", users);
76
+        }
77
+        
78
+        for (String userinfo : config.getOptionList(WebInterfaceUI.DOMAIN, "users")) {
79
+            if (userinfo.startsWith(username + ":")) {
80
+                final String pass = userinfo.substring(username.length() + 1);
81
+
82
+                if (pass.equals(getHash(username, credentials))) {
83
+                    principals.put(username, new WebPrincipal(username));
84
+                    return getPrincipal(username);
85
+                }
86
+            }
87
+        }
88
+        
89
+        return null;
90
+    }
91
+
92
+    /** {@inheritDoc} */
93
+    @Override
94
+    public boolean reauthenticate(final Principal user) {
95
+        return principals.containsValue(user);
96
+    }
97
+
98
+    /** {@inheritDoc} */
99
+    @Override
100
+    public boolean isUserInRole(final Principal user, final String role) {
101
+        return true;
102
+    }
103
+
104
+    /** {@inheritDoc} */
105
+    @Override
106
+    public void disassociate(final Principal user) {
107
+        // Do nothing
108
+    }
109
+
110
+    /** {@inheritDoc} */
111
+    @Override
112
+    public Principal pushRole(final Principal user, final String role) {
113
+        // Do nothing
114
+        return user;
115
+    }
116
+
117
+    /** {@inheritDoc} */
118
+    @Override
119
+    public Principal popRole(final Principal user) {
120
+        // Do nothing
121
+        return user;
122
+    }
123
+
124
+    /** {@inheritDoc} */
125
+    @Override
126
+    public void logout(final Principal user) {
127
+        principals.remove(user.getName());
128
+    }
129
+
130
+    private String getHash(String username, Object credentials) {
131
+        final String target = username + "--" + (String) credentials;
132
+        
133
+        try {
134
+            final MessageDigest md = MessageDigest.getInstance("SHA-512");
135
+            
136
+            return new BigInteger(md.digest(target.getBytes())).toString(16);
137
+        } catch (NoSuchAlgorithmException ex) {
138
+            // Don't hash
139
+            return target;
140
+        }
141
+    }
142
+
143
+}

+ 28
- 0
src/com/dmdirc/addons/ui_web/plugin.config View File

@@ -0,0 +1,28 @@
1
+# This is a DMDirc configuration file.
2
+
3
+# This section indicates which sections below take key/value
4
+# pairs, rather than a simple list. It should be placed above
5
+# any sections that take key/values.
6
+keysections:
7
+  metadata
8
+  version
9
+  defaults
10
+
11
+metadata:
12
+  author=Chris <chris@dmdirc.com>
13
+  mainclass=com.dmdirc.addons.ui_web.WebInterfacePlugin
14
+  description=A web-based interface to DMDirc
15
+  name=ui_web
16
+  nicename=Web Interface
17
+  unloadable=no
18
+
19
+version:
20
+  friendly=0.2
21
+
22
+provides:
23
+  web ui
24
+
25
+permanent:
26
+  *
27
+
28
+defaults:

+ 39
- 0
src/com/dmdirc/addons/ui_web/res/default.html View File

@@ -0,0 +1,39 @@
1
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
2
+<html>
3
+    <head>
4
+        <title>DMDirc web interface</title>
5
+        <script src="javascript/prototype.js" type="text/javascript"></script>
6
+        <script src="javascript/scriptaculous.js" type="text/javascript"></script>
7
+        <script src="javascript/dmdirc.js" type="text/javascript"></script>
8
+        <link rel="stylesheet" href="style.css" type="text/css"/>
9
+    </head>
10
+    <body onload="dmdirc_start();">
11
+        <div id="titlebar">
12
+            <img src="images/dmdirc.png" id="throbber" alt="DMDirc logo">
13
+            <span id="title">DMDirc web interface</span>
14
+        </div>
15
+        
16
+        <div id="toolbar"><a class="button" onclick="nsd_show();">New server</a><a class="button" onclick="wus_show();">WebUI status</a></div>
17
+        
18
+        <ul id="treeview">
19
+        </ul>
20
+        
21
+        <div id="inputarea">
22
+            <input type="text" id="input" onkeydown="return input_keydown(event);"
23
+                   autocomplete="off">
24
+        </div>
25
+        
26
+        <div id="content">
27
+            <p class="nowindow">
28
+                No windows open.
29
+            </p>
30
+        </div>
31
+        
32
+        <ul id="nicklist">
33
+        </ul>
34
+        
35
+        <div id="statusbar">
36
+            <div id="statusbar_main">Ready</div>
37
+        </div>
38
+    </body>
39
+</html>

BIN
src/com/dmdirc/addons/ui_web/res/images/background.png View File


BIN
src/com/dmdirc/addons/ui_web/res/images/dmdirc.gif View File


BIN
src/com/dmdirc/addons/ui_web/res/images/dmdirc.png View File


+ 136
- 0
src/com/dmdirc/addons/ui_web/res/javascript/builder.js View File

@@ -0,0 +1,136 @@
1
+// script.aculo.us builder.js v1.8.1, Thu Jan 03 22:07:12 -0500 2008
2
+
3
+// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
4
+//
5
+// script.aculo.us is freely distributable under the terms of an MIT-style license.
6
+// For details, see the script.aculo.us web site: http://script.aculo.us/
7
+
8
+var Builder = {
9
+  NODEMAP: {
10
+    AREA: 'map',
11
+    CAPTION: 'table',
12
+    COL: 'table',
13
+    COLGROUP: 'table',
14
+    LEGEND: 'fieldset',
15
+    OPTGROUP: 'select',
16
+    OPTION: 'select',
17
+    PARAM: 'object',
18
+    TBODY: 'table',
19
+    TD: 'table',
20
+    TFOOT: 'table',
21
+    TH: 'table',
22
+    THEAD: 'table',
23
+    TR: 'table'
24
+  },
25
+  // note: For Firefox < 1.5, OPTION and OPTGROUP tags are currently broken,
26
+  //       due to a Firefox bug
27
+  node: function(elementName) {
28
+    elementName = elementName.toUpperCase();
29
+    
30
+    // try innerHTML approach
31
+    var parentTag = this.NODEMAP[elementName] || 'div';
32
+    var parentElement = document.createElement(parentTag);
33
+    try { // prevent IE "feature": http://dev.rubyonrails.org/ticket/2707
34
+      parentElement.innerHTML = "<" + elementName + "></" + elementName + ">";
35
+    } catch(e) {}
36
+    var element = parentElement.firstChild || null;
37
+      
38
+    // see if browser added wrapping tags
39
+    if(element && (element.tagName.toUpperCase() != elementName))
40
+      element = element.getElementsByTagName(elementName)[0];
41
+    
42
+    // fallback to createElement approach
43
+    if(!element) element = document.createElement(elementName);
44
+    
45
+    // abort if nothing could be created
46
+    if(!element) return;
47
+
48
+    // attributes (or text)
49
+    if(arguments[1])
50
+      if(this._isStringOrNumber(arguments[1]) ||
51
+        (arguments[1] instanceof Array) ||
52
+        arguments[1].tagName) {
53
+          this._children(element, arguments[1]);
54
+        } else {
55
+          var attrs = this._attributes(arguments[1]);
56
+          if(attrs.length) {
57
+            try { // prevent IE "feature": http://dev.rubyonrails.org/ticket/2707
58
+              parentElement.innerHTML = "<" +elementName + " " +
59
+                attrs + "></" + elementName + ">";
60
+            } catch(e) {}
61
+            element = parentElement.firstChild || null;
62
+            // workaround firefox 1.0.X bug
63
+            if(!element) {
64
+              element = document.createElement(elementName);
65
+              for(attr in arguments[1]) 
66
+                element[attr == 'class' ? 'className' : attr] = arguments[1][attr];
67
+            }
68
+            if(element.tagName.toUpperCase() != elementName)
69
+              element = parentElement.getElementsByTagName(elementName)[0];
70
+          }
71
+        } 
72
+
73
+    // text, or array of children
74
+    if(arguments[2])
75
+      this._children(element, arguments[2]);
76
+
77
+     return element;
78
+  },
79
+  _text: function(text) {
80
+     return document.createTextNode(text);
81
+  },
82
+
83
+  ATTR_MAP: {
84
+    'className': 'class',
85
+    'htmlFor': 'for'
86
+  },
87
+
88
+  _attributes: function(attributes) {
89
+    var attrs = [];
90
+    for(attribute in attributes)
91
+      attrs.push((attribute in this.ATTR_MAP ? this.ATTR_MAP[attribute] : attribute) +
92
+          '="' + attributes[attribute].toString().escapeHTML().gsub(/"/,'&quot;') + '"');
93
+    return attrs.join(" ");
94
+  },
95
+  _children: function(element, children) {
96
+    if(children.tagName) {
97
+      element.appendChild(children);
98
+      return;
99
+    }
100
+    if(typeof children=='object') { // array can hold nodes and text
101
+      children.flatten().each( function(e) {
102
+        if(typeof e=='object')
103
+          element.appendChild(e)
104
+        else
105
+          if(Builder._isStringOrNumber(e))
106
+            element.appendChild(Builder._text(e));
107
+      });
108
+    } else
109
+      if(Builder._isStringOrNumber(children))
110
+        element.appendChild(Builder._text(children));
111
+  },
112
+  _isStringOrNumber: function(param) {
113
+    return(typeof param=='string' || typeof param=='number');
114
+  },
115
+  build: function(html) {
116
+    var element = this.node('div');
117
+    $(element).update(html.strip());
118
+    return element.down();
119
+  },
120
+  dump: function(scope) { 
121
+    if(typeof scope != 'object' && typeof scope != 'function') scope = window; //global scope 
122
+  
123
+    var tags = ("A ABBR ACRONYM ADDRESS APPLET AREA B BASE BASEFONT BDO BIG BLOCKQUOTE BODY " +
124
+      "BR BUTTON CAPTION CENTER CITE CODE COL COLGROUP DD DEL DFN DIR DIV DL DT EM FIELDSET " +
125
+      "FONT FORM FRAME FRAMESET H1 H2 H3 H4 H5 H6 HEAD HR HTML I IFRAME IMG INPUT INS ISINDEX "+
126
+      "KBD LABEL LEGEND LI LINK MAP MENU META NOFRAMES NOSCRIPT OBJECT OL OPTGROUP OPTION P "+
127
+      "PARAM PRE Q S SAMP SCRIPT SELECT SMALL SPAN STRIKE STRONG STYLE SUB SUP TABLE TBODY TD "+
128
+      "TEXTAREA TFOOT TH THEAD TITLE TR TT U UL VAR").split(/\s+/);
129
+  
130
+    tags.each( function(tag){ 
131
+      scope[tag] = function() { 
132
+        return Builder.node.apply(Builder, [tag].concat($A(arguments)));  
133
+      } 
134
+    });
135
+  }
136
+}

+ 965
- 0
src/com/dmdirc/addons/ui_web/res/javascript/controls.js View File

@@ -0,0 +1,965 @@
1
+// script.aculo.us controls.js v1.8.1, Thu Jan 03 22:07:12 -0500 2008
2
+
3
+// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
4
+//           (c) 2005-2007 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
5
+//           (c) 2005-2007 Jon Tirsen (http://www.tirsen.com)
6
+// Contributors:
7
+//  Richard Livsey
8
+//  Rahul Bhargava
9
+//  Rob Wills
10
+// 
11
+// script.aculo.us is freely distributable under the terms of an MIT-style license.
12
+// For details, see the script.aculo.us web site: http://script.aculo.us/
13
+
14
+// Autocompleter.Base handles all the autocompletion functionality 
15
+// that's independent of the data source for autocompletion. This
16
+// includes drawing the autocompletion menu, observing keyboard
17
+// and mouse events, and similar.
18
+//
19
+// Specific autocompleters need to provide, at the very least, 
20
+// a getUpdatedChoices function that will be invoked every time
21
+// the text inside the monitored textbox changes. This method 
22
+// should get the text for which to provide autocompletion by
23
+// invoking this.getToken(), NOT by directly accessing
24
+// this.element.value. This is to allow incremental tokenized
25
+// autocompletion. Specific auto-completion logic (AJAX, etc)
26
+// belongs in getUpdatedChoices.
27
+//
28
+// Tokenized incremental autocompletion is enabled automatically
29
+// when an autocompleter is instantiated with the 'tokens' option
30
+// in the options parameter, e.g.:
31
+// new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' });
32
+// will incrementally autocomplete with a comma as the token.
33
+// Additionally, ',' in the above example can be replaced with
34
+// a token array, e.g. { tokens: [',', '\n'] } which
35
+// enables autocompletion on multiple tokens. This is most 
36
+// useful when one of the tokens is \n (a newline), as it 
37
+// allows smart autocompletion after linebreaks.
38
+
39
+if(typeof Effect == 'undefined')
40
+  throw("controls.js requires including script.aculo.us' effects.js library");
41
+
42
+var Autocompleter = { }
43
+Autocompleter.Base = Class.create({
44
+  baseInitialize: function(element, update, options) {
45
+    element          = $(element)
46
+    this.element     = element; 
47
+    this.update      = $(update);  
48
+    this.hasFocus    = false; 
49
+    this.changed     = false; 
50
+    this.active      = false; 
51
+    this.index       = 0;     
52
+    this.entryCount  = 0;
53
+    this.oldElementValue = this.element.value;
54
+
55
+    if(this.setOptions)
56
+      this.setOptions(options);
57
+    else
58
+      this.options = options || { };
59
+
60
+    this.options.paramName    = this.options.paramName || this.element.name;
61
+    this.options.tokens       = this.options.tokens || [];
62
+    this.options.frequency    = this.options.frequency || 0.4;
63
+    this.options.minChars     = this.options.minChars || 1;
64
+    this.options.onShow       = this.options.onShow || 
65
+      function(element, update){ 
66
+        if(!update.style.position || update.style.position=='absolute') {
67
+          update.style.position = 'absolute';
68
+          Position.clone(element, update, {
69
+            setHeight: false, 
70
+            offsetTop: element.offsetHeight
71
+          });
72
+        }
73
+        Effect.Appear(update,{duration:0.15});
74
+      };
75
+    this.options.onHide = this.options.onHide || 
76
+      function(element, update){ new Effect.Fade(update,{duration:0.15}) };
77
+
78
+    if(typeof(this.options.tokens) == 'string') 
79
+      this.options.tokens = new Array(this.options.tokens);
80
+    // Force carriage returns as token delimiters anyway
81
+    if (!this.options.tokens.include('\n'))
82
+      this.options.tokens.push('\n');
83
+
84
+    this.observer = null;
85
+    
86
+    this.element.setAttribute('autocomplete','off');
87
+
88
+    Element.hide(this.update);
89
+
90
+    Event.observe(this.element, 'blur', this.onBlur.bindAsEventListener(this));
91
+    Event.observe(this.element, 'keydown', this.onKeyPress.bindAsEventListener(this));
92
+  },
93
+
94
+  show: function() {
95
+    if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update);
96
+    if(!this.iefix && 
97
+      (Prototype.Browser.IE) &&
98
+      (Element.getStyle(this.update, 'position')=='absolute')) {
99
+      new Insertion.After(this.update, 
100
+       '<iframe id="' + this.update.id + '_iefix" '+
101
+       'style="display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' +
102
+       'src="javascript:false;" frameborder="0" scrolling="no"></iframe>');
103
+      this.iefix = $(this.update.id+'_iefix');
104
+    }
105
+    if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50);
106
+  },
107
+  
108
+  fixIEOverlapping: function() {
109
+    Position.clone(this.update, this.iefix, {setTop:(!this.update.style.height)});
110
+    this.iefix.style.zIndex = 1;
111
+    this.update.style.zIndex = 2;
112
+    Element.show(this.iefix);
113
+  },
114
+
115
+  hide: function() {
116
+    this.stopIndicator();
117
+    if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update);
118
+    if(this.iefix) Element.hide(this.iefix);
119
+  },
120
+
121
+  startIndicator: function() {
122
+    if(this.options.indicator) Element.show(this.options.indicator);
123
+  },
124
+
125
+  stopIndicator: function() {
126
+    if(this.options.indicator) Element.hide(this.options.indicator);
127
+  },
128
+
129
+  onKeyPress: function(event) {
130
+    if(this.active)
131
+      switch(event.keyCode) {
132
+       case Event.KEY_TAB:
133
+       case Event.KEY_RETURN:
134
+         this.selectEntry();
135
+         Event.stop(event);
136
+       case Event.KEY_ESC:
137
+         this.hide();
138
+         this.active = false;
139
+         Event.stop(event);
140
+         return;
141
+       case Event.KEY_LEFT:
142
+       case Event.KEY_RIGHT:
143
+         return;
144
+       case Event.KEY_UP:
145
+         this.markPrevious();
146
+         this.render();
147
+         Event.stop(event);
148
+         return;
149
+       case Event.KEY_DOWN:
150
+         this.markNext();
151
+         this.render();
152
+         Event.stop(event);
153
+         return;
154
+      }
155
+     else 
156
+       if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN || 
157
+         (Prototype.Browser.WebKit > 0 && event.keyCode == 0)) return;
158
+
159
+    this.changed = true;
160
+    this.hasFocus = true;
161
+
162
+    if(this.observer) clearTimeout(this.observer);
163
+      this.observer = 
164
+        setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000);
165
+  },
166
+
167
+  activate: function() {
168
+    this.changed = false;
169
+    this.hasFocus = true;
170
+    this.getUpdatedChoices();
171
+  },
172
+
173
+  onHover: function(event) {
174
+    var element = Event.findElement(event, 'LI');
175
+    if(this.index != element.autocompleteIndex) 
176
+    {
177
+        this.index = element.autocompleteIndex;
178
+        this.render();
179
+    }
180
+    Event.stop(event);
181
+  },
182
+  
183
+  onClick: function(event) {
184
+    var element = Event.findElement(event, 'LI');
185
+    this.index = element.autocompleteIndex;
186
+    this.selectEntry();
187
+    this.hide();
188
+  },
189
+  
190
+  onBlur: function(event) {
191
+    // needed to make click events working
192
+    setTimeout(this.hide.bind(this), 250);
193
+    this.hasFocus = false;
194
+    this.active = false;     
195
+  }, 
196
+  
197
+  render: function() {
198
+    if(this.entryCount > 0) {
199
+      for (var i = 0; i < this.entryCount; i++)
200
+        this.index==i ? 
201
+          Element.addClassName(this.getEntry(i),"selected") : 
202
+          Element.removeClassName(this.getEntry(i),"selected");
203
+      if(this.hasFocus) { 
204
+        this.show();
205
+        this.active = true;
206
+      }
207
+    } else {
208
+      this.active = false;
209
+      this.hide();
210
+    }
211
+  },
212
+  
213
+  markPrevious: function() {
214
+    if(this.index > 0) this.index--
215
+      else this.index = this.entryCount-1;
216
+    this.getEntry(this.index).scrollIntoView(true);
217
+  },
218
+  
219
+  markNext: function() {
220
+    if(this.index < this.entryCount-1) this.index++
221
+      else this.index = 0;
222
+    this.getEntry(this.index).scrollIntoView(false);
223
+  },
224
+  
225
+  getEntry: function(index) {
226
+    return this.update.firstChild.childNodes[index];
227
+  },
228
+  
229
+  getCurrentEntry: function() {
230
+    return this.getEntry(this.index);
231
+  },
232
+  
233
+  selectEntry: function() {
234
+    this.active = false;
235
+    this.updateElement(this.getCurrentEntry());
236
+  },
237
+
238
+  updateElement: function(selectedElement) {
239
+    if (this.options.updateElement) {
240
+      this.options.updateElement(selectedElement);
241
+      return;
242
+    }
243
+    var value = '';
244
+    if (this.options.select) {
245
+      var nodes = $(selectedElement).select('.' + this.options.select) || [];
246
+      if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select);
247
+    } else
248
+      value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal');
249
+    
250
+    var bounds = this.getTokenBounds();
251
+    if (bounds[0] != -1) {
252
+      var newValue = this.element.value.substr(0, bounds[0]);
253
+      var whitespace = this.element.value.substr(bounds[0]).match(/^\s+/);
254
+      if (whitespace)
255
+        newValue += whitespace[0];
256
+      this.element.value = newValue + value + this.element.value.substr(bounds[1]);
257
+    } else {
258
+      this.element.value = value;
259
+    }
260
+    this.oldElementValue = this.element.value;
261
+    this.element.focus();
262
+    
263
+    if (this.options.afterUpdateElement)
264
+      this.options.afterUpdateElement(this.element, selectedElement);
265
+  },
266
+
267
+  updateChoices: function(choices) {
268
+    if(!this.changed && this.hasFocus) {
269
+      this.update.innerHTML = choices;
270
+      Element.cleanWhitespace(this.update);
271
+      Element.cleanWhitespace(this.update.down());
272
+
273
+      if(this.update.firstChild && this.update.down().childNodes) {
274
+        this.entryCount = 
275
+          this.update.down().childNodes.length;
276
+        for (var i = 0; i < this.entryCount; i++) {
277
+          var entry = this.getEntry(i);
278
+          entry.autocompleteIndex = i;
279
+          this.addObservers(entry);
280
+        }
281
+      } else { 
282
+        this.entryCount = 0;
283
+      }
284
+
285
+      this.stopIndicator();
286
+      this.index = 0;
287
+      
288
+      if(this.entryCount==1 && this.options.autoSelect) {
289
+        this.selectEntry();
290
+        this.hide();
291
+      } else {
292
+        this.render();
293
+      }
294
+    }
295
+  },
296
+
297
+  addObservers: function(element) {
298
+    Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this));
299
+    Event.observe(element, "click", this.onClick.bindAsEventListener(this));
300
+  },
301
+
302
+  onObserverEvent: function() {
303
+    this.changed = false;   
304
+    this.tokenBounds = null;
305
+    if(this.getToken().length>=this.options.minChars) {
306
+      this.getUpdatedChoices();
307
+    } else {
308
+      this.active = false;
309
+      this.hide();
310
+    }
311
+    this.oldElementValue = this.element.value;
312
+  },
313
+
314
+  getToken: function() {
315
+    var bounds = this.getTokenBounds();
316
+    return this.element.value.substring(bounds[0], bounds[1]).strip();
317
+  },
318
+
319
+  getTokenBounds: function() {
320
+    if (null != this.tokenBounds) return this.tokenBounds;
321
+    var value = this.element.value;
322
+    if (value.strip().empty()) return [-1, 0];
323
+    var diff = arguments.callee.getFirstDifferencePos(value, this.oldElementValue);
324
+    var offset = (diff == this.oldElementValue.length ? 1 : 0);
325
+    var prevTokenPos = -1, nextTokenPos = value.length;
326
+    var tp;
327
+    for (var index = 0, l = this.options.tokens.length; index < l; ++index) {
328
+      tp = value.lastIndexOf(this.options.tokens[index], diff + offset - 1);
329
+      if (tp > prevTokenPos) prevTokenPos = tp;
330
+      tp = value.indexOf(this.options.tokens[index], diff + offset);
331
+      if (-1 != tp && tp < nextTokenPos) nextTokenPos = tp;
332
+    }
333
+    return (this.tokenBounds = [prevTokenPos + 1, nextTokenPos]);
334
+  }
335
+});
336
+
337
+Autocompleter.Base.prototype.getTokenBounds.getFirstDifferencePos = function(newS, oldS) {
338
+  var boundary = Math.min(newS.length, oldS.length);
339
+  for (var index = 0; index < boundary; ++index)
340
+    if (newS[index] != oldS[index])
341
+      return index;
342
+  return boundary;
343
+};
344
+
345
+Ajax.Autocompleter = Class.create(Autocompleter.Base, {
346
+  initialize: function(element, update, url, options) {
347
+    this.baseInitialize(element, update, options);
348
+    this.options.asynchronous  = true;
349
+    this.options.onComplete    = this.onComplete.bind(this);
350
+    this.options.defaultParams = this.options.parameters || null;
351
+    this.url                   = url;
352
+  },
353
+
354
+  getUpdatedChoices: function() {
355
+    this.startIndicator();
356
+    
357
+    var entry = encodeURIComponent(this.options.paramName) + '=' + 
358
+      encodeURIComponent(this.getToken());
359
+
360
+    this.options.parameters = this.options.callback ?
361
+      this.options.callback(this.element, entry) : entry;
362
+
363
+    if(this.options.defaultParams) 
364
+      this.options.parameters += '&' + this.options.defaultParams;
365
+    
366
+    new Ajax.Request(this.url, this.options);
367
+  },
368
+
369
+  onComplete: function(request) {
370
+    this.updateChoices(request.responseText);
371
+  }
372
+});
373
+
374
+// The local array autocompleter. Used when you'd prefer to
375
+// inject an array of autocompletion options into the page, rather
376
+// than sending out Ajax queries, which can be quite slow sometimes.
377
+//
378
+// The constructor takes four parameters. The first two are, as usual,
379
+// the id of the monitored textbox, and id of the autocompletion menu.
380
+// The third is the array you want to autocomplete from, and the fourth
381
+// is the options block.
382
+//
383
+// Extra local autocompletion options:
384
+// - choices - How many autocompletion choices to offer
385
+//
386
+// - partialSearch - If false, the autocompleter will match entered
387
+//                    text only at the beginning of strings in the 
388
+//                    autocomplete array. Defaults to true, which will
389
+//                    match text at the beginning of any *word* in the
390
+//                    strings in the autocomplete array. If you want to
391
+//                    search anywhere in the string, additionally set
392
+//                    the option fullSearch to true (default: off).
393
+//
394
+// - fullSsearch - Search anywhere in autocomplete array strings.
395
+//
396
+// - partialChars - How many characters to enter before triggering
397
+//                   a partial match (unlike minChars, which defines
398
+//                   how many characters are required to do any match
399
+//                   at all). Defaults to 2.
400
+//
401
+// - ignoreCase - Whether to ignore case when autocompleting.
402
+//                 Defaults to true.
403
+//
404
+// It's possible to pass in a custom function as the 'selector' 
405
+// option, if you prefer to write your own autocompletion logic.
406
+// In that case, the other options above will not apply unless
407
+// you support them.
408
+
409
+Autocompleter.Local = Class.create(Autocompleter.Base, {
410
+  initialize: function(element, update, array, options) {
411
+    this.baseInitialize(element, update, options);
412
+    this.options.array = array;
413
+  },
414
+
415
+  getUpdatedChoices: function() {
416
+    this.updateChoices(this.options.selector(this));
417
+  },
418
+
419
+  setOptions: function(options) {
420
+    this.options = Object.extend({
421
+      choices: 10,
422
+      partialSearch: true,
423
+      partialChars: 2,
424
+      ignoreCase: true,
425
+      fullSearch: false,
426
+      selector: function(instance) {
427
+        var ret       = []; // Beginning matches
428
+        var partial   = []; // Inside matches
429
+        var entry     = instance.getToken();
430
+        var count     = 0;
431
+
432
+        for (var i = 0; i < instance.options.array.length &&  
433
+          ret.length < instance.options.choices ; i++) { 
434
+
435
+          var elem = instance.options.array[i];
436
+          var foundPos = instance.options.ignoreCase ? 
437
+            elem.toLowerCase().indexOf(entry.toLowerCase()) : 
438
+            elem.indexOf(entry);
439
+
440
+          while (foundPos != -1) {
441
+            if (foundPos == 0 && elem.length != entry.length) { 
442
+              ret.push("<li><strong>" + elem.substr(0, entry.length) + "</strong>" + 
443
+                elem.substr(entry.length) + "</li>");
444
+              break;
445
+            } else if (entry.length >= instance.options.partialChars && 
446
+              instance.options.partialSearch && foundPos != -1) {
447
+              if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) {
448
+                partial.push("<li>" + elem.substr(0, foundPos) + "<strong>" +
449
+                  elem.substr(foundPos, entry.length) + "</strong>" + elem.substr(
450
+                  foundPos + entry.length) + "</li>");
451
+                break;
452
+              }
453
+            }
454
+
455
+            foundPos = instance.options.ignoreCase ? 
456
+              elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) : 
457
+              elem.indexOf(entry, foundPos + 1);
458
+
459
+          }
460
+        }
461
+        if (partial.length)
462
+          ret = ret.concat(partial.slice(0, instance.options.choices - ret.length))
463
+        return "<ul>" + ret.join('') + "</ul>";
464
+      }
465
+    }, options || { });
466
+  }
467
+});
468
+
469
+// AJAX in-place editor and collection editor
470
+// Full rewrite by Christophe Porteneuve <tdd@tddsworld.com> (April 2007).
471
+
472
+// Use this if you notice weird scrolling problems on some browsers,
473
+// the DOM might be a bit confused when this gets called so do this
474
+// waits 1 ms (with setTimeout) until it does the activation
475
+Field.scrollFreeActivate = function(field) {
476
+  setTimeout(function() {
477
+    Field.activate(field);
478
+  }, 1);
479
+}
480
+
481
+Ajax.InPlaceEditor = Class.create({
482
+  initialize: function(element, url, options) {
483
+    this.url = url;
484
+    this.element = element = $(element);
485
+    this.prepareOptions();
486
+    this._controls = { };
487
+    arguments.callee.dealWithDeprecatedOptions(options); // DEPRECATION LAYER!!!
488
+    Object.extend(this.options, options || { });
489
+    if (!this.options.formId && this.element.id) {
490
+      this.options.formId = this.element.id + '-inplaceeditor';
491
+      if ($(this.options.formId))
492
+        this.options.formId = '';
493
+    }
494
+    if (this.options.externalControl)
495
+      this.options.externalControl = $(this.options.externalControl);
496
+    if (!this.options.externalControl)
497
+      this.options.externalControlOnly = false;
498
+    this._originalBackground = this.element.getStyle('background-color') || 'transparent';
499
+    this.element.title = this.options.clickToEditText;
500
+    this._boundCancelHandler = this.handleFormCancellation.bind(this);
501
+    this._boundComplete = (this.options.onComplete || Prototype.emptyFunction).bind(this);
502
+    this._boundFailureHandler = this.handleAJAXFailure.bind(this);
503
+    this._boundSubmitHandler = this.handleFormSubmission.bind(this);
504
+    this._boundWrapperHandler = this.wrapUp.bind(this);
505
+    this.registerListeners();
506
+  },
507
+  checkForEscapeOrReturn: function(e) {
508
+    if (!this._editing || e.ctrlKey || e.altKey || e.shiftKey) return;
509
+    if (Event.KEY_ESC == e.keyCode)
510
+      this.handleFormCancellation(e);
511
+    else if (Event.KEY_RETURN == e.keyCode)
512
+      this.handleFormSubmission(e);
513
+  },
514
+  createControl: function(mode, handler, extraClasses) {
515
+    var control = this.options[mode + 'Control'];
516
+    var text = this.options[mode + 'Text'];
517
+    if ('button' == control) {
518
+      var btn = document.createElement('input');
519
+      btn.type = 'submit';
520
+      btn.value = text;
521
+      btn.className = 'editor_' + mode + '_button';
522
+      if ('cancel' == mode)
523
+        btn.onclick = this._boundCancelHandler;
524
+      this._form.appendChild(btn);
525
+      this._controls[mode] = btn;
526
+    } else if ('link' == control) {
527
+      var link = document.createElement('a');
528
+      link.href = '#';
529
+      link.appendChild(document.createTextNode(text));
530
+      link.onclick = 'cancel' == mode ? this._boundCancelHandler : this._boundSubmitHandler;
531
+      link.className = 'editor_' + mode + '_link';
532
+      if (extraClasses)
533
+        link.className += ' ' + extraClasses;
534
+      this._form.appendChild(link);
535
+      this._controls[mode] = link;
536
+    }
537
+  },
538
+  createEditField: function() {
539
+    var text = (this.options.loadTextURL ? this.options.loadingText : this.getText());
540
+    var fld;
541
+    if (1 >= this.options.rows && !/\r|\n/.test(this.getText())) {
542
+      fld = document.createElement('input');
543
+      fld.type = 'text';
544
+      var size = this.options.size || this.options.cols || 0;
545
+      if (0 < size) fld.size = size;
546
+    } else {
547
+      fld = document.createElement('textarea');
548
+      fld.rows = (1 >= this.options.rows ? this.options.autoRows : this.options.rows);
549
+      fld.cols = this.options.cols || 40;
550
+    }
551
+    fld.name = this.options.paramName;
552
+    fld.value = text; // No HTML breaks conversion anymore
553
+    fld.className = 'editor_field';
554
+    if (this.options.submitOnBlur)
555
+      fld.onblur = this._boundSubmitHandler;
556
+    this._controls.editor = fld;
557
+    if (this.options.loadTextURL)
558
+      this.loadExternalText();
559
+    this._form.appendChild(this._controls.editor);
560
+  },
561
+  createForm: function() {
562
+    var ipe = this;
563
+    function addText(mode, condition) {
564
+      var text = ipe.options['text' + mode + 'Controls'];
565
+      if (!text || condition === false) return;
566
+      ipe._form.appendChild(document.createTextNode(text));
567
+    };
568
+    this._form = $(document.createElement('form'));
569
+    this._form.id = this.options.formId;
570
+    this._form.addClassName(this.options.formClassName);
571
+    this._form.onsubmit = this._boundSubmitHandler;
572
+    this.createEditField();
573
+    if ('textarea' == this._controls.editor.tagName.toLowerCase())
574
+      this._form.appendChild(document.createElement('br'));
575
+    if (this.options.onFormCustomization)
576
+      this.options.onFormCustomization(this, this._form);
577
+    addText('Before', this.options.okControl || this.options.cancelControl);
578
+    this.createControl('ok', this._boundSubmitHandler);
579
+    addText('Between', this.options.okControl && this.options.cancelControl);
580
+    this.createControl('cancel', this._boundCancelHandler, 'editor_cancel');
581
+    addText('After', this.options.okControl || this.options.cancelControl);
582
+  },
583
+  destroy: function() {
584
+    if (this._oldInnerHTML)
585
+      this.element.innerHTML = this._oldInnerHTML;
586
+    this.leaveEditMode();
587
+    this.unregisterListeners();
588
+  },
589
+  enterEditMode: function(e) {
590
+    if (this._saving || this._editing) return;
591
+    this._editing = true;
592
+    this.triggerCallback('onEnterEditMode');
593
+    if (this.options.externalControl)
594
+      this.options.externalControl.hide();
595
+    this.element.hide();
596
+    this.createForm();
597
+    this.element.parentNode.insertBefore(this._form, this.element);
598
+    if (!this.options.loadTextURL)
599
+      this.postProcessEditField();
600
+    if (e) Event.stop(e);
601
+  },
602
+  enterHover: function(e) {
603
+    if (this.options.hoverClassName)
604
+      this.element.addClassName(this.options.hoverClassName);
605
+    if (this._saving) return;
606
+    this.triggerCallback('onEnterHover');
607
+  },
608
+  getText: function() {
609
+    return this.element.innerHTML;
610
+  },
611
+  handleAJAXFailure: function(transport) {
612
+    this.triggerCallback('onFailure', transport);
613
+    if (this._oldInnerHTML) {
614
+      this.element.innerHTML = this._oldInnerHTML;
615
+      this._oldInnerHTML = null;
616
+    }
617
+  },
618
+  handleFormCancellation: function(e) {
619
+    this.wrapUp();
620
+    if (e) Event.stop(e);
621
+  },
622
+  handleFormSubmission: function(e) {
623
+    var form = this._form;
624
+    var value = $F(this._controls.editor);
625
+    this.prepareSubmission();
626
+    var params = this.options.callback(form, value) || '';
627
+    if (Object.isString(params))
628
+      params = params.toQueryParams();
629
+    params.editorId = this.element.id;
630
+    if (this.options.htmlResponse) {
631
+      var options = Object.extend({ evalScripts: true }, this.options.ajaxOptions);
632
+      Object.extend(options, {
633
+        parameters: params,
634
+        onComplete: this._boundWrapperHandler,
635
+        onFailure: this._boundFailureHandler
636
+      });
637
+      new Ajax.Updater({ success: this.element }, this.url, options);
638
+    } else {
639
+      var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
640
+      Object.extend(options, {
641
+        parameters: params,
642
+        onComplete: this._boundWrapperHandler,
643
+        onFailure: this._boundFailureHandler
644
+      });
645
+      new Ajax.Request(this.url, options);
646
+    }
647
+    if (e) Event.stop(e);
648
+  },
649
+  leaveEditMode: function() {
650
+    this.element.removeClassName(this.options.savingClassName);
651
+    this.removeForm();
652
+    this.leaveHover();
653
+    this.element.style.backgroundColor = this._originalBackground;
654
+    this.element.show();
655
+    if (this.options.externalControl)
656
+      this.options.externalControl.show();
657
+    this._saving = false;
658
+    this._editing = false;
659
+    this._oldInnerHTML = null;
660
+    this.triggerCallback('onLeaveEditMode');
661
+  },
662
+  leaveHover: function(e) {
663
+    if (this.options.hoverClassName)
664
+      this.element.removeClassName(this.options.hoverClassName);
665
+    if (this._saving) return;
666
+    this.triggerCallback('onLeaveHover');
667
+  },
668
+  loadExternalText: function() {
669
+    this._form.addClassName(this.options.loadingClassName);
670
+    this._controls.editor.disabled = true;
671
+    var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
672
+    Object.extend(options, {
673
+      parameters: 'editorId=' + encodeURIComponent(this.element.id),
674
+      onComplete: Prototype.emptyFunction,
675
+      onSuccess: function(transport) {
676
+        this._form.removeClassName(this.options.loadingClassName);
677
+        var text = transport.responseText;
678
+        if (this.options.stripLoadedTextTags)
679
+          text = text.stripTags();
680
+        this._controls.editor.value = text;
681
+        this._controls.editor.disabled = false;
682
+        this.postProcessEditField();
683
+      }.bind(this),
684
+      onFailure: this._boundFailureHandler
685
+    });
686
+    new Ajax.Request(this.options.loadTextURL, options);
687
+  },
688
+  postProcessEditField: function() {
689
+    var fpc = this.options.fieldPostCreation;
690
+    if (fpc)
691
+      $(this._controls.editor)['focus' == fpc ? 'focus' : 'activate']();
692
+  },
693
+  prepareOptions: function() {
694
+    this.options = Object.clone(Ajax.InPlaceEditor.DefaultOptions);
695
+    Object.extend(this.options, Ajax.InPlaceEditor.DefaultCallbacks);
696
+    [this._extraDefaultOptions].flatten().compact().each(function(defs) {
697
+      Object.extend(this.options, defs);
698
+    }.bind(this));
699
+  },
700
+  prepareSubmission: function() {
701
+    this._saving = true;
702
+    this.removeForm();
703
+    this.leaveHover();
704
+    this.showSaving();
705
+  },
706
+  registerListeners: function() {
707
+    this._listeners = { };
708
+    var listener;
709
+    $H(Ajax.InPlaceEditor.Listeners).each(function(pair) {
710
+      listener = this[pair.value].bind(this);
711
+      this._listeners[pair.key] = listener;
712
+      if (!this.options.externalControlOnly)
713
+        this.element.observe(pair.key, listener);
714
+      if (this.options.externalControl)
715
+        this.options.externalControl.observe(pair.key, listener);
716
+    }.bind(this));
717
+  },
718
+  removeForm: function() {
719
+    if (!this._form) return;
720
+    this._form.remove();
721
+    this._form = null;
722
+    this._controls = { };
723
+  },
724
+  showSaving: function() {
725
+    this._oldInnerHTML = this.element.innerHTML;
726
+    this.element.innerHTML = this.options.savingText;
727
+    this.element.addClassName(this.options.savingClassName);
728
+    this.element.style.backgroundColor = this._originalBackground;
729
+    this.element.show();
730
+  },
731
+  triggerCallback: function(cbName, arg) {
732
+    if ('function' == typeof this.options[cbName]) {
733
+      this.options[cbName](this, arg);
734
+    }
735
+  },
736
+  unregisterListeners: function() {
737
+    $H(this._listeners).each(function(pair) {
738
+      if (!this.options.externalControlOnly)
739
+        this.element.stopObserving(pair.key, pair.value);
740
+      if (this.options.externalControl)
741
+        this.options.externalControl.stopObserving(pair.key, pair.value);
742
+    }.bind(this));
743
+  },
744
+  wrapUp: function(transport) {
745
+    this.leaveEditMode();
746
+    // Can't use triggerCallback due to backward compatibility: requires
747
+    // binding + direct element
748
+    this._boundComplete(transport, this.element);
749
+  }
750
+});
751
+
752
+Object.extend(Ajax.InPlaceEditor.prototype, {
753
+  dispose: Ajax.InPlaceEditor.prototype.destroy
754
+});
755
+
756
+Ajax.InPlaceCollectionEditor = Class.create(Ajax.InPlaceEditor, {
757
+  initialize: function($super, element, url, options) {
758
+    this._extraDefaultOptions = Ajax.InPlaceCollectionEditor.DefaultOptions;
759
+    $super(element, url, options);
760
+  },
761
+
762
+  createEditField: function() {
763
+    var list = document.createElement('select');
764
+    list.name = this.options.paramName;
765
+    list.size = 1;
766
+    this._controls.editor = list;
767
+    this._collection = this.options.collection || [];
768
+    if (this.options.loadCollectionURL)
769
+      this.loadCollection();
770
+    else
771
+      this.checkForExternalText();
772
+    this._form.appendChild(this._controls.editor);
773
+  },
774
+
775
+  loadCollection: function() {
776
+    this._form.addClassName(this.options.loadingClassName);
777
+    this.showLoadingText(this.options.loadingCollectionText);
778
+    var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
779
+    Object.extend(options, {
780
+      parameters: 'editorId=' + encodeURIComponent(this.element.id),
781
+      onComplete: Prototype.emptyFunction,
782
+      onSuccess: function(transport) {
783
+        var js = transport.responseText.strip();
784
+        if (!/^\[.*\]$/.test(js)) // TODO: improve sanity check
785
+          throw 'Server returned an invalid collection representation.';
786
+        this._collection = eval(js);
787
+        this.checkForExternalText();
788
+      }.bind(this),
789
+      onFailure: this.onFailure
790
+    });
791
+    new Ajax.Request(this.options.loadCollectionURL, options);
792
+  },
793
+
794
+  showLoadingText: function(text) {
795
+    this._controls.editor.disabled = true;
796
+    var tempOption = this._controls.editor.firstChild;
797
+    if (!tempOption) {
798
+      tempOption = document.createElement('option');
799
+      tempOption.value = '';
800
+      this._controls.editor.appendChild(tempOption);
801
+      tempOption.selected = true;
802
+    }
803
+    tempOption.update((text || '').stripScripts().stripTags());
804
+  },
805
+
806
+  checkForExternalText: function() {
807
+    this._text = this.getText();
808
+    if (this.options.loadTextURL)
809
+      this.loadExternalText();
810
+    else
811
+      this.buildOptionList();
812
+  },
813
+
814
+  loadExternalText: function() {
815
+    this.showLoadingText(this.options.loadingText);
816
+    var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
817
+    Object.extend(options, {
818
+      parameters: 'editorId=' + encodeURIComponent(this.element.id),
819
+      onComplete: Prototype.emptyFunction,
820
+      onSuccess: function(transport) {
821
+        this._text = transport.responseText.strip();
822
+        this.buildOptionList();
823
+      }.bind(this),
824
+      onFailure: this.onFailure
825
+    });
826
+    new Ajax.Request(this.options.loadTextURL, options);
827
+  },
828
+
829
+  buildOptionList: function() {
830
+    this._form.removeClassName(this.options.loadingClassName);
831
+    this._collection = this._collection.map(function(entry) {
832
+      return 2 === entry.length ? entry : [entry, entry].flatten();
833
+    });
834
+    var marker = ('value' in this.options) ? this.options.value : this._text;
835
+    var textFound = this._collection.any(function(entry) {
836
+      return entry[0] == marker;
837
+    }.bind(this));
838
+    this._controls.editor.update('');
839
+    var option;
840
+    this._collection.each(function(entry, index) {
841
+      option = document.createElement('option');
842
+      option.value = entry[0];
843
+      option.selected = textFound ? entry[0] == marker : 0 == index;
844
+      option.appendChild(document.createTextNode(entry[1]));
845
+      this._controls.editor.appendChild(option);
846
+    }.bind(this));
847
+    this._controls.editor.disabled = false;
848
+    Field.scrollFreeActivate(this._controls.editor);
849
+  }
850
+});
851
+
852
+//**** DEPRECATION LAYER FOR InPlace[Collection]Editor! ****
853
+//**** This only  exists for a while,  in order to  let ****
854
+//**** users adapt to  the new API.  Read up on the new ****
855
+//**** API and convert your code to it ASAP!            ****
856
+
857
+Ajax.InPlaceEditor.prototype.initialize.dealWithDeprecatedOptions = function(options) {
858
+  if (!options) return;
859
+  function fallback(name, expr) {
860
+    if (name in options || expr === undefined) return;
861
+    options[name] = expr;
862
+  };
863
+  fallback('cancelControl', (options.cancelLink ? 'link' : (options.cancelButton ? 'button' :
864
+    options.cancelLink == options.cancelButton == false ? false : undefined)));
865
+  fallback('okControl', (options.okLink ? 'link' : (options.okButton ? 'button' :
866
+    options.okLink == options.okButton == false ? false : undefined)));
867
+  fallback('highlightColor', options.highlightcolor);
868
+  fallback('highlightEndColor', options.highlightendcolor);
869
+};
870
+
871
+Object.extend(Ajax.InPlaceEditor, {
872
+  DefaultOptions: {
873
+    ajaxOptions: { },
874
+    autoRows: 3,                                // Use when multi-line w/ rows == 1
875
+    cancelControl: 'link',                      // 'link'|'button'|false
876
+    cancelText: 'cancel',
877
+    clickToEditText: 'Click to edit',
878
+    externalControl: null,                      // id|elt
879
+    externalControlOnly: false,
880
+    fieldPostCreation: 'activate',              // 'activate'|'focus'|false
881
+    formClassName: 'inplaceeditor-form',
882
+    formId: null,                               // id|elt
883
+    highlightColor: '#ffff99',
884
+    highlightEndColor: '#ffffff',
885
+    hoverClassName: '',
886
+    htmlResponse: true,
887
+    loadingClassName: 'inplaceeditor-loading',
888
+    loadingText: 'Loading...',
889
+    okControl: 'button',                        // 'link'|'button'|false
890
+    okText: 'ok',
891
+    paramName: 'value',
892
+    rows: 1,                                    // If 1 and multi-line, uses autoRows
893
+    savingClassName: 'inplaceeditor-saving',
894
+    savingText: 'Saving...',
895
+    size: 0,
896
+    stripLoadedTextTags: false,
897
+    submitOnBlur: false,
898
+    textAfterControls: '',
899
+    textBeforeControls: '',
900
+    textBetweenControls: ''
901
+  },
902
+  DefaultCallbacks: {
903
+    callback: function(form) {
904
+      return Form.serialize(form);
905
+    },
906
+    onComplete: function(transport, element) {
907
+      // For backward compatibility, this one is bound to the IPE, and passes
908
+      // the element directly.  It was too often customized, so we don't break it.
909
+      new Effect.Highlight(element, {
910
+        startcolor: this.options.highlightColor, keepBackgroundImage: true });
911
+    },
912
+    onEnterEditMode: null,
913
+    onEnterHover: function(ipe) {
914
+      ipe.element.style.backgroundColor = ipe.options.highlightColor;
915
+      if (ipe._effect)
916
+        ipe._effect.cancel();
917
+    },
918
+    onFailure: function(transport, ipe) {
919
+      alert('Error communication with the server: ' + transport.responseText.stripTags());
920
+    },
921
+    onFormCustomization: null, // Takes the IPE and its generated form, after editor, before controls.
922
+    onLeaveEditMode: null,
923
+    onLeaveHover: function(ipe) {
924
+      ipe._effect = new Effect.Highlight(ipe.element, {
925
+        startcolor: ipe.options.highlightColor, endcolor: ipe.options.highlightEndColor,
926
+        restorecolor: ipe._originalBackground, keepBackgroundImage: true
927
+      });
928
+    }
929
+  },
930
+  Listeners: {
931
+    click: 'enterEditMode',
932
+    keydown: 'checkForEscapeOrReturn',
933
+    mouseover: 'enterHover',
934
+    mouseout: 'leaveHover'
935
+  }
936
+});
937
+
938
+Ajax.InPlaceCollectionEditor.DefaultOptions = {
939
+  loadingCollectionText: 'Loading options...'
940
+};
941
+
942
+// Delayed observer, like Form.Element.Observer, 
943
+// but waits for delay after last key input
944
+// Ideal for live-search fields
945
+
946
+Form.Element.DelayedObserver = Class.create({
947
+  initialize: function(element, delay, callback) {
948
+    this.delay     = delay || 0.5;
949
+    this.element   = $(element);
950
+    this.callback  = callback;
951
+    this.timer     = null;
952
+    this.lastValue = $F(this.element); 
953
+    Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this));
954
+  },
955
+  delayedListener: function(event) {
956
+    if(this.lastValue == $F(this.element)) return;
957
+    if(this.timer) clearTimeout(this.timer);
958
+    this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000);
959
+    this.lastValue = $F(this.element);
960
+  },
961
+  onTimerEvent: function() {
962
+    this.timer = null;
963
+    this.callback(this.element, $F(this.element));
964
+  }
965
+});

+ 538
- 0
src/com/dmdirc/addons/ui_web/res/javascript/dmdirc.js View File

@@ -0,0 +1,538 @@
1
+var enabled = true;
2
+var clientID = Math.ceil(Math.random() * 1000000000);
3
+var activeWindow = null;
4
+var wus_open = false;
5
+var windows = new Hash();
6
+var speeds = new Hash();
7
+var interval;
8
+
9
+speeds.set('wus', 1000);
10
+
11
+function dmdirc_start() {
12
+    if (interval != null) {
13
+        clearInterval(interval);
14
+    }
15
+    
16
+    doUpdate();
17
+}
18
+
19
+function setSpeed(what, speed) {
20
+    document.getElementById(what + '_' + speeds.get(what)).style.textDecoration = 'none';
21
+    speeds.set(what, speed);
22
+    document.getElementById(what + '_' + speed).style.textDecoration = 'underline';
23
+}
24
+
25
+function treeview_add(name, id, type, parent) {
26
+    var parentNode;
27
+
28
+    if (parent == null) {
29
+        parentNode = document.getElementById('treeview');
30
+    } else {
31
+        parentNode = document.getElementById(parent);
32
+
33
+        if (parentNode.getElementsByTagName('ul').length == 0) {
34
+            var newUL = document.createElement('ul');
35
+            parentNode.appendChild(newUL);
36
+            parentNode = newUL;
37
+        } else {
38
+            parentNode = parentNode.getElementsByTagName('ul')[0];
39
+        }
40
+    }
41
+
42
+    var newNode = document.createElement('li');
43
+    newNode.id = id;
44
+    newNode.className = type;
45
+
46
+    var wrapperNode = document.createElement('div');
47
+    wrapperNode.innerHTML = name;
48
+    wrapperNode.style.cursor = 'pointer';
49
+    wrapperNode.onclick = function() { treeview_click(newNode); };
50
+    newNode.appendChild(wrapperNode);
51
+
52
+    parentNode.appendChild(newNode);
53
+}
54
+
55
+function treeview_click(element) {
56
+    window_show(element.id);
57
+}
58
+
59
+function treeview_setactive(id) {
60
+    if (activeWindow != null) {
61
+        document.getElementById(activeWindow).style.fontWeight = 'normal';
62
+    }
63
+
64
+    activeWindow = id;
65
+    document.getElementById(activeWindow).style.fontWeight = 'bold';
66
+}
67
+
68
+function wus_show() {
69
+    if (!enabled) {
70
+        return;
71
+    }
72
+    
73
+    if (document.getElementById('wus') == null) {
74
+        var nsd = document.createElement('div');
75
+        nsd.style.position = 'absolute';
76
+        nsd.style.top = '50%';
77
+        nsd.style.height = '400px';
78
+        nsd.style.width = '600px';
79
+        nsd.style.left = '50%';
80
+        nsd.style.marginLeft = '-300px';
81
+        nsd.style.marginTop = '-200px';
82
+        nsd.style.border = '1px solid black';
83
+        nsd.style.display = 'none';
84
+        nsd.style.zIndex = '10';
85
+        nsd.id = 'wus';
86
+        nsd.className = 'dialog';
87
+        nsd.innerHTML = '<p class="nowindow">Loading</p>';
88
+
89
+        document.body.appendChild(nsd);
90
+    }
91
+    
92
+    wus_open = true;
93
+    
94
+    new Ajax.Updater('wus', '/static/webuistatus.html', {onSuccess:function() {
95
+            setTimeout('wus_init()', 200); }});
96
+    new Effect.Appear('wus');
97
+    
98
+    wus_query();
99
+}
100
+
101
+function wus_init() {
102
+    if (speeds.get('wus') != 1000) {
103
+        document.getElementById('wus_1000').style.textDecoration = 'none';
104
+        document.getElementById('wus_' + speeds.get('wus')).style.textDecoration
105
+            = 'underline';
106
+    }
107
+
108
+    draggable("wus");
109
+}
110
+
111
+function wus_addrequest(url) {
112
+    var objDiv = document.getElementById('wus_requests');
113
+    
114
+    if (objDiv != null) {
115
+        var p = document.createElement('p');
116
+        p.innerHTML = '<code>' + url + '</code> ' + new Date().toString();
117
+
118
+
119
+        objDiv.appendChild(p);
120
+        objDiv.scrollTop = objDiv.scrollHeight;    
121
+    }
122
+}
123
+
124
+function wus_query() {
125
+    new Ajax.Request('/dynamic/clients',
126
+    {onSuccess:wus_handler, onFailure:errFunc, onException:excFunc});
127
+}
128
+
129
+function wus_close() {
130
+    wus_open = false;
131
+    
132
+    new Effect.Fade('wus');
133
+}
134
+
135
+function wus_handler(transport) {
136
+    if (!wus_open) {
137
+        return;
138
+    }
139
+    
140
+    var data = eval('(' + transport.responseText + ')');
141
+
142
+    removeElements('wus_clients');
143
+    
144
+    for (var i = 0; i < data.length; i++) {
145
+        var client = data[i];
146
+        var tr = document.createElement('tr');
147
+        var td = document.createElement('td');
148
+        td.innerHTML = client.ip;
149
+        tr.appendChild(td);
150
+        
151
+        var secs = Math.floor(client.time / 1000);
152
+        var mins = Math.floor(client.time / 60000)
153
+        
154
+        td = document.createElement('td');
155
+        td.innerHTML = (mins > 0 ? mins + ' minute' + (mins != 1 ? 's' : '') :
156
+            secs + ' second' + (secs != 1 ? 's' : '')) + ' ago';
157
+        tr.appendChild(td);
158
+        
159
+        td = document.createElement('td');
160
+        td.innerHTML = client.eventCount;
161
+        tr.appendChild(td);
162
+        
163
+        document.getElementById('wus_clients').appendChild(tr);
164
+    }
165
+    
166
+    document.getElementById('wus_last').innerHTML = 'Last updated: ' + new Date().toString();
167
+    
168
+    setTimeout('wus_query()', speeds.get('wus'));
169
+}
170
+
171
+function nsd_show() {
172
+    if (!enabled) {
173
+        return;
174
+    }
175
+    
176
+    if (document.getElementById('nsd') == null) {
177
+        var nsd = document.createElement('div');
178
+        nsd.style.position = 'absolute';
179
+        nsd.style.top = '50%';
180
+        nsd.style.height = '300px';
181
+        nsd.style.width = '500px';
182
+        nsd.style.left = '50%';
183
+        nsd.style.marginLeft = '-250px';
184
+        nsd.style.marginTop = '-150px';
185
+        nsd.style.border = '1px solid black';
186
+        nsd.style.display = 'none';
187
+        nsd.style.zIndex = '10';
188
+        nsd.id = 'nsd';
189
+        nsd.className = 'dialog';
190
+        nsd.innerHTML = '<p class="nowindow">Loading</p>';
191
+
192
+        document.body.appendChild(nsd);
193
+    }
194
+
195
+    new Ajax.Updater('nsd', '/static/newserverdialog.html', {onSuccess:function() {
196
+            setTimeout('draggable("nsd")', 200); }});
197
+    new Ajax.Request('/dynamic/getprofiles',
198
+    {onFailure:errFunc, onSuccess:handlerFunc, onException:excFunc});
199
+    new Effect.Appear('nsd');
200
+}
201
+
202
+function draggable(what) {
203
+    new Draggable(what, {handle:document.getElementById(what).firstChild,
204
+        starteffect:null,endeffect:null});
205
+}
206
+
207
+function nsd_ok() {
208
+    var server = document.getElementById('nsd_server').value;
209
+    var port = document.getElementById('nsd_port').value;
210
+    var password = document.getElementById('nsd_password').value;
211
+    var profile = document.getElementById('nsd_profile').value;
212
+
213
+    if (!/^[^\s]+$/.test(server)) {
214
+        alert("Server name cannot contain spaces");
215
+        new Effect.Pulsate('nsd_server', {pulses: 3});
216
+        return;
217
+    }
218
+
219
+    if (!/^[0-9]+$/.test(port) || port < 1 || port > 65535) {
220
+        alert("Port must be a number between 1 and 65535");
221
+        new Effect.Pulsate('nsd_port', {pulses: 3});
222
+        return;
223
+    }
224
+
225
+    new Ajax.Request('/dynamic/newserver',
226
+    {parameters:{server:server, port:port, password:password,
227
+            profile:profile}, onFailure:errFunc});
228
+    nsd_cancel();
229
+}
230
+
231
+function nsd_cancel() {
232
+    new Effect.Fade('nsd');
233
+}
234
+
235
+function profiles_clear() {
236
+    var elements = document.getElementsByClassName('profilelist');
237
+
238
+    for (var i = 0; i < elements.length; i++) {
239
+        removeElements(elements[i].id);
240
+    }
241
+}
242
+
243
+function profiles_add(profile) {
244
+    var elements = document.getElementsByClassName('profilelist');
245
+
246
+    for (var i = 0; i < elements.length; i++) {
247
+        var option = document.createElement('option');
248
+        option.innerHTML = profile;
249
+        option.value = profile;
250
+        elements[i].appendChild(option);
251
+    }
252
+}
253
+
254
+function removeElements(id) {
255
+    var element = document.getElementById(id);
256
+    if (element.hasChildNodes()) {
257
+        while (element.childNodes.length > 0) {
258
+            element.removeChild(element.firstChild );
259
+        }
260
+    }
261
+}
262
+
263
+function nicklist_show() {
264
+    var nicklist = document.getElementById('nicklist');
265
+
266
+    if (nicklist.style.display != 'block') {
267
+        document.getElementById('content').style.right = '240px';
268
+        nicklist.style.display = 'block';
269
+        new Ajax.Request('/dynamic/nicklistrefresh',
270
+        {onFailure:errFunc})
271
+    }
272
+}
273
+
274
+function nicklist_clear() {
275
+    removeElements('nicklist');
276
+}
277
+
278
+function nicklist_add(nick) {
279
+    var entry = document.createElement('li');
280
+    entry.innerHTML = nick;
281
+
282
+    document.getElementById('nicklist').appendChild(entry);
283
+}
284
+
285
+function nicklist_hide() {
286
+    var nicklist = document.getElementById('nicklist');
287
+
288
+    if (nicklist.style.display == 'block') {
289
+        document.getElementById('content').style.right = '15px';
290
+        nicklist.style.display = 'none';
291
+    }
292
+}
293
+
294
+function inputarea_show() {
295
+    var ia = document.getElementById('inputarea');
296
+
297
+    if (ia.style.display != 'block') {
298
+        document.getElementById('content').style.bottom = '65px';
299
+        document.getElementById('nicklist').style.bottom = '65px';
300
+        ia.style.display = 'block';
301
+    }
302
+}
303
+
304
+function inputarea_hide() {
305
+    var ia = document.getElementById('inputarea');
306
+
307
+    if (ia.style.display == 'block') {
308
+        document.getElementById('content').style.bottom = '40px';
309
+        document.getElementById('nicklist').style.bottom = '40px';
310
+        ia.style.display = 'none';
311
+    }
312
+}
313
+
314
+function input_settext(text) {
315
+    document.getElementById('input').value = text;
316
+}
317
+
318
+function input_keydown(e) {
319
+    var keynum;
320
+
321
+    if (window.event) {
322
+        keynum = e.keyCode;
323
+    } else if (e.which) {
324
+        keynum = e.which;
325
+    }
326
+    
327
+    if (keynum == 13) {
328
+        keynum = 10;
329
+    }
330
+
331
+    var control = e.ctrlKey;
332
+    var shift = e.shiftKey;
333
+    var alt = e.altKey;
334
+    var el = document.getElementById('input');
335
+
336
+    if (keynum == 10 && !control) {
337
+        new Ajax.Request('/dynamic/input',
338
+        {parameters:{input:document.getElementById('input').value,
339
+                clientID:clientID}, onFailure:errFunc, onException:excFunc});
340
+        el.value = '';
341
+    } else if (keynum == 9 && !control) {
342
+        new Ajax.Request('/dynamic/tab',
343
+        {parameters:{input:el.value, selstart:el.selectionStart,
344
+                selend:el.selectionEnd, clientID:clientID}, onFailure:errFunc, onException:excFunc});
345
+        return false;
346
+    } else if (keynum == 38 || keynum == 40) {
347
+        // up/down
348
+        new Ajax.Request('/dynamic/key' + ((keynum == 38) ? 'up' : 'down'),
349
+        {parameters:{input:el.value, selstart:el.selectionStart,
350
+                selend:el.selectionEnd, clientID:clientID}, onFailure:errFunc, onException:excFunc});
351
+        return false;
352
+    } else if (control && (keynum == 10 || keynum == 66 || keynum == 70 || keynum == 73
353
+        || keynum == 75 || keynum == 79 || keynum == 85)) {
354
+        new Ajax.Request('/dynamic/key',
355
+        {parameters:{input:el.value, selstart:el.selectionStart,
356
+                selend:el.selectionEnd, clientID:clientID, key:keynum, ctrl:control,
357
+                shift:shift, alt:alt}, onFailure:errFunc, onException:excFunc});
358
+        return false;
359
+    }
360
+
361
+    return true;
362
+}
363
+
364
+function input_setcaret(pos) {
365
+    var el = document.getElementById('input');
366
+    el.selectionStart = pos;
367
+    el.selectionEnd = pos;
368
+}
369
+
370
+function window_clear(id) {
371
+    if (activeWindow == id) {
372
+        removeElements('content');
373
+    }
374
+    
375
+    windows.get(id).lines = [];
376
+}
377
+
378
+function window_addline(id, line) {
379
+    var p = document.createElement('p');
380
+    p.innerHTML = line;
381
+    
382
+    if (activeWindow == id) {
383
+        document.getElementById('content').appendChild(p);
384
+        var objDiv = document.getElementById('content');
385
+        objDiv.scrollTop = objDiv.scrollHeight;
386
+    }
387
+    
388
+    windows.get(id).lines[windows.get(id).lines.length] = p;
389
+}
390
+
391
+function window_create(window, parent) {
392
+    treeview_add(window.name, window.id, window.type, parent == null ? null : parent.id);
393
+    windows.set(window.id, window);
394
+    windows.get(window.id).lines = [];
395
+    window_show(window.id);
396
+    
397
+    new Ajax.Request('/dynamic/windowrefresh',
398
+    {parameters:{window:window.id}, onFailure:errFunc,
399
+        onException:excFunc})    
400
+}
401
+
402
+function window_show(id) {
403
+    treeview_setactive(id);
404
+    title_settext(windows.get(id).title)
405
+
406
+    var className = document.getElementById(id).className;
407
+
408
+    if (className == 'channel')  {
409
+        nicklist_show();
410
+    } else {
411
+        nicklist_hide();
412
+    }
413
+
414
+    if (className == 'server' || className == 'channel' || className == 'input'
415
+        || className == 'query') {
416
+        inputarea_show();
417
+    } else {
418
+        inputarea_hide();
419
+    }
420
+    
421
+    removeElements('content');
422
+    var objDiv = document.getElementById('content');
423
+    
424
+    windows.get(id).lines.forEach(function(x) {
425
+        objDiv.appendChild(x);
426
+    });
427
+    objDiv.scrollTop = objDiv.scrollHeight;
428
+}
429
+
430
+function title_settext(newText) {
431
+    var title = newText + " - DMDirc web interface";
432
+    document.getElementById('title').innerHTML = new String(title).escapeHTML();
433
+    document.title = title;
434
+}
435
+
436
+function statusbar_settext(newText) {
437
+    document.getElementById('statusbar_main').innerHTML = new String(newText).escapeHTML();
438
+    new Effect.Highlight('statusbar_main', {endcolor: "#c0c0c0", restorecolor: "#c0c0c0"});
439
+}
440
+
441
+function link_hyperlink(url) {
442
+    statusbar_settext('Opening ' + url + '...');
443
+    window.open(url);
444
+}
445
+
446
+function link_channel(channel) {
447
+    new Ajax.Request('/dynamic/joinchannel',
448
+    {parameters:{clientID:clientID, source:activeWindow, channel:channel},
449
+        onFailure:errFunc, onException:excFunc});
450
+}
451
+
452
+function link_query(user) {
453
+    new Ajax.Request('/dynamic/openquery',
454
+    {parameters:{clientID:clientID, source:activeWindow, target:user},
455
+        onFailure:errFunc, onException:excFunc});
456
+}
457
+
458
+function doUpdate() {
459
+    new Ajax.Request('/dynamic/feed',
460
+    {parameters:{clientID:clientID}, method: 'GET',
461
+        onSuccess:updateHandlerFunc, onFailure:updateErrFunc, onException:updateExcFunc});
462
+}
463
+
464
+function updateHandlerFunc(transport) {
465
+    handlerFunc(transport);
466
+    doUpdate();
467
+}
468
+
469
+function handlerFunc(transport) {
470
+    enabled = true;
471
+    
472
+    var data = eval('(' + transport.responseText + ')');
473
+
474
+    for (var i = 0; i < data.length; i++) {
475
+        var event = data[i];
476
+
477
+        if (event.type == 'statusbar') {
478
+            statusbar_settext(event.arg1);
479
+        } else if (event.type == 'clearprofiles') {
480
+            profiles_clear();
481
+        } else if (event.type == 'addprofile') {
482
+            profiles_add(event.arg1);
483
+        } else if (event.type == 'newwindow') {
484
+            window_create(event.arg1);
485
+        } else if (event.type == 'newchildwindow') {
486
+            window_create(event.arg1[1], event.arg1[0]);
487
+        } else if (event.type == 'clearwindow') {
488
+            window_clear(event.arg1);
489
+        } else if (event.type == 'lineadded') {
490
+            window_addline(event.arg1.window, event.arg1.message);
491
+        } else if (event.type == 'settext') {
492
+            input_settext(event.arg1);
493
+        } else if (event.type == 'clearnicklist') {
494
+            nicklist_clear();
495
+        } else if (event.type == 'addnicklist') {
496
+            nicklist_add(event.arg1);
497
+        } else if (event.type == 'setcaret') {
498
+            input_setcaret(event.arg1);
499
+        } else {
500
+            statusbar_settext("Unknown event type: " + event.type);
501
+        }
502
+    }
503
+}
504
+
505
+function updateErrFunc(transport) {
506
+    errFunc(transport);
507
+    doUpdate();
508
+}
509
+
510
+function errFunc(transport) {
511
+    statusbar_settext('Error while perfoming remote call...');
512
+    alert(transport.status + "\n" + transport.statusText + "\n" + transport.responseText);
513
+}
514
+
515
+function updateExcFunc(request, exception) {
516
+    excFunc(request, exception);
517
+}
518
+
519
+function excFunc(request, exception) {
520
+    //enabled = false;
521
+    statusbar_settext('An exception occured while updating. Perhaps the client shutdown?');
522
+}
523
+
524
+function callInProgress (xmlhttp) {
525
+    if (xmlhttp.readyState == 0 || xmlhttp.readyState == 4) {
526
+        return false;
527
+    } else {
528
+        return true;
529
+    }
530
+}
531
+    
532
+Ajax.Responders.register({
533
+    onCreate: function(request) {        
534
+        if (wus_open) {
535
+            wus_addrequest(request.url);
536
+        }
537
+    }
538
+});

+ 974
- 0
src/com/dmdirc/addons/ui_web/res/javascript/dragdrop.js View File

@@ -0,0 +1,974 @@
1
+// script.aculo.us dragdrop.js v1.8.1, Thu Jan 03 22:07:12 -0500 2008
2
+
3
+// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
4
+//           (c) 2005-2007 Sammi Williams (http://www.oriontransfer.co.nz, sammi@oriontransfer.co.nz)
5
+// 
6
+// script.aculo.us is freely distributable under the terms of an MIT-style license.
7
+// For details, see the script.aculo.us web site: http://script.aculo.us/
8
+
9
+if(Object.isUndefined(Effect))
10
+  throw("dragdrop.js requires including script.aculo.us' effects.js library");
11
+
12
+var Droppables = {
13
+  drops: [],
14
+
15
+  remove: function(element) {
16
+    this.drops = this.drops.reject(function(d) { return d.element==$(element) });
17
+  },
18
+
19
+  add: function(element) {
20
+    element = $(element);
21
+    var options = Object.extend({
22
+      greedy:     true,
23
+      hoverclass: null,
24
+      tree:       false
25
+    }, arguments[1] || { });
26
+
27
+    // cache containers
28
+    if(options.containment) {
29
+      options._containers = [];
30
+      var containment = options.containment;
31
+      if(Object.isArray(containment)) {
32
+        containment.each( function(c) { options._containers.push($(c)) });
33
+      } else {
34
+        options._containers.push($(containment));
35
+      }
36
+    }
37
+    
38
+    if(options.accept) options.accept = [options.accept].flatten();
39
+
40
+    Element.makePositioned(element); // fix IE
41
+    options.element = element;
42
+
43
+    this.drops.push(options);
44
+  },
45
+  
46
+  findDeepestChild: function(drops) {
47
+    deepest = drops[0];
48
+      
49
+    for (i = 1; i < drops.length; ++i)
50
+      if (Element.isParent(drops[i].element, deepest.element))
51
+        deepest = drops[i];
52
+    
53
+    return deepest;
54
+  },
55
+
56
+  isContained: function(element, drop) {
57
+    var containmentNode;
58
+    if(drop.tree) {
59
+      containmentNode = element.treeNode; 
60
+    } else {
61
+      containmentNode = element.parentNode;
62
+    }
63
+    return drop._containers.detect(function(c) { return containmentNode == c });
64
+  },
65
+  
66
+  isAffected: function(point, element, drop) {
67
+    return (
68
+      (drop.element!=element) &&
69
+      ((!drop._containers) ||
70
+        this.isContained(element, drop)) &&
71
+      ((!drop.accept) ||
72
+        (Element.classNames(element).detect( 
73
+          function(v) { return drop.accept.include(v) } ) )) &&
74
+      Position.within(drop.element, point[0], point[1]) );
75
+  },
76
+
77
+  deactivate: function(drop) {
78
+    if(drop.hoverclass)
79
+      Element.removeClassName(drop.element, drop.hoverclass);
80
+    this.last_active = null;
81
+  },
82
+
83
+  activate: function(drop) {
84
+    if(drop.hoverclass)
85
+      Element.addClassName(drop.element, drop.hoverclass);
86
+    this.last_active = drop;
87
+  },
88
+
89
+  show: function(point, element) {
90
+    if(!this.drops.length) return;
91
+    var drop, affected = [];
92
+    
93
+    this.drops.each( function(drop) {
94
+      if(Droppables.isAffected(point, element, drop))
95
+        affected.push(drop);
96
+    });
97
+        
98
+    if(affected.length>0)
99
+      drop = Droppables.findDeepestChild(affected);
100
+
101
+    if(this.last_active && this.last_active != drop) this.deactivate(this.last_active);
102
+    if (drop) {
103
+      Position.within(drop.element, point[0], point[1]);
104
+      if(drop.onHover)
105
+        drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element));
106
+      
107
+      if (drop != this.last_active) Droppables.activate(drop);
108
+    }
109
+  },
110
+
111
+  fire: function(event, element) {
112
+    if(!this.last_active) return;
113
+    Position.prepare();
114
+
115
+    if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], element, this.last_active))
116
+      if (this.last_active.onDrop) {
117
+        this.last_active.onDrop(element, this.last_active.element, event); 
118
+        return true; 
119
+      }
120
+  },
121
+
122
+  reset: function() {
123
+    if(this.last_active)
124
+      this.deactivate(this.last_active);
125
+  }
126
+}
127
+
128
+var Draggables = {
129
+  drags: [],
130
+  observers: [],
131
+  
132
+  register: function(draggable) {
133
+    if(this.drags.length == 0) {
134
+      this.eventMouseUp   = this.endDrag.bindAsEventListener(this);
135
+      this.eventMouseMove = this.updateDrag.bindAsEventListener(this);
136
+      this.eventKeypress  = this.keyPress.bindAsEventListener(this);
137
+      
138
+      Event.observe(document, "mouseup", this.eventMouseUp);
139
+      Event.observe(document, "mousemove", this.eventMouseMove);
140
+      Event.observe(document, "keypress", this.eventKeypress);
141
+    }
142
+    this.drags.push(draggable);
143
+  },
144
+  
145
+  unregister: function(draggable) {
146
+    this.drags = this.drags.reject(function(d) { return d==draggable });
147
+    if(this.drags.length == 0) {
148
+      Event.stopObserving(document, "mouseup", this.eventMouseUp);
149
+      Event.stopObserving(document, "mousemove", this.eventMouseMove);
150
+      Event.stopObserving(document, "keypress", this.eventKeypress);
151
+    }
152
+  },
153
+  
154
+  activate: function(draggable) {
155
+    if(draggable.options.delay) { 
156
+      this._timeout = setTimeout(function() { 
157
+        Draggables._timeout = null; 
158
+        window.focus(); 
159
+        Draggables.activeDraggable = draggable; 
160
+      }.bind(this), draggable.options.delay); 
161
+    } else {
162
+      window.focus(); // allows keypress events if window isn't currently focused, fails for Safari
163
+      this.activeDraggable = draggable;
164
+    }
165
+  },
166
+  
167
+  deactivate: function() {
168
+    this.activeDraggable = null;
169
+  },
170
+  
171
+  updateDrag: function(event) {
172
+    if(!this.activeDraggable) return;
173
+    var pointer = [Event.pointerX(event), Event.pointerY(event)];
174
+    // Mozilla-based browsers fire successive mousemove events with
175
+    // the same coordinates, prevent needless redrawing (moz bug?)
176
+    if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return;
177
+    this._lastPointer = pointer;
178
+    
179
+    this.activeDraggable.updateDrag(event, pointer);
180
+  },
181
+  
182
+  endDrag: function(event) {
183
+    if(this._timeout) { 
184
+      clearTimeout(this._timeout); 
185
+      this._timeout = null; 
186
+    }
187
+    if(!this.activeDraggable) return;
188
+    this._lastPointer = null;
189
+    this.activeDraggable.endDrag(event);
190
+    this.activeDraggable = null;
191
+  },
192
+  
193
+  keyPress: function(event) {
194
+    if(this.activeDraggable)
195
+      this.activeDraggable.keyPress(event);
196
+  },
197
+  
198
+  addObserver: function(observer) {
199
+    this.observers.push(observer);
200
+    this._cacheObserverCallbacks();
201
+  },
202
+  
203
+  removeObserver: function(element) {  // element instead of observer fixes mem leaks
204
+    this.observers = this.observers.reject( function(o) { return o.element==element });
205
+    this._cacheObserverCallbacks();
206
+  },
207
+  
208
+  notify: function(eventName, draggable, event) {  // 'onStart', 'onEnd', 'onDrag'
209
+    if(this[eventName+'Count'] > 0)
210
+      this.observers.each( function(o) {
211
+        if(o[eventName]) o[eventName](eventName, draggable, event);
212
+      });
213
+    if(draggable.options[eventName]) draggable.options[eventName](draggable, event);
214
+  },
215
+  
216
+  _cacheObserverCallbacks: function() {
217
+    ['onStart','onEnd','onDrag'].each( function(eventName) {
218
+      Draggables[eventName+'Count'] = Draggables.observers.select(
219
+        function(o) { return o[eventName]; }
220
+      ).length;
221
+    });
222
+  }
223
+}
224
+
225
+/*--------------------------------------------------------------------------*/
226
+
227
+var Draggable = Class.create({
228
+  initialize: function(element) {
229
+    var defaults = {
230
+      handle: false,
231
+      reverteffect: function(element, top_offset, left_offset) {
232
+        var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02;
233
+        new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur,
234
+          queue: {scope:'_draggable', position:'end'}
235
+        });
236
+      },
237
+      endeffect: function(element) {
238
+        var toOpacity = Object.isNumber(element._opacity) ? element._opacity : 1.0;
239
+        new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity, 
240
+          queue: {scope:'_draggable', position:'end'},
241
+          afterFinish: function(){ 
242
+            Draggable._dragging[element] = false 
243
+          }
244
+        }); 
245
+      },
246
+      zindex: 1000,
247
+      revert: false,
248
+      quiet: false,
249
+      scroll: false,
250
+      scrollSensitivity: 20,
251
+      scrollSpeed: 15,
252
+      snap: false,  // false, or xy or [x,y] or function(x,y){ return [x,y] }
253
+      delay: 0
254
+    };
255
+    
256
+    if(!arguments[1] || Object.isUndefined(arguments[1].endeffect))
257
+      Object.extend(defaults, {
258
+        starteffect: function(element) {
259
+          element._opacity = Element.getOpacity(element);
260
+          Draggable._dragging[element] = true;
261
+          new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7}); 
262
+        }
263
+      });
264
+    
265
+    var options = Object.extend(defaults, arguments[1] || { });
266
+
267
+    this.element = $(element);
268
+    
269
+    if(options.handle && Object.isString(options.handle))
270
+      this.handle = this.element.down('.'+options.handle, 0);
271
+    
272
+    if(!this.handle) this.handle = $(options.handle);
273
+    if(!this.handle) this.handle = this.element;
274
+    
275
+    if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML) {
276
+      options.scroll = $(options.scroll);
277
+      this._isScrollChild = Element.childOf(this.element, options.scroll);
278
+    }
279
+
280
+    Element.makePositioned(this.element); // fix IE    
281
+
282
+    this.options  = options;
283
+    this.dragging = false;   
284
+
285
+    this.eventMouseDown = this.initDrag.bindAsEventListener(this);
286
+    Event.observe(this.handle, "mousedown", this.eventMouseDown);
287
+    
288
+    Draggables.register(this);
289
+  },
290
+  
291
+  destroy: function() {
292
+    Event.stopObserving(this.handle, "mousedown", this.eventMouseDown);
293
+    Draggables.unregister(this);
294
+  },
295
+  
296
+  currentDelta: function() {
297
+    return([
298
+      parseInt(Element.getStyle(this.element,'left') || '0'),
299
+      parseInt(Element.getStyle(this.element,'top') || '0')]);
300
+  },
301
+  
302
+  initDrag: function(event) {
303
+    if(!Object.isUndefined(Draggable._dragging[this.element]) &&
304
+      Draggable._dragging[this.element]) return;
305
+    if(Event.isLeftClick(event)) {    
306
+      // abort on form elements, fixes a Firefox issue
307
+      var src = Event.element(event);
308
+      if((tag_name = src.tagName.toUpperCase()) && (
309
+        tag_name=='INPUT' ||
310
+        tag_name=='SELECT' ||
311
+        tag_name=='OPTION' ||
312
+        tag_name=='BUTTON' ||
313
+        tag_name=='TEXTAREA')) return;
314
+        
315
+      var pointer = [Event.pointerX(event), Event.pointerY(event)];
316
+      var pos     = Position.cumulativeOffset(this.element);
317
+      this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) });
318
+      
319
+      Draggables.activate(this);
320
+      Event.stop(event);
321
+    }
322
+  },
323
+  
324
+  startDrag: function(event) {
325
+    this.dragging = true;
326
+    if(!this.delta)
327
+      this.delta = this.currentDelta();
328
+    
329
+    if(this.options.zindex) {
330
+      this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0);
331
+      this.element.style.zIndex = this.options.zindex;
332
+    }
333
+    
334
+    if(this.options.ghosting) {
335
+      this._clone = this.element.cloneNode(true);
336
+      this.element._originallyAbsolute = (this.element.getStyle('position') == 'absolute');
337
+      if (!this.element._originallyAbsolute)
338
+        Position.absolutize(this.element);
339
+      this.element.parentNode.insertBefore(this._clone, this.element);
340
+    }
341
+    
342
+    if(this.options.scroll) {
343
+      if (this.options.scroll == window) {
344
+        var where = this._getWindowScroll(this.options.scroll);
345
+        this.originalScrollLeft = where.left;
346
+        this.originalScrollTop = where.top;
347
+      } else {
348
+        this.originalScrollLeft = this.options.scroll.scrollLeft;
349
+        this.originalScrollTop = this.options.scroll.scrollTop;
350
+      }
351
+    }
352
+    
353
+    Draggables.notify('onStart', this, event);
354
+        
355
+    if(this.options.starteffect) this.options.starteffect(this.element);
356
+  },
357
+  
358
+  updateDrag: function(event, pointer) {
359
+    if(!this.dragging) this.startDrag(event);
360
+    
361
+    if(!this.options.quiet){
362
+      Position.prepare();
363
+      Droppables.show(pointer, this.element);
364
+    }
365
+    
366
+    Draggables.notify('onDrag', this, event);
367
+    
368
+    this.draw(pointer);
369
+    if(this.options.change) this.options.change(this);
370
+    
371
+    if(this.options.scroll) {
372
+      this.stopScrolling();
373
+      
374
+      var p;
375
+      if (this.options.scroll == window) {
376
+        with(this._getWindowScroll(this.options.scroll)) { p = [ left, top, left+width, top+height ]; }
377
+      } else {
378
+        p = Position.page(this.options.scroll);
379
+        p[0] += this.options.scroll.scrollLeft + Position.deltaX;
380
+        p[1] += this.options.scroll.scrollTop + Position.deltaY;
381
+        p.push(p[0]+this.options.scroll.offsetWidth);
382
+        p.push(p[1]+this.options.scroll.offsetHeight);
383
+      }
384
+      var speed = [0,0];
385
+      if(pointer[0] < (p[0]+this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[0]+this.options.scrollSensitivity);
386
+      if(pointer[1] < (p[1]+this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[1]+this.options.scrollSensitivity);
387
+      if(pointer[0] > (p[2]-this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[2]-this.options.scrollSensitivity);
388
+      if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity);
389
+      this.startScrolling(speed);
390
+    }
391
+    
392
+    // fix AppleWebKit rendering
393
+    if(Prototype.Browser.WebKit) window.scrollBy(0,0);
394
+    
395
+    Event.stop(event);
396
+  },
397
+  
398
+  finishDrag: function(event, success) {
399
+    this.dragging = false;
400
+    
401
+    if(this.options.quiet){
402
+      Position.prepare();
403
+      var pointer = [Event.pointerX(event), Event.pointerY(event)];
404
+      Droppables.show(pointer, this.element);
405
+    }
406
+
407
+    if(this.options.ghosting) {
408
+      if (!this.element._originallyAbsolute)
409
+        Position.relativize(this.element);
410
+      delete this.element._originallyAbsolute;
411
+      Element.remove(this._clone);
412
+      this._clone = null;
413
+    }
414
+
415
+    var dropped = false; 
416
+    if(success) { 
417
+      dropped = Droppables.fire(event, this.element); 
418
+      if (!dropped) dropped = false; 
419
+    }
420
+    if(dropped && this.options.onDropped) this.options.onDropped(this.element);
421
+    Draggables.notify('onEnd', this, event);
422
+
423
+    var revert = this.options.revert;
424
+    if(revert && Object.isFunction(revert)) revert = revert(this.element);
425
+    
426
+    var d = this.currentDelta();
427
+    if(revert && this.options.reverteffect) {
428
+      if (dropped == 0 || revert != 'failure')
429
+        this.options.reverteffect(this.element,
430
+          d[1]-this.delta[1], d[0]-this.delta[0]);
431
+    } else {
432
+      this.delta = d;
433
+    }
434
+
435
+    if(this.options.zindex)
436
+      this.element.style.zIndex = this.originalZ;
437
+
438
+    if(this.options.endeffect) 
439
+      this.options.endeffect(this.element);
440
+      
441
+    Draggables.deactivate(this);
442
+    Droppables.reset();
443
+  },
444
+  
445
+  keyPress: function(event) {
446
+    if(event.keyCode!=Event.KEY_ESC) return;
447
+    this.finishDrag(event, false);
448
+    Event.stop(event);
449
+  },
450
+  
451
+  endDrag: function(event) {
452
+    if(!this.dragging) return;
453
+    this.stopScrolling();
454
+    this.finishDrag(event, true);
455
+    Event.stop(event);
456
+  },
457
+  
458
+  draw: function(point) {
459
+    var pos = Position.cumulativeOffset(this.element);
460
+    if(this.options.ghosting) {
461
+      var r   = Position.realOffset(this.element);
462
+      pos[0] += r[0] - Position.deltaX; pos[1] += r[1] - Position.deltaY;
463
+    }
464
+    
465
+    var d = this.currentDelta();
466
+    pos[0] -= d[0]; pos[1] -= d[1];
467
+    
468
+    if(this.options.scroll && (this.options.scroll != window && this._isScrollChild)) {
469
+      pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft;
470
+      pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop;
471
+    }
472
+    
473
+    var p = [0,1].map(function(i){ 
474
+      return (point[i]-pos[i]-this.offset[i]) 
475
+    }.bind(this));
476
+    
477
+    if(this.options.snap) {
478
+      if(Object.isFunction(this.options.snap)) {
479
+        p = this.options.snap(p[0],p[1],this);
480
+      } else {
481
+      if(Object.isArray(this.options.snap)) {
482
+        p = p.map( function(v, i) {
483
+          return (v/this.options.snap[i]).round()*this.options.snap[i] }.bind(this))
484
+      } else {
485
+        p = p.map( function(v) {
486
+          return (v/this.options.snap).round()*this.options.snap }.bind(this))
487
+      }
488
+    }}
489
+    
490
+    var style = this.element.style;
491
+    if((!this.options.constraint) || (this.options.constraint=='horizontal'))
492
+      style.left = p[0] + "px";
493
+    if((!this.options.constraint) || (this.options.constraint=='vertical'))
494
+      style.top  = p[1] + "px";
495
+    
496
+    if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering
497
+  },
498
+  
499
+  stopScrolling: function() {
500
+    if(this.scrollInterval) {
501
+      clearInterval(this.scrollInterval);
502
+      this.scrollInterval = null;
503
+      Draggables._lastScrollPointer = null;
504
+    }
505
+  },
506
+  
507
+  startScrolling: function(speed) {
508
+    if(!(speed[0] || speed[1])) return;
509
+    this.scrollSpeed = [speed[0]*this.options.scrollSpeed,speed[1]*this.options.scrollSpeed];
510
+    this.lastScrolled = new Date();
511
+    this.scrollInterval = setInterval(this.scroll.bind(this), 10);
512
+  },
513
+  
514
+  scroll: function() {
515
+    var current = new Date();
516
+    var delta = current - this.lastScrolled;
517
+    this.lastScrolled = current;
518
+    if(this.options.scroll == window) {
519
+      with (this._getWindowScroll(this.options.scroll)) {
520
+        if (this.scrollSpeed[0] || this.scrollSpeed[1]) {
521
+          var d = delta / 1000;
522
+          this.options.scroll.scrollTo( left + d*this.scrollSpeed[0], top + d*this.scrollSpeed[1] );
523
+        }
524
+      }
525
+    } else {
526
+      this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000;
527
+      this.options.scroll.scrollTop  += this.scrollSpeed[1] * delta / 1000;
528
+    }
529
+    
530
+    Position.prepare();
531
+    Droppables.show(Draggables._lastPointer, this.element);
532
+    Draggables.notify('onDrag', this);
533
+    if (this._isScrollChild) {
534
+      Draggables._lastScrollPointer = Draggables._lastScrollPointer || $A(Draggables._lastPointer);
535
+      Draggables._lastScrollPointer[0] += this.scrollSpeed[0] * delta / 1000;
536
+      Draggables._lastScrollPointer[1] += this.scrollSpeed[1] * delta / 1000;
537
+      if (Draggables._lastScrollPointer[0] < 0)
538
+        Draggables._lastScrollPointer[0] = 0;
539
+      if (Draggables._lastScrollPointer[1] < 0)
540
+        Draggables._lastScrollPointer[1] = 0;
541
+      this.draw(Draggables._lastScrollPointer);
542
+    }
543
+    
544
+    if(this.options.change) this.options.change(this);
545
+  },
546
+  
547
+  _getWindowScroll: function(w) {
548
+    var T, L, W, H;
549
+    with (w.document) {
550
+      if (w.document.documentElement && documentElement.scrollTop) {
551
+        T = documentElement.scrollTop;
552
+        L = documentElement.scrollLeft;
553
+      } else if (w.document.body) {
554
+        T = body.scrollTop;
555
+        L = body.scrollLeft;
556
+      }
557
+      if (w.innerWidth) {
558
+        W = w.innerWidth;
559
+        H = w.innerHeight;
560
+      } else if (w.document.documentElement && documentElement.clientWidth) {
561
+        W = documentElement.clientWidth;
562
+        H = documentElement.clientHeight;
563
+      } else {
564
+        W = body.offsetWidth;
565
+        H = body.offsetHeight
566
+      }
567
+    }
568
+    return { top: T, left: L, width: W, height: H };
569
+  }
570
+});
571
+
572
+Draggable._dragging = { };
573
+
574
+/*--------------------------------------------------------------------------*/
575
+
576
+var SortableObserver = Class.create({
577
+  initialize: function(element, observer) {
578
+    this.element   = $(element);
579
+    this.observer  = observer;
580
+    this.lastValue = Sortable.serialize(this.element);
581
+  },
582
+  
583
+  onStart: function() {
584
+    this.lastValue = Sortable.serialize(this.element);
585
+  },
586
+  
587
+  onEnd: function() {
588
+    Sortable.unmark();
589
+    if(this.lastValue != Sortable.serialize(this.element))
590
+      this.observer(this.element)
591
+  }
592
+});
593
+
594
+var Sortable = {
595
+  SERIALIZE_RULE: /^[^_\-](?:[A-Za-z0-9\-\_]*)[_](.*)$/,
596
+  
597
+  sortables: { },
598
+  
599
+  _findRootElement: function(element) {
600
+    while (element.tagName.toUpperCase() != "BODY") {  
601
+      if(element.id && Sortable.sortables[element.id]) return element;
602
+      element = element.parentNode;
603
+    }
604
+  },
605
+
606
+  options: function(element) {
607
+    element = Sortable._findRootElement($(element));
608
+    if(!element) return;
609
+    return Sortable.sortables[element.id];
610
+  },
611
+  
612
+  destroy: function(element){
613
+    var s = Sortable.options(element);
614
+    
615
+    if(s) {
616
+      Draggables.removeObserver(s.element);
617
+      s.droppables.each(function(d){ Droppables.remove(d) });
618
+      s.draggables.invoke('destroy');
619
+      
620
+      delete Sortable.sortables[s.element.id];
621
+    }
622
+  },
623
+
624
+  create: function(element) {
625
+    element = $(element);
626
+    var options = Object.extend({ 
627
+      element:     element,
628
+      tag:         'li',       // assumes li children, override with tag: 'tagname'
629
+      dropOnEmpty: false,
630
+      tree:        false,
631
+      treeTag:     'ul',
632
+      overlap:     'vertical', // one of 'vertical', 'horizontal'
633
+      constraint:  'vertical', // one of 'vertical', 'horizontal', false
634
+      containment: element,    // also takes array of elements (or id's); or false
635
+      handle:      false,      // or a CSS class
636
+      only:        false,
637
+      delay:       0,
638
+      hoverclass:  null,
639
+      ghosting:    false,
640
+      quiet:       false, 
641
+      scroll:      false,
642
+      scrollSensitivity: 20,
643
+      scrollSpeed: 15,
644
+      format:      this.SERIALIZE_RULE,
645
+      
646
+      // these take arrays of elements or ids and can be 
647
+      // used for better initialization performance
648
+      elements:    false,
649
+      handles:     false,
650
+      
651
+      onChange:    Prototype.emptyFunction,
652
+      onUpdate:    Prototype.emptyFunction
653
+    }, arguments[1] || { });
654
+
655
+    // clear any old sortable with same element
656
+    this.destroy(element);
657
+
658
+    // build options for the draggables
659
+    var options_for_draggable = {
660
+      revert:      true,
661
+      quiet:       options.quiet,
662
+      scroll:      options.scroll,
663
+      scrollSpeed: options.scrollSpeed,
664
+      scrollSensitivity: options.scrollSensitivity,
665
+      delay:       options.delay,
666
+      ghosting:    options.ghosting,
667
+      constraint:  options.constraint,
668
+      handle:      options.handle };
669
+
670
+    if(options.starteffect)
671
+      options_for_draggable.starteffect = options.starteffect;
672
+
673
+    if(options.reverteffect)
674
+      options_for_draggable.reverteffect = options.reverteffect;
675
+    else
676
+      if(options.ghosting) options_for_draggable.reverteffect = function(element) {
677
+        element.style.top  = 0;
678
+        element.style.left = 0;
679
+      };
680
+
681
+    if(options.endeffect)
682
+      options_for_draggable.endeffect = options.endeffect;
683
+
684
+    if(options.zindex)
685
+      options_for_draggable.zindex = options.zindex;
686
+
687
+    // build options for the droppables  
688
+    var options_for_droppable = {
689
+      overlap:     options.overlap,
690
+      containment: options.containment,
691
+      tree:        options.tree,
692
+      hoverclass:  options.hoverclass,
693
+      onHover:     Sortable.onHover
694
+    }
695
+    
696
+    var options_for_tree = {
697
+      onHover:      Sortable.onEmptyHover,
698
+      overlap:      options.overlap,
699
+      containment:  options.containment,
700
+      hoverclass:   options.hoverclass
701
+    }
702
+
703
+    // fix for gecko engine
704
+    Element.cleanWhitespace(element); 
705
+
706
+    options.draggables = [];
707
+    options.droppables = [];
708
+
709
+    // drop on empty handling
710
+    if(options.dropOnEmpty || options.tree) {
711
+      Droppables.add(element, options_for_tree);
712
+      options.droppables.push(element);
713
+    }
714
+
715
+    (options.elements || this.findElements(element, options) || []).each( function(e,i) {
716
+      var handle = options.handles ? $(options.handles[i]) :
717
+        (options.handle ? $(e).select('.' + options.handle)[0] : e); 
718
+      options.draggables.push(
719
+        new Draggable(e, Object.extend(options_for_draggable, { handle: handle })));
720
+      Droppables.add(e, options_for_droppable);
721
+      if(options.tree) e.treeNode = element;
722
+      options.droppables.push(e);      
723
+    });
724
+    
725
+    if(options.tree) {
726
+      (Sortable.findTreeElements(element, options) || []).each( function(e) {
727
+        Droppables.add(e, options_for_tree);
728
+        e.treeNode = element;
729
+        options.droppables.push(e);
730
+      });
731
+    }
732
+
733
+    // keep reference
734
+    this.sortables[element.id] = options;
735
+
736
+    // for onupdate
737
+    Draggables.addObserver(new SortableObserver(element, options.onUpdate));
738
+
739
+  },
740
+
741
+  // return all suitable-for-sortable elements in a guaranteed order
742
+  findElements: function(element, options) {
743
+    return Element.findChildren(
744
+      element, options.only, options.tree ? true : false, options.tag);
745
+  },
746
+  
747
+  findTreeElements: function(element, options) {
748
+    return Element.findChildren(
749
+      element, options.only, options.tree ? true : false, options.treeTag);
750
+  },
751
+
752
+  onHover: function(element, dropon, overlap) {
753
+    if(Element.isParent(dropon, element)) return;
754
+
755
+    if(overlap > .33 && overlap < .66 && Sortable.options(dropon).tree) {
756
+      return;
757
+    } else if(overlap>0.5) {
758
+      Sortable.mark(dropon, 'before');
759
+      if(dropon.previousSibling != element) {
760
+        var oldParentNode = element.parentNode;
761
+        element.style.visibility = "hidden"; // fix gecko rendering
762
+        dropon.parentNode.insertBefore(element, dropon);
763
+        if(dropon.parentNode!=oldParentNode) 
764
+          Sortable.options(oldParentNode).onChange(element);
765
+        Sortable.options(dropon.parentNode).onChange(element);
766
+      }
767
+    } else {
768
+      Sortable.mark(dropon, 'after');
769
+      var nextElement = dropon.nextSibling || null;
770
+      if(nextElement != element) {
771
+        var oldParentNode = element.parentNode;
772
+        element.style.visibility = "hidden"; // fix gecko rendering
773
+        dropon.parentNode.insertBefore(element, nextElement);
774
+        if(dropon.parentNode!=oldParentNode) 
775
+          Sortable.options(oldParentNode).onChange(element);
776
+        Sortable.options(dropon.parentNode).onChange(element);
777
+      }
778
+    }
779
+  },
780
+  
781
+  onEmptyHover: function(element, dropon, overlap) {
782
+    var oldParentNode = element.parentNode;
783
+    var droponOptions = Sortable.options(dropon);
784
+        
785
+    if(!Element.isParent(dropon, element)) {
786
+      var index;
787
+      
788
+      var children = Sortable.findElements(dropon, {tag: droponOptions.tag, only: droponOptions.only});
789
+      var child = null;
790
+            
791
+      if(children) {
792
+        var offset = Element.offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap);
793
+        
794
+        for (index = 0; index < children.length; index += 1) {
795
+          if (offset - Element.offsetSize (children[index], droponOptions.overlap) >= 0) {
796
+            offset -= Element.offsetSize (children[index], droponOptions.overlap);
797
+          } else if (offset - (Element.offsetSize (children[index], droponOptions.overlap) / 2) >= 0) {
798
+            child = index + 1 < children.length ? children[index + 1] : null;
799
+            break;
800
+          } else {
801
+            child = children[index];
802
+            break;
803
+          }
804
+        }
805
+      }
806
+      
807
+      dropon.insertBefore(element, child);
808
+      
809
+      Sortable.options(oldParentNode).onChange(element);
810
+      droponOptions.onChange(element);
811
+    }
812
+  },
813
+
814
+  unmark: function() {
815
+    if(Sortable._marker) Sortable._marker.hide();
816
+  },
817
+
818
+  mark: function(dropon, position) {
819
+    // mark on ghosting only
820
+    var sortable = Sortable.options(dropon.parentNode);
821
+    if(sortable && !sortable.ghosting) return; 
822
+
823
+    if(!Sortable._marker) {
824
+      Sortable._marker = 
825
+        ($('dropmarker') || Element.extend(document.createElement('DIV'))).
826
+          hide().addClassName('dropmarker').setStyle({position:'absolute'});
827
+      document.getElementsByTagName("body").item(0).appendChild(Sortable._marker);
828
+    }    
829
+    var offsets = Position.cumulativeOffset(dropon);
830
+    Sortable._marker.setStyle({left: offsets[0]+'px', top: offsets[1] + 'px'});
831
+    
832
+    if(position=='after')
833
+      if(sortable.overlap == 'horizontal') 
834
+        Sortable._marker.setStyle({left: (offsets[0]+dropon.clientWidth) + 'px'});
835
+      else
836
+        Sortable._marker.setStyle({top: (offsets[1]+dropon.clientHeight) + 'px'});
837
+    
838
+    Sortable._marker.show();
839
+  },
840
+  
841
+  _tree: function(element, options, parent) {
842
+    var children = Sortable.findElements(element, options) || [];
843
+  
844
+    for (var i = 0; i < children.length; ++i) {
845
+      var match = children[i].id.match(options.format);
846
+
847
+      if (!match) continue;
848
+      
849
+      var child = {
850
+        id: encodeURIComponent(match ? match[1] : null),
851
+        element: element,
852
+        parent: parent,
853
+        children: [],
854
+        position: parent.children.length,
855
+        container: $(children[i]).down(options.treeTag)
856
+      }
857
+      
858
+      /* Get the element containing the children and recurse over it */
859
+      if (child.container)
860
+        this._tree(child.container, options, child)
861
+      
862
+      parent.children.push (child);
863
+    }
864
+
865
+    return parent; 
866
+  },
867
+
868
+  tree: function(element) {
869
+    element = $(element);
870
+    var sortableOptions = this.options(element);
871
+    var options = Object.extend({
872
+      tag: sortableOptions.tag,
873
+      treeTag: sortableOptions.treeTag,
874
+      only: sortableOptions.only,
875
+      name: element.id,
876
+      format: sortableOptions.format
877
+    }, arguments[1] || { });
878
+    
879
+    var root = {
880
+      id: null,
881
+      parent: null,
882
+      children: [],
883
+      container: element,
884
+      position: 0
885
+    }
886
+    
887
+    return Sortable._tree(element, options, root);
888
+  },
889
+
890
+  /* Construct a [i] index for a particular node */
891
+  _constructIndex: function(node) {
892
+    var index = '';
893
+    do {
894
+      if (node.id) index = '[' + node.position + ']' + index;
895
+    } while ((node = node.parent) != null);
896
+    return index;
897
+  },
898
+
899
+  sequence: function(element) {
900
+    element = $(element);
901
+    var options = Object.extend(this.options(element), arguments[1] || { });
902
+    
903
+    return $(this.findElements(element, options) || []).map( function(item) {
904
+      return item.id.match(options.format) ? item.id.match(options.format)[1] : '';
905
+    });
906
+  },
907
+
908
+  setSequence: function(element, new_sequence) {
909
+    element = $(element);
910
+    var options = Object.extend(this.options(element), arguments[2] || { });
911
+    
912
+    var nodeMap = { };
913
+    this.findElements(element, options).each( function(n) {
914
+        if (n.id.match(options.format))
915
+            nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode];
916
+        n.parentNode.removeChild(n);
917
+    });
918
+   
919
+    new_sequence.each(function(ident) {
920
+      var n = nodeMap[ident];
921
+      if (n) {
922
+        n[1].appendChild(n[0]);
923
+        delete nodeMap[ident];
924
+      }
925
+    });
926
+  },
927
+  
928
+  serialize: function(element) {
929
+    element = $(element);
930
+    var options = Object.extend(Sortable.options(element), arguments[1] || { });
931
+    var name = encodeURIComponent(
932
+      (arguments[1] && arguments[1].name) ? arguments[1].name : element.id);
933
+    
934
+    if (options.tree) {
935
+      return Sortable.tree(element, arguments[1]).children.map( function (item) {
936
+        return [name + Sortable._constructIndex(item) + "[id]=" + 
937
+                encodeURIComponent(item.id)].concat(item.children.map(arguments.callee));
938
+      }).flatten().join('&');
939
+    } else {
940
+      return Sortable.sequence(element, arguments[1]).map( function(item) {
941
+        return name + "[]=" + encodeURIComponent(item);
942
+      }).join('&');
943
+    }
944
+  }
945
+}
946
+
947
+// Returns true if child is contained within element
948
+Element.isParent = function(child, element) {
949
+  if (!child.parentNode || child == element) return false;
950
+  if (child.parentNode == element) return true;
951
+  return Element.isParent(child.parentNode, element);
952
+}
953
+
954
+Element.findChildren = function(element, only, recursive, tagName) {   
955
+  if(!element.hasChildNodes()) return null;
956
+  tagName = tagName.toUpperCase();
957
+  if(only) only = [only].flatten();
958
+  var elements = [];
959
+  $A(element.childNodes).each( function(e) {
960
+    if(e.tagName && e.tagName.toUpperCase()==tagName &&
961
+      (!only || (Element.classNames(e).detect(function(v) { return only.include(v) }))))
962
+        elements.push(e);
963
+    if(recursive) {
964
+      var grandchildren = Element.findChildren(e, only, recursive, tagName);
965
+      if(grandchildren) elements.push(grandchildren);
966
+    }
967
+  });
968
+
969
+  return (elements.length>0 ? elements.flatten() : []);
970
+}
971
+
972
+Element.offsetSize = function (element, type) {
973
+  return element['offset' + ((type=='vertical' || type=='height') ? 'Height' : 'Width')];
974
+}

+ 1122
- 0
src/com/dmdirc/addons/ui_web/res/javascript/effects.js
File diff suppressed because it is too large
View File


+ 4170
- 0
src/com/dmdirc/addons/ui_web/res/javascript/prototype.js
File diff suppressed because it is too large
View File


+ 58
- 0
src/com/dmdirc/addons/ui_web/res/javascript/scriptaculous.js View File

@@ -0,0 +1,58 @@
1
+// script.aculo.us scriptaculous.js v1.8.1, Thu Jan 03 22:07:12 -0500 2008
2
+
3
+// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
4
+// 
5
+// Permission is hereby granted, free of charge, to any person obtaining
6
+// a copy of this software and associated documentation files (the
7
+// "Software"), to deal in the Software without restriction, including
8
+// without limitation the rights to use, copy, modify, merge, publish,
9
+// distribute, sublicense, and/or sell copies of the Software, and to
10
+// permit persons to whom the Software is furnished to do so, subject to
11
+// the following conditions:
12
+// 
13
+// The above copyright notice and this permission notice shall be
14
+// included in all copies or substantial portions of the Software.
15
+//
16
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
+//
24
+// For details, see the script.aculo.us web site: http://script.aculo.us/
25
+
26
+var Scriptaculous = {
27
+  Version: '1.8.1',
28
+  require: function(libraryName) {
29
+    // inserting via DOM fails in Safari 2.0, so brute force approach
30
+    document.write('<script type="text/javascript" src="'+libraryName+'"><\/script>');
31
+  },
32
+  REQUIRED_PROTOTYPE: '1.6.0',
33
+  load: function() {
34
+    function convertVersionString(versionString){
35
+      var r = versionString.split('.');
36
+      return parseInt(r[0])*100000 + parseInt(r[1])*1000 + parseInt(r[2]);
37
+    }
38
+ 
39
+    if((typeof Prototype=='undefined') || 
40
+       (typeof Element == 'undefined') || 
41
+       (typeof Element.Methods=='undefined') ||
42
+       (convertVersionString(Prototype.Version) < 
43
+        convertVersionString(Scriptaculous.REQUIRED_PROTOTYPE)))
44
+       throw("script.aculo.us requires the Prototype JavaScript framework >= " +
45
+        Scriptaculous.REQUIRED_PROTOTYPE);
46
+    
47
+    $A(document.getElementsByTagName("script")).findAll( function(s) {
48
+      return (s.src && s.src.match(/scriptaculous\.js(\?.*)?$/))
49
+    }).each( function(s) {
50
+      var path = s.src.replace(/scriptaculous\.js(\?.*)?$/,'');
51
+      var includes = s.src.match(/\?.*load=([a-z,]*)/);
52
+      (includes ? includes[1] : 'builder,effects,dragdrop,controls,slider,sound').split(',').each(
53
+       function(include) { Scriptaculous.require(path+include+'.js') });
54
+    });
55
+  }
56
+}
57
+
58
+Scriptaculous.load();

+ 275
- 0
src/com/dmdirc/addons/ui_web/res/javascript/slider.js View File

@@ -0,0 +1,275 @@
1
+// script.aculo.us slider.js v1.8.1, Thu Jan 03 22:07:12 -0500 2008
2
+
3
+// Copyright (c) 2005-2007 Marty Haught, Thomas Fuchs 
4
+//
5
+// script.aculo.us is freely distributable under the terms of an MIT-style license.
6
+// For details, see the script.aculo.us web site: http://script.aculo.us/
7
+
8
+if (!Control) var Control = { };
9
+
10
+// options:
11
+//  axis: 'vertical', or 'horizontal' (default)
12
+//
13
+// callbacks:
14
+//  onChange(value)
15
+//  onSlide(value)
16
+Control.Slider = Class.create({
17
+  initialize: function(handle, track, options) {
18
+    var slider = this;
19
+    
20
+    if (Object.isArray(handle)) {
21
+      this.handles = handle.collect( function(e) { return $(e) });
22
+    } else {
23
+      this.handles = [$(handle)];
24
+    }
25
+    
26
+    this.track   = $(track);
27
+    this.options = options || { };
28
+
29
+    this.axis      = this.options.axis || 'horizontal';
30
+    this.increment = this.options.increment || 1;
31
+    this.step      = parseInt(this.options.step || '1');
32
+    this.range     = this.options.range || $R(0,1);
33
+    
34
+    this.value     = 0; // assure backwards compat
35
+    this.values    = this.handles.map( function() { return 0 });
36
+    this.spans     = this.options.spans ? this.options.spans.map(function(s){ return $(s) }) : false;
37
+    this.options.startSpan = $(this.options.startSpan || null);
38
+    this.options.endSpan   = $(this.options.endSpan || null);
39
+
40
+    this.restricted = this.options.restricted || false;
41
+
42
+    this.maximum   = this.options.maximum || this.range.end;
43
+    this.minimum   = this.options.minimum || this.range.start;
44
+
45
+    // Will be used to align the handle onto the track, if necessary
46
+    this.alignX = parseInt(this.options.alignX || '0');
47
+    this.alignY = parseInt(this.options.alignY || '0');
48
+    
49
+    this.trackLength = this.maximumOffset() - this.minimumOffset();
50
+
51
+    this.handleLength = this.isVertical() ? 
52
+      (this.handles[0].offsetHeight != 0 ? 
53
+        this.handles[0].offsetHeight : this.handles[0].style.height.replace(/px$/,"")) : 
54
+      (this.handles[0].offsetWidth != 0 ? this.handles[0].offsetWidth : 
55
+        this.handles[0].style.width.replace(/px$/,""));
56
+
57
+    this.active   = false;
58
+    this.dragging = false;
59
+    this.disabled = false;
60
+
61
+    if (this.options.disabled) this.setDisabled();
62
+
63
+    // Allowed values array
64
+    this.allowedValues = this.options.values ? this.options.values.sortBy(Prototype.K) : false;
65
+    if (this.allowedValues) {
66
+      this.minimum = this.allowedValues.min();
67
+      this.maximum = this.allowedValues.max();
68
+    }
69
+
70
+    this.eventMouseDown = this.startDrag.bindAsEventListener(this);
71
+    this.eventMouseUp   = this.endDrag.bindAsEventListener(this);
72
+    this.eventMouseMove = this.update.bindAsEventListener(this);
73
+
74
+    // Initialize handles in reverse (make sure first handle is active)
75
+    this.handles.each( function(h,i) {
76
+      i = slider.handles.length-1-i;
77
+      slider.setValue(parseFloat(
78
+        (Object.isArray(slider.options.sliderValue) ? 
79
+          slider.options.sliderValue[i] : slider.options.sliderValue) || 
80
+         slider.range.start), i);
81
+      h.makePositioned().observe("mousedown", slider.eventMouseDown);
82
+    });
83
+    
84
+    this.track.observe("mousedown", this.eventMouseDown);
85
+    document.observe("mouseup", this.eventMouseUp);
86
+    document.observe("mousemove", this.eventMouseMove);
87
+    
88
+    this.initialized = true;
89
+  },
90
+  dispose: function() {
91
+    var slider = this;    
92
+    Event.stopObserving(this.track, "mousedown", this.eventMouseDown);
93
+    Event.stopObserving(document, "mouseup", this.eventMouseUp);
94
+    Event.stopObserving(document, "mousemove", this.eventMouseMove);
95
+    this.handles.each( function(h) {
96
+      Event.stopObserving(h, "mousedown", slider.eventMouseDown);
97
+    });
98
+  },
99
+  setDisabled: function(){
100
+    this.disabled = true;
101
+  },
102
+  setEnabled: function(){
103
+    this.disabled = false;
104
+  },  
105
+  getNearestValue: function(value){
106
+    if (this.allowedValues){
107
+      if (value >= this.allowedValues.max()) return(this.allowedValues.max());
108
+      if (value <= this.allowedValues.min()) return(this.allowedValues.min());
109
+      
110
+      var offset = Math.abs(this.allowedValues[0] - value);
111
+      var newValue = this.allowedValues[0];
112
+      this.allowedValues.each( function(v) {
113
+        var currentOffset = Math.abs(v - value);
114
+        if (currentOffset <= offset){
115
+          newValue = v;
116
+          offset = currentOffset;
117
+        } 
118
+      });
119
+      return newValue;
120
+    }
121
+    if (value > this.range.end) return this.range.end;
122
+    if (value < this.range.start) return this.range.start;
123
+    return value;
124
+  },
125
+  setValue: function(sliderValue, handleIdx){
126
+    if (!this.active) {
127
+      this.activeHandleIdx = handleIdx || 0;
128
+      this.activeHandle    = this.handles[this.activeHandleIdx];
129
+      this.updateStyles();
130
+    }
131
+    handleIdx = handleIdx || this.activeHandleIdx || 0;
132
+    if (this.initialized && this.restricted) {
133
+      if ((handleIdx>0) && (sliderValue<this.values[handleIdx-1]))
134
+        sliderValue = this.values[handleIdx-1];
135
+      if ((handleIdx < (this.handles.length-1)) && (sliderValue>this.values[handleIdx+1]))
136
+        sliderValue = this.values[handleIdx+1];
137
+    }
138
+    sliderValue = this.getNearestValue(sliderValue);
139
+    this.values[handleIdx] = sliderValue;
140
+    this.value = this.values[0]; // assure backwards compat
141
+    
142
+    this.handles[handleIdx].style[this.isVertical() ? 'top' : 'left'] = 
143
+      this.translateToPx(sliderValue);
144
+    
145
+    this.drawSpans();
146
+    if (!this.dragging || !this.event) this.updateFinished();
147
+  },
148
+  setValueBy: function(delta, handleIdx) {
149
+    this.setValue(this.values[handleIdx || this.activeHandleIdx || 0] + delta, 
150
+      handleIdx || this.activeHandleIdx || 0);
151
+  },
152
+  translateToPx: function(value) {
153
+    return Math.round(
154
+      ((this.trackLength-this.handleLength)/(this.range.end-this.range.start)) * 
155
+      (value - this.range.start)) + "px";
156
+  },
157
+  translateToValue: function(offset) {
158
+    return ((offset/(this.trackLength-this.handleLength) * 
159
+      (this.range.end-this.range.start)) + this.range.start);
160
+  },
161
+  getRange: function(range) {
162
+    var v = this.values.sortBy(Prototype.K); 
163
+    range = range || 0;
164
+    return $R(v[range],v[range+1]);
165
+  },
166
+  minimumOffset: function(){
167
+    return(this.isVertical() ? this.alignY : this.alignX);
168
+  },
169
+  maximumOffset: function(){
170
+    return(this.isVertical() ? 
171
+      (this.track.offsetHeight != 0 ? this.track.offsetHeight :
172
+        this.track.style.height.replace(/px$/,"")) - this.alignY : 
173
+      (this.track.offsetWidth != 0 ? this.track.offsetWidth : 
174
+        this.track.style.width.replace(/px$/,"")) - this.alignX);
175
+  },  
176
+  isVertical:  function(){
177
+    return (this.axis == 'vertical');
178
+  },
179
+  drawSpans: function() {
180
+    var slider = this;
181
+    if (this.spans)
182
+      $R(0, this.spans.length-1).each(function(r) { slider.setSpan(slider.spans[r], slider.getRange(r)) });
183
+    if (this.options.startSpan)
184
+      this.setSpan(this.options.startSpan,
185
+        $R(0, this.values.length>1 ? this.getRange(0).min() : this.value ));
186
+    if (this.options.endSpan)
187
+      this.setSpan(this.options.endSpan, 
188
+        $R(this.values.length>1 ? this.getRange(this.spans.length-1).max() : this.value, this.maximum));
189
+  },
190
+  setSpan: function(span, range) {
191
+    if (this.isVertical()) {
192
+      span.style.top = this.translateToPx(range.start);
193
+      span.style.height = this.translateToPx(range.end - range.start + this.range.start);
194
+    } else {
195
+      span.style.left = this.translateToPx(range.start);
196
+      span.style.width = this.translateToPx(range.end - range.start + this.range.start);
197
+    }
198
+  },
199
+  updateStyles: function() {
200
+    this.handles.each( function(h){ Element.removeClassName(h, 'selected') });
201
+    Element.addClassName(this.activeHandle, 'selected');
202
+  },
203
+  startDrag: function(event) {
204
+    if (Event.isLeftClick(event)) {
205
+      if (!this.disabled){
206
+        this.active = true;
207
+        
208
+        var handle = Event.element(event);
209
+        var pointer  = [Event.pointerX(event), Event.pointerY(event)];
210
+        var track = handle;
211
+        if (track==this.track) {
212
+          var offsets  = Position.cumulativeOffset(this.track); 
213
+          this.event = event;
214
+          this.setValue(this.translateToValue( 
215
+           (this.isVertical() ? pointer[1]-offsets[1] : pointer[0]-offsets[0])-(this.handleLength/2)
216
+          ));
217
+          var offsets  = Position.cumulativeOffset(this.activeHandle);
218
+          this.offsetX = (pointer[0] - offsets[0]);
219
+          this.offsetY = (pointer[1] - offsets[1]);
220
+        } else {
221
+          // find the handle (prevents issues with Safari)
222
+          while((this.handles.indexOf(handle) == -1) && handle.parentNode) 
223
+            handle = handle.parentNode;
224
+            
225
+          if (this.handles.indexOf(handle)!=-1) {
226
+            this.activeHandle    = handle;
227
+            this.activeHandleIdx = this.handles.indexOf(this.activeHandle);
228
+            this.updateStyles();
229
+            
230
+            var offsets  = Position.cumulativeOffset(this.activeHandle);
231
+            this.offsetX = (pointer[0] - offsets[0]);
232
+            this.offsetY = (pointer[1] - offsets[1]);
233
+          }
234
+        }
235
+      }
236
+      Event.stop(event);
237
+    }
238
+  },
239
+  update: function(event) {
240
+   if (this.active) {
241
+      if (!this.dragging) this.dragging = true;
242
+      this.draw(event);
243
+      if (Prototype.Browser.WebKit) window.scrollBy(0,0);
244
+      Event.stop(event);
245
+   }
246
+  },
247
+  draw: function(event) {
248
+    var pointer = [Event.pointerX(event), Event.pointerY(event)];
249
+    var offsets = Position.cumulativeOffset(this.track);
250
+    pointer[0] -= this.offsetX + offsets[0];
251
+    pointer[1] -= this.offsetY + offsets[1];
252
+    this.event = event;
253
+    this.setValue(this.translateToValue( this.isVertical() ? pointer[1] : pointer[0] ));
254
+    if (this.initialized && this.options.onSlide)
255
+      this.options.onSlide(this.values.length>1 ? this.values : this.value, this);
256
+  },
257
+  endDrag: function(event) {
258
+    if (this.active && this.dragging) {
259
+      this.finishDrag(event, true);
260
+      Event.stop(event);
261
+    }
262
+    this.active = false;
263
+    this.dragging = false;
264
+  },  
265
+  finishDrag: function(event, success) {
266
+    this.active = false;
267
+    this.dragging = false;
268
+    this.updateFinished();
269
+  },
270
+  updateFinished: function() {
271
+    if (this.initialized && this.options.onChange) 
272
+      this.options.onChange(this.values.length>1 ? this.values : this.value, this);
273
+    this.event = null;
274
+  }
275
+});

+ 55
- 0
src/com/dmdirc/addons/ui_web/res/javascript/sound.js View File

@@ -0,0 +1,55 @@
1
+// script.aculo.us sound.js v1.8.1, Thu Jan 03 22:07:12 -0500 2008
2
+
3
+// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
4
+//
5
+// Based on code created by Jules Gravinese (http://www.webveteran.com/)
6
+//
7
+// script.aculo.us is freely distributable under the terms of an MIT-style license.
8
+// For details, see the script.aculo.us web site: http://script.aculo.us/
9
+
10
+Sound = {
11
+  tracks: {},
12
+  _enabled: true,
13
+  template:
14
+    new Template('<embed style="height:0" id="sound_#{track}_#{id}" src="#{url}" loop="false" autostart="true" hidden="true"/>'),
15
+  enable: function(){
16
+    Sound._enabled = true;
17
+  },
18
+  disable: function(){
19
+    Sound._enabled = false;
20
+  },
21
+  play: function(url){
22
+    if(!Sound._enabled) return;
23
+    var options = Object.extend({
24
+      track: 'global', url: url, replace: false
25
+    }, arguments[1] || {});
26
+    
27
+    if(options.replace && this.tracks[options.track]) {
28
+      $R(0, this.tracks[options.track].id).each(function(id){
29
+        var sound = $('sound_'+options.track+'_'+id);
30
+        sound.Stop && sound.Stop();
31
+        sound.remove();
32
+      })
33
+      this.tracks[options.track] = null;
34
+    }
35
+      
36
+    if(!this.tracks[options.track])
37
+      this.tracks[options.track] = { id: 0 }
38
+    else
39
+      this.tracks[options.track].id++;
40
+      
41
+    options.id = this.tracks[options.track].id;
42
+    $$('body')[0].insert( 
43
+      Prototype.Browser.IE ? new Element('bgsound',{
44
+        id: 'sound_'+options.track+'_'+options.id,
45
+        src: options.url, loop: 1, autostart: true
46
+      }) : Sound.template.evaluate(options));
47
+  }
48
+};
49
+
50
+if(Prototype.Browser.Gecko && navigator.userAgent.indexOf("Win") > 0){
51
+  if(navigator.plugins && $A(navigator.plugins).detect(function(p){ return p.name.indexOf('QuickTime') != -1 }))
52
+    Sound.template = new Template('<object id="sound_#{track}_#{id}" width="0" height="0" type="audio/mpeg" data="#{url}"/>')
53
+  else
54
+    Sound.play = function(){}
55
+}

+ 568
- 0
src/com/dmdirc/addons/ui_web/res/javascript/unittest.js View File

@@ -0,0 +1,568 @@
1
+// script.aculo.us unittest.js v1.8.1, Thu Jan 03 22:07:12 -0500 2008
2
+
3
+// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
4
+//           (c) 2005-2007 Jon Tirsen (http://www.tirsen.com)
5
+//           (c) 2005-2007 Michael Schuerig (http://www.schuerig.de/michael/)
6
+//
7
+// script.aculo.us is freely distributable under the terms of an MIT-style license.
8
+// For details, see the script.aculo.us web site: http://script.aculo.us/
9
+
10
+// experimental, Firefox-only
11
+Event.simulateMouse = function(element, eventName) {
12
+  var options = Object.extend({
13
+    pointerX: 0,
14
+    pointerY: 0,
15
+    buttons:  0,
16
+    ctrlKey:  false,
17
+    altKey:   false,
18
+    shiftKey: false,
19
+    metaKey:  false
20
+  }, arguments[2] || {});
21
+  var oEvent = document.createEvent("MouseEvents");
22
+  oEvent.initMouseEvent(eventName, true, true, document.defaultView, 
23
+    options.buttons, options.pointerX, options.pointerY, options.pointerX, options.pointerY, 
24
+    options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, 0, $(element));
25
+  
26
+  if(this.mark) Element.remove(this.mark);
27
+  this.mark = document.createElement('div');
28
+  this.mark.appendChild(document.createTextNode(" "));
29
+  document.body.appendChild(this.mark);
30
+  this.mark.style.position = 'absolute';
31
+  this.mark.style.top = options.pointerY + "px";
32
+  this.mark.style.left = options.pointerX + "px";
33
+  this.mark.style.width = "5px";
34
+  this.mark.style.height = "5px;";
35
+  this.mark.style.borderTop = "1px solid red;"
36
+  this.mark.style.borderLeft = "1px solid red;"
37
+  
38
+  if(this.step)
39
+    alert('['+new Date().getTime().toString()+'] '+eventName+'/'+Test.Unit.inspect(options));
40
+  
41
+  $(element).dispatchEvent(oEvent);
42
+};
43
+
44
+// Note: Due to a fix in Firefox 1.0.5/6 that probably fixed "too much", this doesn't work in 1.0.6 or DP2.
45
+// You need to downgrade to 1.0.4 for now to get this working
46
+// See https://bugzilla.mozilla.org/show_bug.cgi?id=289940 for the fix that fixed too much
47
+Event.simulateKey = function(element, eventName) {
48
+  var options = Object.extend({
49
+    ctrlKey: false,
50
+    altKey: false,
51
+    shiftKey: false,
52
+    metaKey: false,
53
+    keyCode: 0,
54
+    charCode: 0
55
+  }, arguments[2] || {});
56
+
57
+  var oEvent = document.createEvent("KeyEvents");
58
+  oEvent.initKeyEvent(eventName, true, true, window, 
59
+    options.ctrlKey, options.altKey, options.shiftKey, options.metaKey,
60
+    options.keyCode, options.charCode );
61
+  $(element).dispatchEvent(oEvent);
62
+};
63
+
64
+Event.simulateKeys = function(element, command) {
65
+  for(var i=0; i<command.length; i++) {
66
+    Event.simulateKey(element,'keypress',{charCode:command.charCodeAt(i)});
67
+  }
68
+};
69
+
70
+var Test = {}
71
+Test.Unit = {};
72
+
73
+// security exception workaround
74
+Test.Unit.inspect = Object.inspect;
75
+
76
+Test.Unit.Logger = Class.create();
77
+Test.Unit.Logger.prototype = {
78
+  initialize: function(log) {
79
+    this.log = $(log);
80
+    if (this.log) {
81
+      this._createLogTable();
82
+    }
83
+  },
84
+  start: function(testName) {
85
+    if (!this.log) return;
86
+    this.testName = testName;
87
+    this.lastLogLine = document.createElement('tr');
88
+    this.statusCell = document.createElement('td');
89
+    this.nameCell = document.createElement('td');
90
+    this.nameCell.className = "nameCell";
91
+    this.nameCell.appendChild(document.createTextNode(testName));
92
+    this.messageCell = document.createElement('td');
93
+    this.lastLogLine.appendChild(this.statusCell);
94
+    this.lastLogLine.appendChild(this.nameCell);
95
+    this.lastLogLine.appendChild(this.messageCell);
96
+    this.loglines.appendChild(this.lastLogLine);
97
+  },
98
+  finish: function(status, summary) {
99
+    if (!this.log) return;
100
+    this.lastLogLine.className = status;
101
+    this.statusCell.innerHTML = status;
102
+    this.messageCell.innerHTML = this._toHTML(summary);
103
+    this.addLinksToResults();
104
+  },
105
+  message: function(message) {
106
+    if (!this.log) return;
107
+    this.messageCell.innerHTML = this._toHTML(message);
108
+  },
109
+  summary: function(summary) {
110
+    if (!this.log) return;
111
+    this.logsummary.innerHTML = this._toHTML(summary);
112
+  },
113
+  _createLogTable: function() {
114
+    this.log.innerHTML =
115
+    '<div id="logsummary"></div>' +
116
+    '<table id="logtable">' +
117
+    '<thead><tr><th>Status</th><th>Test</th><th>Message</th></tr></thead>' +
118
+    '<tbody id="loglines"></tbody>' +
119
+    '</table>';
120
+    this.logsummary = $('logsummary')
121
+    this.loglines = $('loglines');
122
+  },
123
+  _toHTML: function(txt) {
124
+    return txt.escapeHTML().replace(/\n/g,"<br/>");
125
+  },
126
+  addLinksToResults: function(){ 
127
+    $$("tr.failed .nameCell").each( function(td){ // todo: limit to children of this.log
128
+      td.title = "Run only this test"
129
+      Event.observe(td, 'click', function(){ window.location.search = "?tests=" + td.innerHTML;});
130
+    });
131
+    $$("tr.passed .nameCell").each( function(td){ // todo: limit to children of this.log
132
+      td.title = "Run all tests"
133
+      Event.observe(td, 'click', function(){ window.location.search = "";});
134
+    });
135
+  }
136
+}
137
+
138
+Test.Unit.Runner = Class.create();
139
+Test.Unit.Runner.prototype = {
140
+  initialize: function(testcases) {
141
+    this.options = Object.extend({
142
+      testLog: 'testlog'
143
+    }, arguments[1] || {});
144
+    this.options.resultsURL = this.parseResultsURLQueryParameter();
145
+    this.options.tests      = this.parseTestsQueryParameter();
146
+    if (this.options.testLog) {
147
+      this.options.testLog = $(this.options.testLog) || null;
148
+    }
149
+    if(this.options.tests) {
150
+      this.tests = [];
151
+      for(var i = 0; i < this.options.tests.length; i++) {
152
+        if(/^test/.test(this.options.tests[i])) {
153
+          this.tests.push(new Test.Unit.Testcase(this.options.tests[i], testcases[this.options.tests[i]], testcases["setup"], testcases["teardown"]));
154
+        }
155
+      }
156
+    } else {
157
+      if (this.options.test) {
158
+        this.tests = [new Test.Unit.Testcase(this.options.test, testcases[this.options.test], testcases["setup"], testcases["teardown"])];
159
+      } else {
160
+        this.tests = [];
161
+        for(var testcase in testcases) {
162
+          if(/^test/.test(testcase)) {
163
+            this.tests.push(
164
+               new Test.Unit.Testcase(
165
+                 this.options.context ? ' -> ' + this.options.titles[testcase] : testcase, 
166
+                 testcases[testcase], testcases["setup"], testcases["teardown"]
167
+               ));
168
+          }
169
+        }
170
+      }
171
+    }
172
+    this.currentTest = 0;
173
+    this.logger = new Test.Unit.Logger(this.options.testLog);
174
+    setTimeout(this.runTests.bind(this), 1000);
175
+  },
176
+  parseResultsURLQueryParameter: function() {
177
+    return window.location.search.parseQuery()["resultsURL"];
178
+  },
179
+  parseTestsQueryParameter: function(){
180
+    if (window.location.search.parseQuery()["tests"]){
181
+        return window.location.search.parseQuery()["tests"].split(',');
182
+    };
183
+  },
184
+  // Returns:
185
+  //  "ERROR" if there was an error,
186
+  //  "FAILURE" if there was a failure, or
187
+  //  "SUCCESS" if there was neither
188
+  getResult: function() {
189
+    var hasFailure = false;
190
+    for(var i=0;i<this.tests.length;i++) {
191
+      if (this.tests[i].errors > 0) {
192
+        return "ERROR";
193
+      }
194
+      if (this.tests[i].failures > 0) {
195
+        hasFailure = true;
196
+      }
197
+    }
198
+    if (hasFailure) {
199
+      return "FAILURE";
200
+    } else {
201
+      return "SUCCESS";
202
+    }
203
+  },
204
+  postResults: function() {
205
+    if (this.options.resultsURL) {
206
+      new Ajax.Request(this.options.resultsURL, 
207
+        { method: 'get', parameters: 'result=' + this.getResult(), asynchronous: false });
208
+    }
209
+  },
210
+  runTests: function() {
211
+    var test = this.tests[this.currentTest];
212
+    if (!test) {
213
+      // finished!
214
+      this.postResults();
215
+      this.logger.summary(this.summary());
216
+      return;
217
+    }
218
+    if(!test.isWaiting) {
219
+      this.logger.start(test.name);
220
+    }
221
+    test.run();
222
+    if(test.isWaiting) {
223
+      this.logger.message("Waiting for " + test.timeToWait + "ms");
224
+      setTimeout(this.runTests.bind(this), test.timeToWait || 1000);
225
+    } else {
226
+      this.logger.finish(test.status(), test.summary());
227
+      this.currentTest++;
228
+      // tail recursive, hopefully the browser will skip the stackframe
229
+      this.runTests();
230
+    }
231
+  },
232
+  summary: function() {
233
+    var assertions = 0;
234
+    var failures = 0;
235
+    var errors = 0;
236
+    var messages = [];
237
+    for(var i=0;i<this.tests.length;i++) {
238
+      assertions +=   this.tests[i].assertions;
239
+      failures   +=   this.tests[i].failures;
240
+      errors     +=   this.tests[i].errors;
241
+    }
242
+    return (
243
+      (this.options.context ? this.options.context + ': ': '') + 
244
+      this.tests.length + " tests, " + 
245
+      assertions + " assertions, " + 
246
+      failures   + " failures, " +
247
+      errors     + " errors");
248
+  }
249
+}
250
+
251
+Test.Unit.Assertions = Class.create();
252
+Test.Unit.Assertions.prototype = {
253
+  initialize: function() {
254
+    this.assertions = 0;
255
+    this.failures   = 0;
256
+    this.errors     = 0;
257
+    this.messages   = [];
258
+  },
259
+  summary: function() {
260
+    return (
261
+      this.assertions + " assertions, " + 
262
+      this.failures   + " failures, " +
263
+      this.errors     + " errors" + "\n" +
264
+      this.messages.join("\n"));
265
+  },
266
+  pass: function() {
267
+    this.assertions++;
268
+  },
269
+  fail: function(message) {
270
+    this.failures++;
271
+    this.messages.push("Failure: " + message);
272
+  },
273
+  info: function(message) {
274
+    this.messages.push("Info: " + message);
275
+  },
276
+  error: function(error) {
277
+    this.errors++;
278
+    this.messages.push(error.name + ": "+ error.message + "(" + Test.Unit.inspect(error) +")");
279
+  },
280
+  status: function() {
281
+    if (this.failures > 0) return 'failed';
282
+    if (this.errors > 0) return 'error';
283
+    return 'passed';
284
+  },
285
+  assert: function(expression) {
286
+    var message = arguments[1] || 'assert: got "' + Test.Unit.inspect(expression) + '"';
287
+    try { expression ? this.pass() : 
288
+      this.fail(message); }
289
+    catch(e) { this.error(e); }
290
+  },
291
+  assertEqual: function(expected, actual) {
292
+    var message = arguments[2] || "assertEqual";
293
+    try { (expected == actual) ? this.pass() :
294
+      this.fail(message + ': expected "' + Test.Unit.inspect(expected) + 
295
+        '", actual "' + Test.Unit.inspect(actual) + '"'); }
296
+    catch(e) { this.error(e); }
297
+  },
298
+  assertInspect: function(expected, actual) {
299
+    var message = arguments[2] || "assertInspect";
300
+    try { (expected == actual.inspect()) ? this.pass() :
301
+      this.fail(message + ': expected "' + Test.Unit.inspect(expected) + 
302
+        '", actual "' + Test.Unit.inspect(actual) + '"'); }
303
+    catch(e) { this.error(e); }
304
+  },
305
+  assertEnumEqual: function(expected, actual) {
306
+    var message = arguments[2] || "assertEnumEqual";
307
+    try { $A(expected).length == $A(actual).length && 
308
+      expected.zip(actual).all(function(pair) { return pair[0] == pair[1] }) ?
309
+        this.pass() : this.fail(message + ': expected ' + Test.Unit.inspect(expected) + 
310
+          ', actual ' + Test.Unit.inspect(actual)); }
311
+    catch(e) { this.error(e); }
312
+  },
313
+  assertNotEqual: function(expected, actual) {
314
+    var message = arguments[2] || "assertNotEqual";
315
+    try { (expected != actual) ? this.pass() : 
316
+      this.fail(message + ': got "' + Test.Unit.inspect(actual) + '"'); }
317
+    catch(e) { this.error(e); }
318
+  },
319
+  assertIdentical: function(expected, actual) { 
320
+    var message = arguments[2] || "assertIdentical"; 
321
+    try { (expected === actual) ? this.pass() : 
322
+      this.fail(message + ': expected "' + Test.Unit.inspect(expected) +  
323
+        '", actual "' + Test.Unit.inspect(actual) + '"'); } 
324
+    catch(e) { this.error(e); } 
325
+  },
326
+  assertNotIdentical: function(expected, actual) { 
327
+    var message = arguments[2] || "assertNotIdentical"; 
328
+    try { !(expected === actual) ? this.pass() : 
329
+      this.fail(message + ': expected "' + Test.Unit.inspect(expected) +  
330
+        '", actual "' + Test.Unit.inspect(actual) + '"'); } 
331
+    catch(e) { this.error(e); } 
332
+  },
333
+  assertNull: function(obj) {
334
+    var message = arguments[1] || 'assertNull'
335
+    try { (obj==null) ? this.pass() : 
336
+      this.fail(message + ': got "' + Test.Unit.inspect(obj) + '"'); }
337
+    catch(e) { this.error(e); }
338
+  },
339
+  assertMatch: function(expected, actual) {
340
+    var message = arguments[2] || 'assertMatch';
341
+    var regex = new RegExp(expected);
342
+    try { (regex.exec(actual)) ? this.pass() :
343
+      this.fail(message + ' : regex: "' +  Test.Unit.inspect(expected) + ' did not match: ' + Test.Unit.inspect(actual) + '"'); }
344
+    catch(e) { this.error(e); }
345
+  },
346
+  assertHidden: function(element) {
347
+    var message = arguments[1] || 'assertHidden';
348
+    this.assertEqual("none", element.style.display, message);
349
+  },
350
+  assertNotNull: function(object) {
351
+    var message = arguments[1] || 'assertNotNull';
352
+    this.assert(object != null, message);
353
+  },
354
+  assertType: function(expected, actual) {
355
+    var message = arguments[2] || 'assertType';
356
+    try { 
357
+      (actual.constructor == expected) ? this.pass() : 
358
+      this.fail(message + ': expected "' + Test.Unit.inspect(expected) +  
359
+        '", actual "' + (actual.constructor) + '"'); }
360
+    catch(e) { this.error(e); }
361
+  },
362
+  assertNotOfType: function(expected, actual) {
363
+    var message = arguments[2] || 'assertNotOfType';
364
+    try { 
365
+      (actual.constructor != expected) ? this.pass() : 
366
+      this.fail(message + ': expected "' + Test.Unit.inspect(expected) +  
367
+        '", actual "' + (actual.constructor) + '"'); }
368
+    catch(e) { this.error(e); }
369
+  },
370
+  assertInstanceOf: function(expected, actual) {
371
+    var message = arguments[2] || 'assertInstanceOf';
372
+    try { 
373
+      (actual instanceof expected) ? this.pass() : 
374
+      this.fail(message + ": object was not an instance of the expected type"); }
375
+    catch(e) { this.error(e); } 
376
+  },
377
+  assertNotInstanceOf: function(expected, actual) {
378
+    var message = arguments[2] || 'assertNotInstanceOf';
379
+    try { 
380
+      !(actual instanceof expected) ? this.pass() : 
381
+      this.fail(message + ": object was an instance of the not expected type"); }
382
+    catch(e) { this.error(e); } 
383
+  },
384
+  assertRespondsTo: function(method, obj) {
385
+    var message = arguments[2] || 'assertRespondsTo';
386
+    try {
387
+      (obj[method] && typeof obj[method] == 'function') ? this.pass() : 
388
+      this.fail(message + ": object doesn't respond to [" + method + "]"); }
389
+    catch(e) { this.error(e); }
390
+  },
391
+  assertReturnsTrue: function(method, obj) {
392
+    var message = arguments[2] || 'assertReturnsTrue';
393
+    try {
394
+      var m = obj[method];
395
+      if(!m) m = obj['is'+method.charAt(0).toUpperCase()+method.slice(1)];
396
+      m() ? this.pass() : 
397
+      this.fail(message + ": method returned false"); }
398
+    catch(e) { this.error(e); }
399
+  },
400
+  assertReturnsFalse: function(method, obj) {
401
+    var message = arguments[2] || 'assertReturnsFalse';
402
+    try {
403
+      var m = obj[method];
404
+      if(!m) m = obj['is'+method.charAt(0).toUpperCase()+method.slice(1)];
405
+      !m() ? this.pass() : 
406
+      this.fail(message + ": method returned true"); }
407
+    catch(e) { this.error(e); }
408
+  },
409
+  assertRaise: function(exceptionName, method) {
410
+    var message = arguments[2] || 'assertRaise';
411
+    try { 
412
+      method();
413
+      this.fail(message + ": exception expected but none was raised"); }
414
+    catch(e) {
415
+      ((exceptionName == null) || (e.name==exceptionName)) ? this.pass() : this.error(e); 
416
+    }
417
+  },
418
+  assertElementsMatch: function() {
419
+    var expressions = $A(arguments), elements = $A(expressions.shift());
420
+    if (elements.length != expressions.length) {
421
+      this.fail('assertElementsMatch: size mismatch: ' + elements.length + ' elements, ' + expressions.length + ' expressions');
422
+      return false;
423
+    }
424
+    elements.zip(expressions).all(function(pair, index) {
425
+      var element = $(pair.first()), expression = pair.last();
426
+      if (element.match(expression)) return true;
427
+      this.fail('assertElementsMatch: (in index ' + index + ') expected ' + expression.inspect() + ' but got ' + element.inspect());
428
+    }.bind(this)) && this.pass();
429
+  },
430
+  assertElementMatches: function(element, expression) {
431
+    this.assertElementsMatch([element], expression);
432
+  },
433
+  benchmark: function(operation, iterations) {
434
+    var startAt = new Date();
435
+    (iterations || 1).times(operation);
436
+    var timeTaken = ((new Date())-startAt);
437
+    this.info((arguments[2] || 'Operation') + ' finished ' + 
438
+       iterations + ' iterations in ' + (timeTaken/1000)+'s' );
439
+    return timeTaken;
440
+  },
441
+  _isVisible: function(element) {
442
+    element = $(element);
443
+    if(!element.parentNode) return true;
444
+    this.assertNotNull(element);
445
+    if(element.style && Element.getStyle(element, 'display') == 'none')
446
+      return false;
447
+    
448
+    return this._isVisible(element.parentNode);
449
+  },
450
+  assertNotVisible: function(element) {
451
+    this.assert(!this._isVisible(element), Test.Unit.inspect(element) + " was not hidden and didn't have a hidden parent either. " + ("" || arguments[1]));
452
+  },
453
+  assertVisible: function(element) {
454
+    this.assert(this._isVisible(element), Test.Unit.inspect(element) + " was not visible. " + ("" || arguments[1]));
455
+  },
456
+  benchmark: function(operation, iterations) {
457
+    var startAt = new Date();
458
+    (iterations || 1).times(operation);
459
+    var timeTaken = ((new Date())-startAt);
460
+    this.info((arguments[2] || 'Operation') + ' finished ' + 
461
+       iterations + ' iterations in ' + (timeTaken/1000)+'s' );
462
+    return timeTaken;
463
+  }
464
+}
465
+
466
+Test.Unit.Testcase = Class.create();
467
+Object.extend(Object.extend(Test.Unit.Testcase.prototype, Test.Unit.Assertions.prototype), {
468
+  initialize: function(name, test, setup, teardown) {
469
+    Test.Unit.Assertions.prototype.initialize.bind(this)();
470
+    this.name           = name;
471
+    
472
+    if(typeof test == 'string') {
473
+      test = test.gsub(/(\.should[^\(]+\()/,'#{0}this,');
474
+      test = test.gsub(/(\.should[^\(]+)\(this,\)/,'#{1}(this)');
475
+      this.test = function() {
476
+        eval('with(this){'+test+'}');
477
+      }
478
+    } else {
479
+      this.test = test || function() {};
480
+    }
481
+    
482
+    this.setup          = setup || function() {};
483
+    this.teardown       = teardown || function() {};
484
+    this.isWaiting      = false;
485
+    this.timeToWait     = 1000;
486
+  },
487
+  wait: function(time, nextPart) {
488
+    this.isWaiting = true;
489
+    this.test = nextPart;
490
+    this.timeToWait = time;
491
+  },
492
+  run: function() {
493
+    try {
494
+      try {
495
+        if (!this.isWaiting) this.setup.bind(this)();
496
+        this.isWaiting = false;
497
+        this.test.bind(this)();
498
+      } finally {
499
+        if(!this.isWaiting) {
500
+          this.teardown.bind(this)();
501
+        }
502
+      }
503
+    }
504
+    catch(e) { this.error(e); }
505
+  }
506
+});
507
+
508
+// *EXPERIMENTAL* BDD-style testing to please non-technical folk
509
+// This draws many ideas from RSpec http://rspec.rubyforge.org/
510
+
511
+Test.setupBDDExtensionMethods = function(){
512
+  var METHODMAP = {
513
+    shouldEqual:     'assertEqual',
514
+    shouldNotEqual:  'assertNotEqual',
515
+    shouldEqualEnum: 'assertEnumEqual',
516
+    shouldBeA:       'assertType',
517
+    shouldNotBeA:    'assertNotOfType',
518
+    shouldBeAn:      'assertType',
519
+    shouldNotBeAn:   'assertNotOfType',
520
+    shouldBeNull:    'assertNull',
521
+    shouldNotBeNull: 'assertNotNull',
522
+    
523
+    shouldBe:        'assertReturnsTrue',
524
+    shouldNotBe:     'assertReturnsFalse',
525
+    shouldRespondTo: 'assertRespondsTo'
526
+  };
527
+  var makeAssertion = function(assertion, args, object) { 
528
+   	this[assertion].apply(this,(args || []).concat([object]));
529
+  }
530
+  
531
+  Test.BDDMethods = {};   
532
+  $H(METHODMAP).each(function(pair) { 
533
+    Test.BDDMethods[pair.key] = function() { 
534
+       var args = $A(arguments); 
535
+       var scope = args.shift(); 
536
+       makeAssertion.apply(scope, [pair.value, args, this]); }; 
537
+  });
538
+  
539
+  [Array.prototype, String.prototype, Number.prototype, Boolean.prototype].each(
540
+    function(p){ Object.extend(p, Test.BDDMethods) }
541
+  );
542
+}
543
+
544
+Test.context = function(name, spec, log){
545
+  Test.setupBDDExtensionMethods();
546
+  
547
+  var compiledSpec = {};
548
+  var titles = {};
549
+  for(specName in spec) {
550
+    switch(specName){
551
+      case "setup":
552
+      case "teardown":
553
+        compiledSpec[specName] = spec[specName];
554
+        break;
555
+      default:
556
+        var testName = 'test'+specName.gsub(/\s+/,'-').camelize();
557
+        var body = spec[specName].toString().split('\n').slice(1);
558
+        if(/^\{/.test(body[0])) body = body.slice(1);
559
+        body.pop();
560
+        body = body.map(function(statement){ 
561
+          return statement.strip()
562
+        });
563
+        compiledSpec[testName] = body.join('\n');
564
+        titles[testName] = specName;
565
+    }
566
+  }
567
+  new Test.Unit.Runner(compiledSpec, { titles: titles, testLog: log || 'testlog', context: name });
568
+};

+ 42
- 0
src/com/dmdirc/addons/ui_web/res/newserverdialog.html View File

@@ -0,0 +1,42 @@
1
+<h2>New server</h2>
2
+
3
+<div class="dialogcontents">
4
+    
5
+    <p>Enter the details of the server that you wish to connect to.</p>
6
+    
7
+    <table class="form">
8
+        <tr>
9
+            <td>Server:</td>
10
+            <td><input type="text" id="nsd_server" value="irc.quakenet.org"></td>
11
+        </tr>
12
+        <tr>
13
+            <td>Port:</td>
14
+            <td><input type="text" id="nsd_port" value="6667"></td>
15
+        </tr>
16
+        <tr>
17
+            <td>Password:</td>
18
+            <td><input type="password" id="nsd_password"></td>
19
+        </tr>
20
+        <tr>
21
+            <td>Profile:</td>
22
+            <td><select id="nsd_profile" class="profilelist"></select></td>
23
+        </tr>
24
+        <tr>
25
+            <td></td>
26
+            <td>
27
+                <label>
28
+                    <input type="checkbox" id="nsd_ssl"> Use a secure (SSL) connection?
29
+                </label>
30
+                <label>
31
+                    <input type="checkbox" id="nsd_window" disabled="disabled" checked="checked">Open in a new window?
32
+                </label>
33
+            </td>
34
+        </tr>
35
+    </table>
36
+    
37
+    <div class="buttons">
38
+        <input type="submit" id="nsd_ok" value="OK" onclick="nsd_ok();">
39
+        <input type="submit" id="nsd_cancel" value="Cancel" onclick="nsd_cancel();">
40
+    </div>
41
+    
42
+</div>

+ 243
- 0
src/com/dmdirc/addons/ui_web/res/style.css View File

@@ -0,0 +1,243 @@
1
+body {
2
+    font-family: sans-serif;
3
+}
4
+
5
+#titlebar {
6
+    position: absolute;
7
+    top: 0px;
8
+    left: 0px;
9
+    width: 100%;
10
+    background: url('images/background.png') repeat-x bottom;
11
+    color: white;
12
+    height: 24px;
13
+}
14
+
15
+#titlebar #throbber {
16
+    width: 16px;
17
+    height: 16px;
18
+    padding: 4px;
19
+}
20
+
21
+#titlebar #title {
22
+    font-size: 14px;
23
+    padding: 4px 0;
24
+    position: absolute;
25
+    vertical-align: middle;
26
+    top: 0px;
27
+    left: 25px;
28
+    margin-right: 240px;
29
+    height: 1em;
30
+    overflow: hidden;
31
+}
32
+
33
+#toolbar {
34
+    position: absolute;
35
+    top: 24px;
36
+    left: 0px;
37
+    right: 0px;
38
+    height: 25px;
39
+    background-color: #e0e0e0;
40
+    border-bottom: 1px solid #666;
41
+}
42
+
43
+#toolbar .button {
44
+    display: -moz-inline-stack;
45
+    display: inline-block;
46
+    border-right: 1px solid #333;
47
+    width: 150px;
48
+    text-align: center;
49
+    height: 19px;
50
+    margin: 0px;
51
+    padding: 3px;
52
+    font-size: small;
53
+    vertical-align: middle;
54
+    cursor: pointer;
55
+}
56
+
57
+#toolbar .button:hover {
58
+    background-color: #efefef;
59
+}
60
+
61
+#toolbar .button:active {
62
+    background-color: #ccc;
63
+}
64
+
65
+#treeview {
66
+    position: absolute;
67
+    left: 15px;
68
+    top: 65px;
69
+    bottom: 40px;
70
+    width: 175px;
71
+    border: 1px solid black;
72
+    padding-left: 30px;
73
+    margin: 0px;
74
+}
75
+
76
+#treeview ul {
77
+    padding-left: 25px;
78
+}
79
+
80
+#treeview li {
81
+    list-style-image: url('/static/images/dmdirc.gif');
82
+    margin: 3px 0px;
83
+    padding: 0px;
84
+}
85
+
86
+#treeview li.server {
87
+    list-style-image: url('/dmdirc/server.png');
88
+}
89
+
90
+#treeview li.channel {
91
+    list-style-image: url('/dmdirc/channel.png');
92
+}
93
+
94
+#treeview li.query {
95
+    list-style-image: url('/dmdirc/query.png');
96
+}
97
+
98
+#content {
99
+    position: absolute;
100
+    left: 240px;
101
+    top: 65px;
102
+    right: 15px;
103
+    bottom: 40px;
104
+    padding: 4px;
105
+    border: 1px solid black;
106
+    overflow: auto;
107
+}
108
+
109
+#content p, #wus_requests p {
110
+    margin: 0px;
111
+    padding: 0px;
112
+}
113
+
114
+.nowindow {
115
+    font-size: xx-large;
116
+    text-align: center;
117
+    margin-top: 100px;
118
+    padding: 0px 100px;
119
+    color: #aaa;
120
+}
121
+
122
+#nicklist {
123
+    position: absolute;
124
+    right: 15px;
125
+    top: 75px;
126
+    bottom: 40px;
127
+    width: 215px;
128
+    border: 1px solid black;
129
+    margin: 0px;
130
+    padding: 0px;
131
+    display: none;
132
+}
133
+
134
+#nicklist li {
135
+    list-style-type: none;
136
+    margin: 2px;
137
+    padding: 0px;
138
+}
139
+
140
+#inputarea {
141
+    position: absolute;
142
+    bottom: 40px;
143
+    left: 240px;
144
+    right: 15px;
145
+    height: 20px;
146
+    display: none;
147
+}
148
+
149
+#inputarea #input {
150
+    width: 100%;
151
+    border: 1px solid black;
152
+}
153
+
154
+#statusbar {
155
+    position: absolute;
156
+    bottom: 0px;
157
+    left: 0px;
158
+    width: 100%;
159
+    border-top: 1px solid black;
160
+    background-color: #ccc;
161
+    font-size: small;
162
+}
163
+
164
+#statusbar div {
165
+    padding: 3px;
166
+}
167
+
168
+.dialog {
169
+    padding: 0px;
170
+    background-color: white;
171
+}
172
+
173
+.dialog h2 {
174
+    margin: 0px;
175
+    padding: 3px;
176
+    background-color: black;
177
+    color: white;
178
+    font-size: small;
179
+    font-weight: normal;
180
+    cursor: default;
181
+}
182
+
183
+.dialog .dialogcontents {
184
+    padding: 0px 10px;
185
+}
186
+
187
+.dialog .form {
188
+    width: 100%;
189
+}
190
+
191
+.dialog .form input[type="text"], .dialog .form input[type="password"], .dialog .form select {
192
+    width: 100%;
193
+}
194
+
195
+.dialog label {
196
+    display: block;
197
+}
198
+
199
+.dialog .buttons {
200
+    text-align: right;
201
+    margin-top: 10px;
202
+    position: absolute;
203
+    bottom: 5px;
204
+    right: 5px;
205
+}
206
+
207
+.dialog .buttons input {
208
+    width: 100px;
209
+}
210
+
211
+.wus_clients {
212
+    width: 100%;
213
+}
214
+
215
+.wus_last {
216
+    font-size: small;
217
+}
218
+
219
+th {
220
+    text-align: left;
221
+}
222
+
223
+#wus_requests {
224
+    width: 100%;
225
+    height: 100px;
226
+    overflow: auto;
227
+    border: 1px solid black;
228
+    font-size: x-small;
229
+}
230
+
231
+.speedbuttons {
232
+    position: absolute;
233
+    top: 3px;
234
+    right: 5px;
235
+    text-align: right;
236
+    color: white;
237
+    font-size: small;
238
+}
239
+
240
+.speedbuttons a {
241
+    color: white;
242
+    text-decoration: none;
243
+}

+ 38
- 0
src/com/dmdirc/addons/ui_web/res/webuistatus.html View File

@@ -0,0 +1,38 @@
1
+<h2>Web UI status</h2>
2
+
3
+<div class="speedbuttons">
4
+    Update speed:
5
+    <a id="wus_500" href="javascript:setSpeed('wus', 500);">500ms</a>
6
+    <a id="wus_1000" href="javascript:setSpeed('wus', 1000);" style="text-decoration: underline;">1s</a>
7
+    <a id="wus_2000" href="javascript:setSpeed('wus', 2000);">2s</a>
8
+    <a id="wus_5000" href="javascript:setSpeed('wus', 5000);">5s</a>
9
+    <a id="wus_10000" href="javascript:setSpeed('wus', 10000);">10s</a>
10
+</div>
11
+
12
+<div class="dialogcontents">
13
+    
14
+    <h3>Clients</h3>
15
+    
16
+    <p id="wus_last">Last updated: never</p>
17
+    
18
+    <table class="wus_clients">
19
+        <thead>
20
+            <tr>
21
+                <th>IP address</th>
22
+                <th>Last update</th>
23
+                <th>Event queue</th>
24
+            </tr>
25
+        </thead>
26
+        <tbody id="wus_clients">
27
+        </tbody>
28
+    </table>
29
+    
30
+    <h3>Requests</h3>
31
+    
32
+    <div id="wus_requests"></div>
33
+    
34
+    <div class="buttons">
35
+        <input type="submit" id="wus_close" value="Close" onclick="wus_close();">
36
+    </div>
37
+    
38
+</div>

+ 86
- 0
src/com/dmdirc/addons/ui_web/uicomponents/WebChannelWindow.java View File

@@ -0,0 +1,86 @@
1
+/*
2
+ * Copyright (c) 2006-2010 Chris Smith, Shane Mc Cormack, Gregory Holmes
3
+ *
4
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ * of this software and associated documentation files (the "Software"), to deal
6
+ * in the Software without restriction, including without limitation the rights
7
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ * copies of the Software, and to permit persons to whom the Software is
9
+ * furnished to do so, subject to the following conditions:
10
+ *
11
+ * The above copyright notice and this permission notice shall be included in
12
+ * all copies or substantial portions of the Software.
13
+ *
14
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ * SOFTWARE.
21
+ */
22
+
23
+package com.dmdirc.addons.ui_web.uicomponents;
24
+
25
+import com.dmdirc.Channel;
26
+import com.dmdirc.commandparser.parsers.ChannelCommandParser;
27
+import com.dmdirc.parser.interfaces.ChannelClientInfo;
28
+import com.dmdirc.ui.interfaces.ChannelWindow;
29
+import com.dmdirc.addons.ui_web.DynamicRequestHandler;
30
+import com.dmdirc.addons.ui_web.Event;
31
+
32
+import java.util.Collection;
33
+
34
+/**
35
+ *
36
+ * @author chris
37
+ */
38
+public class WebChannelWindow extends WebInputWindow implements ChannelWindow {
39
+    
40
+    private final Channel channel;
41
+
42
+    public WebChannelWindow(Channel channel) {
43
+        super(channel, new ChannelCommandParser(channel.getServer(), channel));
44
+        this.channel = channel;
45
+    }
46
+
47
+    /** {@inheritDoc} */
48
+    @Override
49
+    public void updateNames(Collection<ChannelClientInfo> clients) {
50
+        updateNames();
51
+    }
52
+
53
+    /** {@inheritDoc} */
54
+    @Override
55
+    public void addName(ChannelClientInfo client) {
56
+        DynamicRequestHandler.addEvent(new Event("addnicklist", client.getClient().getNickname()));
57
+    }
58
+
59
+    /** {@inheritDoc} */
60
+    @Override
61
+    public void removeName(ChannelClientInfo client) {
62
+        updateNames();
63
+    }
64
+
65
+    /** {@inheritDoc} */
66
+    @Override
67
+    public void updateNames() {
68
+        DynamicRequestHandler.addEvent(new Event("clearnicklist", null));
69
+        for (ChannelClientInfo cci : channel.getChannelInfo().getChannelClients()) {
70
+            DynamicRequestHandler.addEvent(new Event("addnicklist", cci.getClient().getNickname()));
71
+        }
72
+    }
73
+
74
+    /** {@inheritDoc} */
75
+    @Override
76
+    public Channel getChannel() {
77
+        return channel;
78
+    }
79
+
80
+    /** {@inheritDoc} */
81
+    @Override
82
+    public void redrawNicklist() {
83
+        // Do nothing
84
+    }
85
+
86
+}

+ 89
- 0
src/com/dmdirc/addons/ui_web/uicomponents/WebFrameManager.java View File

@@ -0,0 +1,89 @@
1
+/*
2
+ * Copyright (c) 2006-2010 Chris Smith, Shane Mc Cormack, Gregory Holmes
3
+ *
4
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ * of this software and associated documentation files (the "Software"), to deal
6
+ * in the Software without restriction, including without limitation the rights
7
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ * copies of the Software, and to permit persons to whom the Software is
9
+ * furnished to do so, subject to the following conditions:
10
+ *
11
+ * The above copyright notice and this permission notice shall be included in
12
+ * all copies or substantial portions of the Software.
13
+ *
14
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ * SOFTWARE.
21
+ */
22
+
23
+package com.dmdirc.addons.ui_web.uicomponents;
24
+
25
+import com.dmdirc.FrameContainer;
26
+import com.dmdirc.ui.interfaces.FrameManager;
27
+import com.dmdirc.addons.ui_web.DynamicRequestHandler;
28
+import com.dmdirc.addons.ui_web.Event;
29
+import com.dmdirc.addons.ui_web.WebInterfaceUI;
30
+import javax.swing.JComponent;
31
+
32
+/**
33
+ *
34
+ * @author chris
35
+ */
36
+public class WebFrameManager implements FrameManager {
37
+
38
+    /** {@inheritDoc} */
39
+    @Override
40
+    public void setParent(final JComponent parent) {
41
+        //TODO FIXME
42
+        throw new UnsupportedOperationException("Not supported yet.");
43
+    }
44
+
45
+    /** {@inheritDoc} */
46
+    @Override
47
+    public boolean canPositionVertically() {
48
+        //TODO FIXME
49
+        throw new UnsupportedOperationException("Not supported yet.");
50
+    }
51
+
52
+    /** {@inheritDoc} */
53
+    @Override
54
+    public boolean canPositionHorizontally() {
55
+        //TODO FIXME
56
+        throw new UnsupportedOperationException("Not supported yet.");
57
+    }
58
+
59
+    /** {@inheritDoc} */
60
+    @Override
61
+    public void addWindow(final FrameContainer window) {
62
+        DynamicRequestHandler.addEvent(new Event("newwindow", window.getFrame()));
63
+        WebInterfaceUI.active = (WebWindow) window.getFrame();
64
+    }
65
+
66
+    /** {@inheritDoc} */
67
+    @Override
68
+    public void delWindow(final FrameContainer window) {
69
+        //TODO FIXME
70
+        throw new UnsupportedOperationException("Not supported yet.");
71
+    }
72
+
73
+    /** {@inheritDoc} */
74
+    @Override
75
+    public void addWindow(final FrameContainer parent, final FrameContainer window) {
76
+        DynamicRequestHandler.addEvent(new Event("newchildwindow", new Object[]{
77
+            parent.getFrame(), window.getFrame()
78
+        }));
79
+        WebInterfaceUI.active = (WebWindow) window.getFrame();
80
+    }
81
+
82
+    /** {@inheritDoc} */
83
+    @Override
84
+    public void delWindow(final FrameContainer parent, final FrameContainer window) {
85
+        //TODO FIXME
86
+        throw new UnsupportedOperationException("Not supported yet.");
87
+    }
88
+
89
+}

+ 148
- 0
src/com/dmdirc/addons/ui_web/uicomponents/WebInputField.java View File

@@ -0,0 +1,148 @@
1
+/*
2
+ * Copyright (c) 2006-2010 Chris Smith, Shane Mc Cormack, Gregory Holmes
3
+ *
4
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ * of this software and associated documentation files (the "Software"), to deal
6
+ * in the Software without restriction, including without limitation the rights
7
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ * copies of the Software, and to permit persons to whom the Software is
9
+ * furnished to do so, subject to the following conditions:
10
+ *
11
+ * The above copyright notice and this permission notice shall be included in
12
+ * all copies or substantial portions of the Software.
13
+ *
14
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ * SOFTWARE.
21
+ */
22
+
23
+package com.dmdirc.addons.ui_web.uicomponents;
24
+
25
+import com.dmdirc.ui.interfaces.InputField;
26
+import com.dmdirc.addons.ui_web.DynamicRequestHandler;
27
+import com.dmdirc.addons.ui_web.Event;
28
+
29
+import java.awt.event.ActionListener;
30
+import java.awt.event.KeyListener;
31
+
32
+/**
33
+ *
34
+ * @author chris
35
+ */
36
+public class WebInputField implements InputField {
37
+    
38
+    private String clientID;
39
+    
40
+    private String text;
41
+    
42
+    private int selStart, selEnd;
43
+
44
+    public WebInputField() {
45
+    }
46
+
47
+    public WebInputField(String clientID) {
48
+        this.clientID = clientID;
49
+    }
50
+
51
+    /** {@inheritDoc} */
52
+    @Override
53
+    public void addActionListener(ActionListener listener) {
54
+        // Do nothing
55
+    }
56
+
57
+    /** {@inheritDoc} */
58
+    @Override
59
+    public void addKeyListener(KeyListener listener) {
60
+        // Do nothing
61
+    }
62
+
63
+    /** {@inheritDoc} */
64
+    @Override
65
+    public boolean hasFocus() {
66
+        //TODO FIXME
67
+        throw new UnsupportedOperationException("Not supported yet.");
68
+    }
69
+
70
+    /** {@inheritDoc} */
71
+    @Override
72
+    public void removeActionListener(ActionListener listener) {
73
+        // Do nothing
74
+    }
75
+
76
+    /** {@inheritDoc} */
77
+    @Override
78
+    public void removeKeyListener(KeyListener listener) {
79
+        // Do nothing
80
+    }
81
+
82
+    /** {@inheritDoc} */
83
+    @Override
84
+    public String getSelectedText() {
85
+        return text.substring(selStart, selEnd);
86
+    }
87
+
88
+    /** {@inheritDoc} */
89
+    @Override
90
+    public int getSelectionEnd() {
91
+        return selEnd;
92
+    }
93
+
94
+    /** {@inheritDoc} */
95
+    @Override
96
+    public int getSelectionStart() {
97
+        return selStart;
98
+    }
99
+
100
+    /** {@inheritDoc} */
101
+    @Override
102
+    public String getText() {
103
+        return text;
104
+    }
105
+
106
+    /** {@inheritDoc} */
107
+    @Override
108
+    public void setText(String text) {
109
+        DynamicRequestHandler.addEvent(clientID, new Event("settext", text));
110
+    }
111
+
112
+    /** {@inheritDoc} */
113
+    @Override
114
+    public int getCaretPosition() {
115
+        return selEnd;
116
+    }
117
+
118
+    /** {@inheritDoc} */
119
+    @Override
120
+    public void setCaretPosition(int position) {
121
+        DynamicRequestHandler.addEvent(clientID, new Event("setcaret", position));
122
+    }
123
+
124
+    /** {@inheritDoc} */
125
+    @Override
126
+    public void showColourPicker(boolean irc, boolean hex) {
127
+        // Do nothing
128
+    }
129
+
130
+    /** {@inheritDoc} */
131
+    @Override
132
+    public void hideColourPicker() {
133
+        // Do nothing
134
+    }
135
+
136
+    public void setSelEnd(int selEnd) {
137
+        this.selEnd = selEnd;
138
+    }
139
+
140
+    public void setSelStart(int selStart) {
141
+        this.selStart = selStart;
142
+    }
143
+    
144
+    public void setContent(final String text) {
145
+        this.text = text;
146
+    }
147
+
148
+}

+ 106
- 0
src/com/dmdirc/addons/ui_web/uicomponents/WebInputHandler.java View File

@@ -0,0 +1,106 @@
1
+/*
2
+ * Copyright (c) 2006-2010 Chris Smith, Shane Mc Cormack, Gregory Holmes
3
+ *
4
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ * of this software and associated documentation files (the "Software"), to deal
6
+ * in the Software without restriction, including without limitation the rights
7
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ * copies of the Software, and to permit persons to whom the Software is
9
+ * furnished to do so, subject to the following conditions:
10
+ *
11
+ * The above copyright notice and this permission notice shall be included in
12
+ * all copies or substantial portions of the Software.
13
+ *
14
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ * SOFTWARE.
21
+ */
22
+
23
+package com.dmdirc.addons.ui_web.uicomponents;
24
+
25
+import com.dmdirc.commandparser.parsers.CommandParser;
26
+import com.dmdirc.ui.input.InputHandler;
27
+import com.dmdirc.ui.input.TabCompleter;
28
+import com.dmdirc.ui.interfaces.InputField;
29
+
30
+/**
31
+ *
32
+ * @author chris
33
+ */
34
+public class WebInputHandler extends InputHandler {
35
+
36
+    public WebInputHandler(InputField thisTarget,
37
+                           CommandParser thisCommandParser,
38
+                           WebInputWindow thisParentWindow) {
39
+        super(thisTarget, thisCommandParser, thisParentWindow);
40
+    }
41
+
42
+    public InputField getTarget() {
43
+        return target;
44
+    }
45
+
46
+    /** {@inheritDoc} */
47
+    @Override
48
+    protected void addUpHandler() {
49
+        // Do nothing
50
+    }
51
+
52
+    /** {@inheritDoc} */
53
+    @Override
54
+    protected void addDownHandler() {
55
+        // Do nothing
56
+    }
57
+
58
+    /** {@inheritDoc} */
59
+    @Override
60
+    protected void addTabHandler() {
61
+        // Do nothing
62
+    }
63
+
64
+    /** {@inheritDoc} */
65
+    @Override
66
+    protected void addKeyHandler() {
67
+        // Do nothing
68
+    }
69
+
70
+    /** {@inheritDoc} */
71
+    @Override
72
+    protected void addEnterHandler() {
73
+        // Do nothing
74
+    }
75
+
76
+    public TabCompleter getTabCompleter() {
77
+        return tabCompleter;
78
+    }
79
+
80
+    /** {@inheritDoc} */
81
+    @Override
82
+    public void doTabCompletion() {
83
+        System.out.println("doTab");
84
+        super.doTabCompletion();
85
+    }
86
+
87
+    /** {@inheritDoc} */
88
+    @Override
89
+    public void doBufferDown() {
90
+        super.doBufferDown();
91
+    }
92
+
93
+    /** {@inheritDoc} */
94
+    @Override
95
+    public void doBufferUp() {
96
+        super.doBufferUp();
97
+    }
98
+
99
+    /** {@inheritDoc} */
100
+    @Override
101
+    public void handleKeyPressed(final String line, final int keyCode,
102
+            final boolean shiftPressed, final boolean ctrlPressed) {
103
+        super.handleKeyPressed(line, keyCode, shiftPressed, ctrlPressed);
104
+    }
105
+
106
+}

+ 111
- 0
src/com/dmdirc/addons/ui_web/uicomponents/WebInputWindow.java View File

@@ -0,0 +1,111 @@
1
+/*
2
+ * Copyright (c) 2006-2010 Chris Smith, Shane Mc Cormack, Gregory Holmes
3
+ *
4
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ * of this software and associated documentation files (the "Software"), to deal
6
+ * in the Software without restriction, including without limitation the rights
7
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ * copies of the Software, and to permit persons to whom the Software is
9
+ * furnished to do so, subject to the following conditions:
10
+ *
11
+ * The above copyright notice and this permission notice shall be included in
12
+ * all copies or substantial portions of the Software.
13
+ *
14
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ * SOFTWARE.
21
+ */
22
+
23
+package com.dmdirc.addons.ui_web.uicomponents;
24
+
25
+import com.dmdirc.WritableFrameContainer;
26
+import com.dmdirc.commandparser.parsers.CommandParser;
27
+import com.dmdirc.ui.input.InputHandler;
28
+import com.dmdirc.ui.interfaces.InputWindow;
29
+
30
+import java.util.HashMap;
31
+import java.util.Map;
32
+
33
+/**
34
+ *
35
+ * @author chris
36
+ */
37
+public class WebInputWindow extends WebWindow implements InputWindow {
38
+    
39
+    private final WritableFrameContainer parent;
40
+    
41
+    private final CommandParser commandparser;
42
+    
43
+    private final WebInputHandler inputhandler;
44
+    
45
+    private final Map<String, WebInputHandler> inputHandlers
46
+            = new HashMap<String, WebInputHandler>();
47
+
48
+    public WebInputWindow(WritableFrameContainer parent, CommandParser parser) {
49
+        super(parent);
50
+        this.parent = parent;
51
+        this.commandparser = parser;
52
+        this.inputhandler = new WebInputHandler(new WebInputField(), commandparser, this);
53
+    }
54
+
55
+    /** {@inheritDoc} */
56
+    @Override
57
+    public CommandParser getCommandParser() {
58
+        return commandparser;
59
+    }
60
+
61
+    /** {@inheritDoc} */
62
+    @Override
63
+    public InputHandler getInputHandler() {
64
+        return inputhandler;
65
+    }
66
+
67
+    public InputHandler getInputHandler(final String clientID) {
68
+        if (!inputHandlers.containsKey(clientID)) {
69
+            WebInputHandler ih
70
+                    = new WebInputHandler(new WebInputField(clientID), commandparser, this);
71
+            ih.setTabCompleter(inputhandler.getTabCompleter());
72
+            inputHandlers.put(clientID, ih);
73
+        }
74
+        
75
+        return inputHandlers.get(clientID);
76
+    }
77
+    
78
+    public InputHandler getInputHandler(final String clientID, final String text,
79
+            final String selStart, final String selEnd) {
80
+        int sel1, sel2;
81
+        
82
+        try {
83
+            sel1 = Integer.parseInt(selStart);
84
+            sel2 = Integer.parseInt(selEnd);
85
+        } catch (NumberFormatException ex) {
86
+            sel1 = 0;
87
+            sel2 = 0;
88
+        }
89
+        
90
+        WebInputHandler ih = (WebInputHandler) getInputHandler(clientID);
91
+        WebInputField field = (WebInputField) ih.getTarget();
92
+        field.setContent(text);
93
+        field.setSelStart(sel1);
94
+        field.setSelEnd(sel2);
95
+        
96
+        return ih;
97
+    }    
98
+
99
+    /** {@inheritDoc} */
100
+    @Override
101
+    public void setAwayIndicator(boolean isAway) {
102
+        // Do nothing
103
+    }
104
+
105
+    /** {@inheritDoc} */
106
+    @Override
107
+    public WritableFrameContainer getContainer() {
108
+        return parent;
109
+    }
110
+
111
+}

+ 90
- 0
src/com/dmdirc/addons/ui_web/uicomponents/WebMainWindow.java View File

@@ -0,0 +1,90 @@
1
+/*
2
+ * Copyright (c) 2006-2010 Chris Smith, Shane Mc Cormack, Gregory Holmes
3
+ *
4
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ * of this software and associated documentation files (the "Software"), to deal
6
+ * in the Software without restriction, including without limitation the rights
7
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ * copies of the Software, and to permit persons to whom the Software is
9
+ * furnished to do so, subject to the following conditions:
10
+ *
11
+ * The above copyright notice and this permission notice shall be included in
12
+ * all copies or substantial portions of the Software.
13
+ *
14
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ * SOFTWARE.
21
+ */
22
+
23
+package com.dmdirc.addons.ui_web.uicomponents;
24
+
25
+import com.dmdirc.ui.interfaces.MainWindow;
26
+import com.dmdirc.ui.interfaces.Window;
27
+import com.dmdirc.addons.ui_web.WebInterfaceUI;
28
+
29
+import javax.swing.ImageIcon;
30
+
31
+/**
32
+ *
33
+ * @author chris
34
+ */
35
+public class WebMainWindow implements MainWindow {
36
+
37
+    public void setActiveFrame(Window frame) {
38
+        WebInterfaceUI.active = (WebWindow) frame;
39
+    }
40
+
41
+    /** {@inheritDoc} */
42
+    @Override
43
+    public ImageIcon getIcon() {
44
+        //TODO FIXME
45
+        throw new UnsupportedOperationException("Not supported yet.");
46
+    }
47
+
48
+    /** {@inheritDoc} */
49
+    @Override
50
+    public void quit() {
51
+        //TODO FIXME
52
+        throw new UnsupportedOperationException("Not supported yet.");
53
+    }
54
+
55
+    /** {@inheritDoc} */
56
+    @Override
57
+    public void setMaximised(boolean max) {
58
+        //TODO FIXME
59
+        throw new UnsupportedOperationException("Not supported yet.");
60
+    }
61
+
62
+    /** {@inheritDoc} */
63
+    @Override
64
+    public boolean getMaximised() {
65
+        //TODO FIXME
66
+        throw new UnsupportedOperationException("Not supported yet.");
67
+    }
68
+
69
+    /** {@inheritDoc} */
70
+    @Override
71
+    public String getTitlePrefix() {
72
+        //TODO FIXME
73
+        throw new UnsupportedOperationException("Not supported yet.");
74
+    }
75
+
76
+    /** {@inheritDoc} */
77
+    @Override
78
+    public void setVisible(boolean visible) {
79
+        //TODO FIXME
80
+        throw new UnsupportedOperationException("Not supported yet.");
81
+    }
82
+
83
+    /** {@inheritDoc} */
84
+    @Override
85
+    public boolean isVisible() {
86
+        //TODO FIXME
87
+        throw new UnsupportedOperationException("Not supported yet.");
88
+    }
89
+
90
+}

+ 42
- 0
src/com/dmdirc/addons/ui_web/uicomponents/WebQueryWindow.java View File

@@ -0,0 +1,42 @@
1
+/*
2
+ * Copyright (c) 2006-2010 Chris Smith, Shane Mc Cormack, Gregory Holmes
3
+ *
4
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ * of this software and associated documentation files (the "Software"), to deal
6
+ * in the Software without restriction, including without limitation the rights
7
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ * copies of the Software, and to permit persons to whom the Software is
9
+ * furnished to do so, subject to the following conditions:
10
+ *
11
+ * The above copyright notice and this permission notice shall be included in
12
+ * all copies or substantial portions of the Software.
13
+ *
14
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ * SOFTWARE.
21
+ */
22
+
23
+package com.dmdirc.addons.ui_web.uicomponents;
24
+
25
+import com.dmdirc.Query;
26
+import com.dmdirc.commandparser.parsers.QueryCommandParser;
27
+import com.dmdirc.ui.interfaces.QueryWindow;
28
+
29
+/**
30
+ *
31
+ * @author chris
32
+ */
33
+public class WebQueryWindow extends WebInputWindow implements QueryWindow {
34
+    
35
+    private final Query query;
36
+
37
+    public WebQueryWindow(final Query parent) {
38
+        super(parent, new QueryCommandParser(parent.getServer(), parent));
39
+        this.query = parent;
40
+    }
41
+
42
+}

+ 43
- 0
src/com/dmdirc/addons/ui_web/uicomponents/WebServerWindow.java View File

@@ -0,0 +1,43 @@
1
+/*
2
+ * Copyright (c) 2006-2010 Chris Smith, Shane Mc Cormack, Gregory Holmes
3
+ *
4
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ * of this software and associated documentation files (the "Software"), to deal
6
+ * in the Software without restriction, including without limitation the rights
7
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ * copies of the Software, and to permit persons to whom the Software is
9
+ * furnished to do so, subject to the following conditions:
10
+ *
11
+ * The above copyright notice and this permission notice shall be included in
12
+ * all copies or substantial portions of the Software.
13
+ *
14
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ * SOFTWARE.
21
+ */
22
+
23
+package com.dmdirc.addons.ui_web.uicomponents;
24
+
25
+import com.dmdirc.Server;
26
+import com.dmdirc.commandparser.parsers.ServerCommandParser;
27
+import com.dmdirc.ui.interfaces.ServerWindow;
28
+
29
+/**
30
+ *
31
+ * @author chris
32
+ */
33
+public class WebServerWindow extends WebInputWindow implements ServerWindow {
34
+ 
35
+    private final Server server;
36
+
37
+    public WebServerWindow(Server server) {
38
+        super(server, new ServerCommandParser(server));
39
+        
40
+        this.server = server;
41
+    }
42
+
43
+}

+ 98
- 0
src/com/dmdirc/addons/ui_web/uicomponents/WebStatusBar.java View File

@@ -0,0 +1,98 @@
1
+/*
2
+ * Copyright (c) 2006-2010 Chris Smith, Shane Mc Cormack, Gregory Holmes
3
+ *
4
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ * of this software and associated documentation files (the "Software"), to deal
6
+ * in the Software without restriction, including without limitation the rights
7
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ * copies of the Software, and to permit persons to whom the Software is
9
+ * furnished to do so, subject to the following conditions:
10
+ *
11
+ * The above copyright notice and this permission notice shall be included in
12
+ * all copies or substantial portions of the Software.
13
+ *
14
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ * SOFTWARE.
21
+ */
22
+
23
+package com.dmdirc.addons.ui_web.uicomponents;
24
+
25
+import com.dmdirc.ui.interfaces.StatusBar;
26
+import com.dmdirc.ui.interfaces.StatusBarComponent;
27
+import com.dmdirc.ui.interfaces.StatusMessageNotifier;
28
+import com.dmdirc.addons.ui_web.DynamicRequestHandler;
29
+import com.dmdirc.addons.ui_web.Event;
30
+
31
+/**
32
+ *
33
+ * @author chris
34
+ */
35
+public class WebStatusBar implements StatusBar {
36
+
37
+    /** {@inheritDoc} */
38
+    @Override
39
+    public void setMessage(final String newMessage) {
40
+        DynamicRequestHandler.addEvent(new Event("statusbar", newMessage));
41
+    }
42
+
43
+    /** {@inheritDoc} */
44
+    @Override
45
+    public void setMessage(String newMessage, StatusMessageNotifier newNotifier) {
46
+        DynamicRequestHandler.addEvent(new Event("statusbar", newMessage));
47
+    }
48
+
49
+    /** {@inheritDoc} */
50
+    @Override
51
+    public void setMessage(String newMessage, StatusMessageNotifier newNotifier,
52
+                           int timeout) {
53
+        DynamicRequestHandler.addEvent(new Event("statusbar", newMessage));
54
+    }
55
+
56
+    /** {@inheritDoc} */
57
+    @Override
58
+    public void clearMessage() {
59
+        DynamicRequestHandler.addEvent(new Event("statusbar", "Ready"));
60
+    }
61
+
62
+    /** {@inheritDoc} */
63
+    @Override
64
+    public void addComponent(StatusBarComponent component) {
65
+        // Do nothing
66
+    }
67
+
68
+    /** {@inheritDoc} */
69
+    @Override
70
+    public void removeComponent(StatusBarComponent component) {
71
+        // Do nothing
72
+    }
73
+
74
+    public boolean isVisible() {
75
+        return true;
76
+    }
77
+
78
+    /** {@inheritDoc} */
79
+    @Override
80
+    public void setMessage(String iconType, String newMessage) {
81
+        DynamicRequestHandler.addEvent(new Event("statusbar", newMessage));
82
+    }
83
+
84
+    /** {@inheritDoc} */
85
+    @Override
86
+    public void setMessage(String iconType, String newMessage,
87
+            StatusMessageNotifier newNotifier) {
88
+        DynamicRequestHandler.addEvent(new Event("statusbar", newMessage));
89
+    }
90
+
91
+    /** {@inheritDoc} */
92
+    @Override
93
+    public void setMessage(String iconType, String newMessage,
94
+            StatusMessageNotifier newNotifier, int timeout) {
95
+        DynamicRequestHandler.addEvent(new Event("statusbar", newMessage));
96
+    }
97
+
98
+}

+ 330
- 0
src/com/dmdirc/addons/ui_web/uicomponents/WebWindow.java View File

@@ -0,0 +1,330 @@
1
+/*
2
+ * Copyright (c) 2006-2010 Chris Smith, Shane Mc Cormack, Gregory Holmes
3
+ *
4
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ * of this software and associated documentation files (the "Software"), to deal
6
+ * in the Software without restriction, including without limitation the rights
7
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ * copies of the Software, and to permit persons to whom the Software is
9
+ * furnished to do so, subject to the following conditions:
10
+ *
11
+ * The above copyright notice and this permission notice shall be included in
12
+ * all copies or substantial portions of the Software.
13
+ *
14
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ * SOFTWARE.
21
+ */
22
+
23
+package com.dmdirc.addons.ui_web.uicomponents;
24
+
25
+import com.dmdirc.FrameContainer;
26
+import com.dmdirc.config.ConfigManager;
27
+import com.dmdirc.ui.core.util.Utils;
28
+import com.dmdirc.ui.interfaces.Window;
29
+import com.dmdirc.ui.messages.Formatter;
30
+import com.dmdirc.ui.messages.IRCTextAttribute;
31
+import com.dmdirc.util.StringTranscoder;
32
+
33
+import com.dmdirc.addons.ui_web.DynamicRequestHandler;
34
+import com.dmdirc.addons.ui_web.Event;
35
+import com.dmdirc.addons.ui_web.Message;
36
+
37
+import java.awt.Color;
38
+import java.awt.font.TextAttribute;
39
+import java.beans.PropertyVetoException;
40
+import java.nio.charset.Charset;
41
+import java.text.AttributedCharacterIterator;
42
+import java.util.ArrayList;
43
+import java.util.Collection;
44
+import java.util.Date;
45
+import java.util.HashMap;
46
+import java.util.List;
47
+import java.util.Map;
48
+
49
+import org.apache.commons.lang.StringEscapeUtils;
50
+
51
+/**
52
+ *
53
+ * @author chris
54
+ */
55
+public class WebWindow implements Window {
56
+    
57
+    protected static int counter = 0;
58
+    
59
+    protected static Map<String, WebWindow> windows = new HashMap<String, WebWindow>();
60
+    
61
+    protected int myID = ++counter;
62
+    
63
+    private final FrameContainer parent;
64
+    
65
+    private List<String> messages = new ArrayList<String>();
66
+    
67
+    private String title;
68
+    
69
+    public WebWindow(final FrameContainer parent) {
70
+        super();
71
+        
72
+        this.parent = parent;
73
+        
74
+        windows.put(getId(), this);
75
+    }
76
+    
77
+    public static Collection<WebWindow> getWindows() {
78
+        return windows.values();
79
+    }
80
+    
81
+    public static WebWindow getWindow(final String id) {
82
+        return windows.get(id);
83
+    }
84
+    
85
+    public List<String> getMessages() {
86
+        return messages;
87
+    }
88
+
89
+    /** {@inheritDoc} */
90
+    @Override
91
+    public void addLine(String messageType, Object... args) {
92
+        if (!messageType.isEmpty()) {
93
+            addLine(Formatter.formatMessage(parent.getConfigManager(), messageType, args), true);
94
+        }
95
+    }
96
+
97
+    /** {@inheritDoc} */
98
+    @Override
99
+    public void addLine(StringBuffer messageType, Object... args) {
100
+        if (messageType != null) {
101
+            addLine(messageType.toString(), args);
102
+        }
103
+    }
104
+
105
+    /** {@inheritDoc} */
106
+    @Override
107
+    public void addLine(String line, boolean timestamp) {
108
+        for (String linepart : line.split("\n")) {
109
+            final String message = 
110
+                    style(Formatter.formatMessage(parent.getConfigManager(), "timestamp",
111
+                    new Date()), getConfigManager()) + style(linepart, getConfigManager());
112
+            messages.add(message);
113
+            DynamicRequestHandler.addEvent(new Event("lineadded", new Message(message, this)));
114
+        }
115
+    }
116
+
117
+    /** {@inheritDoc} */
118
+    @Override
119
+    public void clear() {
120
+        // Do nothing
121
+    }
122
+
123
+    /** {@inheritDoc} */
124
+    @Override
125
+    public ConfigManager getConfigManager() {
126
+        return parent.getConfigManager();
127
+    }
128
+
129
+    /** {@inheritDoc} */
130
+    @Override
131
+    public FrameContainer getContainer() {
132
+        return parent;
133
+    }
134
+
135
+    /** {@inheritDoc} */
136
+    @Override
137
+    public boolean isVisible() {
138
+        return true;
139
+    }
140
+
141
+    /** {@inheritDoc} */
142
+    @Override
143
+    public void setVisible(boolean isVisible) {
144
+        // Do nothing
145
+    }
146
+
147
+    /** {@inheritDoc} */
148
+    @Override
149
+    public String getTitle() {
150
+        return title;
151
+    }
152
+    
153
+    public String getName() {
154
+        return parent.toString();
155
+    }
156
+
157
+    /** {@inheritDoc} */
158
+    @Override
159
+    public boolean isMaximum() {
160
+        return true;
161
+    }
162
+
163
+    public void setMaximum(boolean b) throws PropertyVetoException {
164
+        // Do nothing
165
+    }
166
+
167
+    /** {@inheritDoc} */
168
+    @Override
169
+    public void setTitle(String title) {
170
+        this.title = title;
171
+    }
172
+
173
+    /** {@inheritDoc} */
174
+    @Override
175
+    public void open() {
176
+        // Do nothing
177
+    }
178
+
179
+    /** {@inheritDoc} */
180
+    @Override
181
+    public StringTranscoder getTranscoder() {
182
+        return new StringTranscoder(Charset.defaultCharset());
183
+    }
184
+
185
+    /** {@inheritDoc} */
186
+    @Override
187
+    public void close() {
188
+        throw new UnsupportedOperationException("Not supported yet.");
189
+    }
190
+    
191
+    public String getType() {
192
+        if (this instanceof WebServerWindow) {
193
+            return "server";
194
+        } else if (this instanceof WebChannelWindow) {
195
+            return "channel";
196
+        } else if (this instanceof WebQueryWindow) {
197
+            return "query";
198
+        } else {
199
+            return "window";
200
+        }
201
+    }
202
+    
203
+    public String getId() {
204
+        return String.valueOf(myID);
205
+    }
206
+    
207
+    protected String style(final String input, final ConfigManager config) {
208
+        final StringBuilder builder = new StringBuilder();
209
+        final AttributedCharacterIterator aci = Utils.getAttributedString(parent.getStyliser(),
210
+                new String[]{input}, "dialog", 12).getAttributedString().getIterator();
211
+         
212
+        Map<AttributedCharacterIterator.Attribute, Object> map = null;
213
+        char chr = aci.current();
214
+        
215
+        while (aci.getIndex() < aci.getEndIndex()) {
216
+            if (!aci.getAttributes().equals(map)) {                
217
+                style(aci.getAttributes(), builder);
218
+                map = aci.getAttributes();
219
+            }
220
+            
221
+            builder.append(StringEscapeUtils.escapeHtml(String.valueOf(chr)));
222
+            chr = aci.next();
223
+        }
224
+        
225
+        return builder.toString();
226
+    }
227
+    
228
+    protected static void style(final Map<AttributedCharacterIterator.Attribute, Object> map,
229
+            final StringBuilder builder) {
230
+        if (builder.length() > 0) {
231
+            builder.append("</span>");
232
+        }
233
+        
234
+        String link = null;
235
+                
236
+        builder.append("<span style=\"");
237
+        
238
+        for (Map.Entry<AttributedCharacterIterator.Attribute, Object> entry : map.entrySet()) {
239
+        
240
+            if (entry.getKey().equals(TextAttribute.FOREGROUND)) {
241
+                builder.append("color: ");
242
+                builder.append(toColour(entry.getValue()));
243
+                builder.append("; ");
244
+            } else if (entry.getKey().equals(TextAttribute.BACKGROUND)) {
245
+                builder.append("background-color: ");
246
+                builder.append(toColour(entry.getValue()));
247
+                builder.append("; ");
248
+            } else if (entry.getKey().equals(TextAttribute.WEIGHT)) { 
249
+                builder.append("font-weight: bold; ");
250
+            } else if (entry.getKey().equals(TextAttribute.FAMILY)) {
251
+                builder.append("font-family: monospace; ");
252
+            } else if (entry.getKey().equals(TextAttribute.POSTURE)) {
253
+                builder.append("font-style: italic; ");
254
+            } else if (entry.getKey().equals(TextAttribute.UNDERLINE)) {
255
+                builder.append("text-decoration: underline; ");
256
+            } else if (entry.getKey().equals(IRCTextAttribute.HYPERLINK)) {
257
+                builder.append("cursor: pointer; ");
258
+                link = "link_hyperlink('"
259
+                        + StringEscapeUtils.escapeHtml(
260
+                        StringEscapeUtils.escapeJavaScript((String) entry.getValue()))
261
+                        + "');";
262
+            } else if (entry.getKey().equals(IRCTextAttribute.CHANNEL)) {
263
+                builder.append("cursor: pointer; ");
264
+                link = "link_channel('"
265
+                        + StringEscapeUtils.escapeHtml(
266
+                        StringEscapeUtils.escapeJavaScript((String) entry.getValue()))
267
+                        + "');";
268
+            } else if (entry.getKey().equals(IRCTextAttribute.NICKNAME)) {
269
+                builder.append("cursor: pointer; ");
270
+                link = "link_query('"
271
+                        + StringEscapeUtils.escapeHtml(
272
+                        StringEscapeUtils.escapeJavaScript((String) entry.getValue()))
273
+                        + "');";
274
+            }
275
+        }
276
+        
277
+        builder.append('"');
278
+        
279
+        if (link != null) {
280
+            builder.append(" onClick=\"");
281
+            builder.append(link);
282
+            builder.append('"');
283
+        }
284
+        
285
+        builder.append('>');
286
+    }
287
+    
288
+    protected static String toColour(final Object object) {
289
+        final Color colour = (Color) object;
290
+        
291
+        return "rgb(" + colour.getRed() + ", " + colour.getGreen() + ", "
292
+                + colour.getBlue() + ")";
293
+    }
294
+
295
+    /** {@inheritDoc} */
296
+    @Override
297
+    public void restore() {
298
+        //TODO FIXME
299
+        throw new UnsupportedOperationException("Not supported yet.");
300
+    }
301
+
302
+    /** {@inheritDoc} */
303
+    @Override
304
+    public void maximise() {
305
+        //TODO FIXME
306
+        throw new UnsupportedOperationException("Not supported yet.");
307
+    }
308
+
309
+    /** {@inheritDoc} */
310
+    @Override
311
+    public void toggleMaximise() {
312
+        //TODO FIXME
313
+        throw new UnsupportedOperationException("Not supported yet.");
314
+    }
315
+
316
+    /** {@inheritDoc} */
317
+    @Override
318
+    public void minimise() {
319
+        //TODO FIXME
320
+        throw new UnsupportedOperationException("Not supported yet.");
321
+    }
322
+
323
+    /** {@inheritDoc} */
324
+    @Override
325
+    public void activateFrame() {
326
+        //TODO FIXME
327
+        throw new UnsupportedOperationException("Not supported yet.");
328
+    }
329
+
330
+}

Loading…
Cancel
Save