[ SF 710733 - Martin v. Loewis] Improving source encoding dialog

M IOBinding.py
M config-main.def
M configDialog.py
diff --git a/Lib/idlelib/IOBinding.py b/Lib/idlelib/IOBinding.py
index 6f46a60..5d7a221 100644
--- a/Lib/idlelib/IOBinding.py
+++ b/Lib/idlelib/IOBinding.py
@@ -13,6 +13,8 @@
 import tkFileDialog
 import tkMessageBox
 import re
+from Tkinter import *
+from SimpleDialog import SimpleDialog
 
 from configHandler import idleConf
 
@@ -67,6 +69,52 @@
 
 coding_re = re.compile("coding[:=]\s*([-\w_.]+)")
 
+class EncodingMessage(SimpleDialog):
+    "Inform user that an encoding declaration is needed."
+    def __init__(self, master, enc):
+        self.should_edit = False
+        
+        self.root = top = Toplevel(master)
+        top.bind("<Return>", self.return_event)
+        top.bind("<Escape>", self.do_ok)
+        top.protocol("WM_DELETE_WINDOW", self.wm_delete_window)
+        top.wm_title("I/O Warning")
+        top.wm_iconname("I/O Warning")
+        self.top = top
+
+        l1 = Label(top,
+            text="Non-ASCII found, yet no encoding declared. Add a line like")
+        l1.pack(side=TOP, anchor=W)
+        l2 = Entry(top, font="courier")
+        l2.insert(0, "# -*- coding: %s -*-" % enc)
+        # For some reason, the text is not selectable anymore if the
+        # widget is disabled.
+        # l2['state'] = DISABLED
+        l2.pack(side=TOP, anchor = W, fill=X)
+        l3 = Label(top, text="to your file\n"
+                   "Choose OK to save this file as %s\n"
+                   "Edit your general options to silence this warning" % enc)
+        l3.pack(side=TOP, anchor = W)
+
+        buttons = Frame(top)
+        buttons.pack(side=TOP, fill=X)
+        # Both return and cancel mean the same thing: do nothing
+        self.default = self.cancel = 0
+        b1 = Button(buttons, text="Ok", default="active",
+                    command=self.do_ok)
+        b1.pack(side=LEFT, fill=BOTH, expand=1)
+        b2 = Button(buttons, text="Edit my file",
+                    command=self.do_edit)
+        b2.pack(side=LEFT, fill=BOTH, expand=1)
+        
+        self._set_transient(master)
+
+    def do_ok(self):
+        self.done(0)
+
+    def do_edit(self):
+        self.done(1)
+
 def coding_spec(str):
     """Return the encoding declaration according to PEP 263.
 
@@ -368,18 +416,35 @@
                 return BOM_UTF8 + chars.encode("utf-8")
         # Nothing was declared, and we had not determined an encoding
         # on loading. Recommend an encoding line.
-        try:
-            chars = chars.encode(encoding)
-            enc = encoding
-        except UnicodeError:
-            chars = BOM_UTF8 + chars.encode("utf-8")
-            enc = "utf-8"
-        tkMessageBox.showerror(
-            "I/O Error",
-            "Non-ASCII found, yet no encoding declared. Add a line like\n"
-            "# -*- coding: %s -*- \nto your file" % enc,
-            master = self.text)
-        return chars
+	config_encoding = idleConf.GetOption("main","EditorWindow",
+					     "encoding")
+	if config_encoding == 'utf-8':
+		# User has requested that we save files as UTF-8
+		return BOM_UTF8 + chars.encode("utf-8")
+	ask_user = True
+	try:
+	    chars = chars.encode(encoding)
+	    enc = encoding
+	    if config_encoding == 'locale':
+                ask_user = False
+	except UnicodeError:
+	    chars = BOM_UTF8 + chars.encode("utf-8")
+	    enc = "utf-8"
+	if not ask_user:
+            return chars
+	dialog = EncodingMessage(self.editwin.top, enc)
+	dialog.go()
+	if dialog.num == 1:
+	    # User asked us to edit the file
+	    encline = "# -*- coding: %s -*-\n" % enc
+	    firstline = self.text.get("1.0", "2.0")
+	    if firstline.startswith("#!"):
+		# Insert encoding after #! line
+		self.text.insert("2.0", encline)
+	    else:
+		self.text.insert("1.0", encline)
+	    return self.encode(self.text.get("1.0", "end-1c"))
+	return chars
 
     def fixlastline(self):
         c = self.text.get("end-2c")
@@ -487,5 +552,4 @@
     root.mainloop()
 
 if __name__ == "__main__":
-    from Tkinter import *
     test()
diff --git a/Lib/idlelib/config-main.def b/Lib/idlelib/config-main.def
index 9d520c1..a6388d1 100644
--- a/Lib/idlelib/config-main.def
+++ b/Lib/idlelib/config-main.def
@@ -49,6 +49,7 @@
 font= courier
 font-size= 12
 font-bold= 0
+encoding=none
 
 [Indent]
 use-spaces= 1
diff --git a/Lib/idlelib/configDialog.py b/Lib/idlelib/configDialog.py
index 814689c..6cbc74f 100644
--- a/Lib/idlelib/configDialog.py
+++ b/Lib/idlelib/configDialog.py
@@ -334,6 +334,7 @@
         self.winWidth=StringVar(self)
         self.winHeight=StringVar(self)
         self.startupEdit=IntVar(self)
+        self.encoding=StringVar(self)
         self.userHelpBrowser=BooleanVar(self)
         self.helpBrowser=StringVar(self)
         #widget creation
@@ -342,6 +343,7 @@
         #body section frames
         frameRun=Frame(frame,borderwidth=2,relief=GROOVE)
         frameWinSize=Frame(frame,borderwidth=2,relief=GROOVE)
+        frameEncoding=Frame(frame,borderwidth=2,relief=GROOVE)
         frameHelp=Frame(frame,borderwidth=2,relief=GROOVE)
         #frameRun
         labelRunTitle=Label(frameRun,text='Startup Preferences')
@@ -359,6 +361,14 @@
         labelWinHeightTitle=Label(frameWinSize,text='Height')
         entryWinHeight=Entry(frameWinSize,textvariable=self.winHeight,
                 width=3)
+        #frameEncoding
+        labelEncodingTitle=Label(frameEncoding,text="Default Source Encoding")
+        radioEncLocale=Radiobutton(frameEncoding,variable=self.encoding,
+            value="locale",text="Locale-defined")
+        radioEncUTF8=Radiobutton(frameEncoding,variable=self.encoding,
+            value="utf-8",text="UTF-8")
+        radioEncNone=Radiobutton(frameEncoding,variable=self.encoding,
+            value="none",text="None")
         #frameHelp
         labelHelpTitle=Label(frameHelp,text='Help Options')
         frameHelpList=Frame(frameHelp)
@@ -387,6 +397,7 @@
         #body
         frameRun.pack(side=TOP,padx=5,pady=5,fill=X)
         frameWinSize.pack(side=TOP,padx=5,pady=5,fill=X)
+	frameEncoding.pack(side=TOP,padx=5,pady=5,fill=X)
         frameHelp.pack(side=TOP,padx=5,pady=5,expand=TRUE,fill=BOTH)
         #frameRun
         labelRunTitle.pack(side=TOP,anchor=W,padx=5,pady=5)
@@ -399,6 +410,11 @@
         labelWinHeightTitle.pack(side=RIGHT,anchor=E,pady=5)
         entryWinWidth.pack(side=RIGHT,anchor=E,padx=10,pady=5)
         labelWinWidthTitle.pack(side=RIGHT,anchor=E,pady=5)
+        #frameEncoding
+        labelEncodingTitle.pack(side=LEFT,anchor=W,padx=5,pady=5)
+        radioEncNone.pack(side=RIGHT,anchor=E,pady=5)
+        radioEncUTF8.pack(side=RIGHT,anchor=E,pady=5)
+        radioEncLocale.pack(side=RIGHT,anchor=E,pady=5)
         #frameHelp
         labelHelpTitle.pack(side=TOP,anchor=W,padx=5,pady=5)
         frameHelpListButtons.pack(side=RIGHT,padx=5,pady=5,fill=Y)
@@ -432,6 +448,7 @@
         self.winWidth.trace_variable('w',self.VarChanged_winWidth)
         self.winHeight.trace_variable('w',self.VarChanged_winHeight)
         self.startupEdit.trace_variable('w',self.VarChanged_startupEdit)
+        self.encoding.trace_variable('w',self.VarChanged_encoding)
 
     def VarChanged_fontSize(self,*params):
         value=self.fontSize.get()
@@ -525,6 +542,10 @@
         value=self.startupEdit.get()
         self.AddChangedItem('main','General','editor-on-startup',value)
 
+    def VarChanged_encoding(self,*params):
+        value=self.encoding.get()
+        self.AddChangedItem('main','EditorWindow','encoding',value)
+
     def ResetChangedItems(self):
         #When any config item is changed in this dialog, an entry
         #should be made in the relevant section (config type) of this
@@ -1020,6 +1041,8 @@
         #initial window size
         self.winWidth.set(idleConf.GetOption('main','EditorWindow','width'))
         self.winHeight.set(idleConf.GetOption('main','EditorWindow','height'))
+        # default source encoding
+        self.encoding.set(idleConf.GetOption('main','EditorWindow','encoding'))
         # additional help sources
         self.userHelpList = idleConf.GetAllExtraHelpSourcesList()
         for helpItem in self.userHelpList: