GWT ScrollPanel for Touch Screens

Getting this working is easier than you think. Basically the only tricky bit is that the touch object allows for multiple fingers they are represented in the array ‘touches[]‘. Since we are only scrolling we only need to worry about the first finger which is why I use touches[0].screenY. Enjoy.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
package com.peterfranza.gwt.mobileui.client.widgets;
 
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.user.client.ui.ScrollPanel;
 
/**
 * 
 * @author peter.franza
 *
 */
public class TouchScrollPanel extends ScrollPanel {
 
	private int initialTouchX = -1;
	private int initialTouchY = -1;
	private int initialHorizontalOffset;
	private int initialVerticalOffset;
	private boolean moved = false;
 
	{
		attachTouch(getElement());
	}
 
	public TouchScrollPanel(VerticalPanel body) {
		super(body);
	}
 
	private native void attachTouch(JavaScriptObject ele) /*-{	
		var ref = this;
		ele.ontouchstart = function(evt){
	  		evt.preventDefault();	
	  		ref.@com.orci.datagateway.OpenIMWeb.mobile.client.widgets.TouchScrollPanel::setInitialTouch(II)(evt.touches[0].screenX, evt.touches[0].screenY);
		}	
		ele.ontouchmove = function(evt){
	  		evt.preventDefault();	
	  		ref.@com.orci.datagateway.OpenIMWeb.mobile.client.widgets.TouchScrollPanel::doScroll(II)(evt.touches[0].screenX, evt.touches[0].screenY);
		}		
		ele.ontouchend = function(evt){
			evt.preventDefault();
			ref.@com.orci.datagateway.OpenIMWeb.mobile.client.widgets.TouchScrollPanel::setEndTouch(II)(evt.pageX, evt.pageY);
		}		
	}-*/;
 
	private native void fireClick(int x, int y) /*-{	
		var theTarget = $doc.elementFromPoint(x, y);
		if (theTarget.nodeType == 3) theTarget = theTarget.parentNode;
 
		var theEvent = $doc.createEvent('MouseEvents');
		theEvent.initEvent('click', true, true);
		theTarget.dispatchEvent(theEvent);
	}-*/;
 
 
	@SuppressWarnings("unused")
	private void setInitialTouch(int x, int y) {
		initialVerticalOffset = getScrollPosition();
		initialHorizontalOffset = getHorizontalScrollPosition();
 
		initialTouchX = x;
		initialTouchY = y;
		moved = false;
 
	}
 
	@SuppressWarnings("unused")
	private void doScroll(int x, int y) {
		if (initialTouchY != -1) {
			moved = true;
			int vDelta = initialTouchY - y;
			int hDelta = initialTouchX - x;
			setScrollPosition(vDelta + initialVerticalOffset);
			setHorizontalScrollPosition(hDelta + initialHorizontalOffset);
		}
	}
 
	@SuppressWarnings("unused")
	private void setEndTouch(int x, int y) {
		if (!moved) {
			fireClick(x, y);
		}
		initialTouchX = -1;
		initialTouchY = -1;
	} 
}


GWT loading remote JSONP XSS

In almost all browsers there is one thing that everybody wants to do, but you just can’t, Load cross site data. That is you can’t unless you are using JSONP. Basically you load the data into its own script tag tell the server you are loading it from to wrap the data in a callback that you have defined. This will fire an event after the data has loaded. Perfect.

I wanted to use it in GWT … now I can.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package com.peterfranza.gwt.cle.client;
 
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.json.client.JSONObject;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.ui.RootPanel;
 
public class JSONPLoader {
 
	private LoaderCallback callback;
 
	public void load(String jsonUrl, LoaderCallback callback) {
		this.callback = callback;
		String callbackString = "jsonLoad_" + 
			DOM.createUniqueId().replace("-", "_") + "_callback";
		String url = jsonUrl + (jsonUrl.contains("?") ? "&" : "?") 
			+ "_callback=" + callbackString;
		publishCallbackMethod(callbackString);
		Element fr1 = DOM.createElement("script");		
		fr1.setAttribute("src", url);
		RootPanel.get().getElement().appendChild(fr1);
 
	}
 
	private native void publishCallbackMethod(String callback) /*-{
		var ptr = this;
  		$wnd[callback] = function(obj) {
  			ptr.@com.peterfranza.gwt.cle.client.JSONPLoader::loadremotedata(Ljava/lang/String;Lcom/google/gwt/core/client/JavaScriptObject;)(callback, obj);
  		};
	}-*/;
 
	@SuppressWarnings("unused")
	private void loadremotedata(String cbp, JavaScriptObject obj) {
	  callback.onLoad(new JSONObject(obj));
	}
 
	public interface LoaderCallback {
		void onLoad(JSONObject object);
	}
 
}

Usage:

1
2
3
4
5
6
7
8
JSONPLoader loader = new JSONPLoader();
loader.load("http://pipes.yahoo.com/pipes/pipe.run?_id=CPreCvz42xG4eaOWouNLYQ&_render=json&location=madison&section=mcy", new LoaderCallback() {
 
	@Override
	public void onLoad(JSONObject object) {
		Window.alert("loaded " + object.size());
	}
});

Notice that the json url does not specify the _callback parameter, this is because it is added by the JSONPLoader class.



Connecting Apache (httpd) To Active Directory

Recently I went through a small effort to connect a subversion repository to active directory. This is a good thing because it means that you no longer will need to manage the usernames and password using the old htpasswd format. The htpasswd is fine for very controlled environments but the passwords it allows you to use are pretty weak and the encryption of the passwords is fairly weak also, so allowing the connection to happen using active directory as the authority is a good thing. Also it keeps you from having to maintain more passwords, and I like that idea.

In your /etc/httpd/conf.d/filename.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
LoadModule dav_svn_module modules/mod_dav_svn.so
LoadModule authz_svn_module modules/mod_authz_svn.so
 
LDAPConnectionTimeout 15
 
LDAPSharedCacheSize 200000
LDAPCacheEntries 1024
LDAPCacheTTL 600
LDAPOpCacheEntries 1024
LDAPOpCacheTTL 600
 
  <Location /server/cache-info>
   SetHandler ldap-status
  </Location>
 
  <Location /svn>
    DAV svn
    SVNPath /subversion/repos
    SVNListParentPath on
 
    AuthzSVNAccessFile /subversion/svnauthorz
    Satisfy Any
    AuthType Basic
    AuthName "Members Only"
    AuthzLDAPAuthoritative off
    AuthBasicProvider ldap
    AuthLDAPBindDN "svn.user@domain.com"
    AuthLDAPBindPassword "svn.user.password"
    AuthLDAPURL "ldap://<ldapserverip>/DC=domain,DC=com?sAMAccountName?sub?(objectClass=user)"
    Require valid-user
 </Location>

However every couple of transactions I would get an error and this message would appear in my error log

/var/log/httpd/error_log

1
[Fri Apr 30 09:21:46 2010] [warn] [client 192.168.100.105] [22578] auth_ldap authenticate: user peter.franza authentication failed; URI /svnad/projects/ [ldap_search_ext_s() for user failed][Operations error], referer: http://svn/svnad/projects/

The solution was to disable following referers

/etc/openldap/ldap.conf

1
REFERRALS off

*Note this is in /etc/openldap/ not the ldap.conf in /etc that file is used from pam authentication and not for mod_ldap



Non-blocking UDP datagram replicator

A class that listens to a UDP port and collects all the datagrams and then rebroadcasts those datagrams to other ports. This is useful for several reasons. I use it when stress testing UDP clients because I can subscribe to 1000 client sockets while only really having a single legitimate datasource.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
public class Replicator {
	private final AsyncDatagramServer aserver;
 
