* mainloop.py: added facility for calling select().  Also added
  embryonic facility for pseudo-modal dialogs.
* stdwinevents.py: added modifier masks for key/mouse events
* renamed exceptions in nntplib.py
* Changed string.join() to call string.joinfields() to profit of
  strop.joinfields()
diff --git a/Lib/lib-stdwin/mainloop.py b/Lib/lib-stdwin/mainloop.py
index 0cf5bde..ab8ad3a 100644
--- a/Lib/lib-stdwin/mainloop.py
+++ b/Lib/lib-stdwin/mainloop.py
@@ -13,6 +13,11 @@
 windows = []
 
 
+# Last window that ever received an event
+#
+last_window = None
+
+
 # Function to register a window.
 #
 def register(win):
@@ -28,6 +33,9 @@
 # (this is useful for cleanup actions).
 #
 def unregister(win):
+	global last_window
+	if win == last_window:
+		last_window = None
 	if win in windows:
 		windows.remove(win) # Not in 0.9.1
 		# 0.9.1 solution:
@@ -49,6 +57,65 @@
 		return None
 
 
+# NEW: register any number of file descriptors
+#
+fdlist = []
+select_args = None
+select_handlers = None
+#
+def registerfd(fd, mode, handler):
+	if mode not in ('r', 'w', 'x'):
+		raise ValueError, 'mode must be r, w or x'
+	if type(fd) <> type(0):
+		fd = fd.fileno() # If this fails it's not a proper select arg
+	for i in range(len(fdlist)):
+		if fdlist[i][:2] == (fd, mode):
+			raise ValueError, \
+				'(fd, mode) combination already registered'
+	fdlist.append((fd, mode, handler))
+	make_select_args()
+#
+def unregisterfd(fd, *args):
+	if type(fd) <> type(0):
+		fd = fd.fileno() # If this fails it's not a proper select arg
+	args = (fd,) + args
+	n = len(args)
+	for i in range(len(fdlist)):
+		if fdlist[i][:n] == args:
+			del fdlist[i]
+	make_select_args()
+#
+def make_select_args():
+	global select_args, select_handlers
+	rlist, wlist, xlist = [], [], []
+	rhandlers, whandlers, xhandlers = {}, {}, {}
+	for fd, mode, handler in fdlist:
+		if mode == 'r':
+			rlist.append(fd)
+			rhandlers[`fd`] = handler
+		if mode == 'w':
+			wlist.append(fd)
+			whandlers[`fd`] = handler
+		if mode == 'x':
+			xlist.append(fd)
+			xhandlers[`fd`] = handler
+	if rlist or wlist or xlist:
+		select_args = rlist, wlist, xlist
+		select_handlers = rhandlers, whandlers, xhandlers
+	else:
+		select_args = None
+		select_handlers = None
+#
+def do_select():
+	import select
+	reply = apply(select.select, select_args)
+	for mode in 0, 1, 2:
+		list = reply[mode]
+		for fd in list:
+			handler = select_handlers[mode][`fd`]
+			handler(fd, 'rwx'[mode])
+
+
 # Event processing main loop.
 # Return when there are no windows left, or when an unhandled
 # exception occurs.  (It is safe to restart the main loop after
@@ -57,17 +124,111 @@
 # into KeyboardInterrupt exceptions; these are turned back in events.
 #
 def mainloop():
-	while windows:
+	stdwin_select_handler() # Process events already in stdwin queue
+	fd = stdwin.fileno()
+	while 1:
+		if windows:
+			registerfd(fd, 'r', stdwin_select_handler)
+			try:
+				while windows:
+					do_select()
+					stdwin_select_handler()
+			finally:
+				unregisterfd(fd)
+		elif fdlist:
+			while fdlist and not windows:
+				do_select()
+		else:
+			break
+
+
+# Handle stdwin events until none are left
+#
+def stdwin_select_handler(*args):
+	while 1:
 		try:
-			dispatch(stdwinq.getevent())
+			event = stdwinq.pollevent()
 		except KeyboardInterrupt:
-			dispatch(WE_COMMAND, stdwin.getactive(), WC_CANCEL)
+			event = (WE_COMMAND, None, WC_CANCEL)
+		if event is None:
+			break
+		dispatch(event)
+
+
+# Run a modal dialog loop for a window.  The dialog window must have
+# been registered first.  This prohibits most events (except size/draw
+# events) to other windows.  The modal dialog loop ends when the
+# dialog window unregisters itself.
+#
+passthrough = WE_SIZE, WE_DRAW
+beeping = WE_MOUSE_DOWN, WE_COMMAND, WE_CHAR, WE_KEY, WE_CLOSE, WE_MENU
+#
+def modaldialog(window):
+	if window not in windows:
+		raise ValueError, 'modaldialog window not registered'
+	while window in windows:
+		try:
+			event = stdwinq.getevent()
+		except KeyboardInterrupt:
+			event = WE_COMMAND, None, WC_CANCEL
+		etype, ewindow, edetail = event
+		if etype not in passthrough and ewindow <> window:
+			if etype in beeping:
+				stdwin.fleep()
+			continue
+		dispatch(event)
 
 
 # Dispatch a single event.
+# Events for the no window in particular are sent to the active window
+# or to the last window that received an event (these hacks are for the
+# WE_LOST_SEL event, which is directed to no particular window).
 # Windows not in the windows list don't get their events:
 # events for such windows are silently ignored.
 #
 def dispatch(event):
-	if event[1] in windows:
-		event[1].dispatch(event)
+	global last_window
+	if event[1] == None:
+		active = stdwin.getactive()
+		if active: last_window = active
+	else:
+		last_window = event[1]
+	if last_window in windows:
+		last_window.dispatch(event)
+
+
+# Dialog base class
+#
+class Dialog:
+	#
+	def init(self, title):
+		self.window = stdwin.open(title)
+		self.window.dispatch = self.dispatch
+		register(self.window)
+		return self
+	#
+	def close(self):
+		unregister(self.window)
+		del self.window.dispatch
+		self.window.close()
+	#
+	def dispatch(self, event):
+		etype, ewindow, edetail = event
+		if etype == WE_CLOSE:
+			self.close()
+
+
+# Standard modal dialogs
+# XXX implemented using stdwin dialogs for now
+#
+def askstr(prompt, default):
+	return stdwin.askstr(prompt, default)
+#
+def askync(prompt, yesorno):
+	return stdwin.askync(prompt, yesorno)
+#
+def askfile(prompt, default, new):
+	return stdwin.askfile(prompt, default, new)
+#
+def message(msg):
+	stdwin.message(msg)