	public Replicator(final int port, 
			final Collection<DataSinkPoint> endPoints) throws Exception {
		aserver = new AsyncDatagramServer(port, 
				new AsyncDatagramServer.AsyncDatagramServerListener() {
 
			@Override
			public void recieveDatagram(ByteBuffer buffer) {
				try {
					for(final DataSinkPoint d: endPoints) {
						d.send(buffer.duplicate());
					}
				} catch (final Exception e) {
					e.printStackTrace();
				}
			}
		});
	}
 
	private static class AsyncDatagramServer {
 
		private boolean running = true;
 
		public AsyncDatagramServer(final int port, 
				final AsyncDatagramServerListener listener) throws Exception {
 
			new Thread(){
 
				@Override
				public void run() {
					try {
						startSocket(port, listener);
					} catch (final Exception e) {
						throw new RuntimeException(e);
					}
				}
 
			}.start();
 
		}
 
		public interface AsyncDatagramServerListener {
			void recieveDatagram(ByteBuffer buffer);
		}
 
		public void shutdown() {
			running = false;
		}
 
		private void startSocket(final int port,
				AsyncDatagramServerListener listener) throws IOException,
				SocketException, ClosedChannelException, Exception {
			DatagramChannel serverChannel = DatagramChannel.open();
			Selector selector = Selector.open();
			DatagramSocket sock = serverChannel.socket();
			sock.setReuseAddress(true);
			sock.bind (new InetSocketAddress (port));
			serverChannel.configureBlocking (false);
			serverChannel.register(selector, SelectionKey.OP_READ);
			ByteBuffer buffer = ByteBuffer.allocate(2048);
 
			while (running) {
				if(selector.select(500) > 0) {
					processData(listener, selector, buffer);
				}
			}
 
			selector.close();
			serverChannel.close();
		}
 
		private void processData(AsyncDatagramServerListener listener,
				Selector selector, ByteBuffer buffer) throws Exception {
			Iterator<SelectionKey> it = selector.selectedKeys().iterator();
 
			while (it.hasNext()) {
				final SelectionKey key = it.next();
 
				if (key.isReadable()) {
					DatagramChannel channel = 
						(DatagramChannel) key.channel();
					buffer.clear();
					if (channel.receive(buffer) != null) {
						buffer.flip();
						listener.recieveDatagram(buffer.duplicate());
					}
				}
				it.remove();
			}
		}
 
 
 
	}
 
	public void shutdown() {
		aserver.shutdown();
	}
 
	public interface DataSinkPoint {
		void send(ByteBuffer buffer) throws IOException;
	}
 
	private static class DataSinkPointImpl implements DataSinkPoint {
		private final DatagramSocket socket;
 
		public DataSinkPointImpl(SocketAddress address) throws Exception {
			socket = new DatagramSocket();
			socket.connect(address);
		}
 
		@Override
		public void send(ByteBuffer buffer) throws IOException {
			socket.send(new DatagramPacket(buffer.array(),
					0, buffer.limit()));
		}
 
	}
 
}

Usage:

1
2
3
4
5
6
7
8
9
10
11
12
public static void main(String[] args) throws Exception {
	Collection<DataSinkPoint> endPoints = new ArrayList<DataSinkPoint>();
	for(int i = 4000; i < 5000; i++) {
		endPoints.add(new DataSinkPointImpl(
				new InetSocketAddress("127.0.0.1", i)));
	}
 
	final Replicator r = new Replicator(3000, endPoints);
	Thread.sleep(5000);
	r.shutdown();
	System.out.println("done.");
}


Announcing – “Announce” for Android

Announce is an application for Android 2.0 and above.  It intercepts incoming calls and and reads the name of the caller aloud.  You can also record custom voice tags to use instead of the text to speech engine.

Project Page: http://www.peterfranza.com/projects/announce-for-android/

Price: Free (That’s a good value)

Enjoy.