#!/usr/bin/python # -*- coding: utf-8 -*- # Python mega widgets # Doc: http://www.scripps.edu/sanner/python/inputform/pmwWidgets.html # Download: http://sourceforge.net/projects/pmw/files/ import os import sys import pickle import glob import shutil import time from Tkinter import * import tkFileDialog import tkMessageBox import tkFont import Pmw import Image import ImageTk class Main_window(): """ Initialisation de la fenêtre principale """ user_dir = os.getcwd() path_orig = "" list_orig = [] file_orig_index = 0 actual_view = "" photo = "" csize = (0, 0) cformat = "" cmode = "" nwmode ="" Img = "" trans = "" name = "" ext = "" nwext = "" nname = "" format_list = [".bmp", ".eps", ".gif", ".im", ".jpeg", ".pcx", ".pdf", ".png", ".ppm", ".tiff", ".xbm"] mode_dic = {"1":"noir & blanc (1 bit)", "L":"gamme de gris", "P":"256 couleurs", "RGB":"RGB 24 bits", "RGBA":"RGBA 32 bits", "CMYK":"CMYK 32", "YCbCr":"couleur vidéo", "I": "I 32 bits entier", "F": "32 bits réel"} cinfoa, cinfob, cinfoc = "", "", "" proportional = True sflag = 0 canvas_size = [750, 540] zoom_current = 1.0 x1, y1 = 0, 0 entsel = () bpos = 2 c_list = [] user_opt = {1000:'oqapy', 1001:'version_0.1.0', 1012:0.1, 1013:True, 1015:True, 1016:False, 1050:6, 2000:20, 2001:0} zoom_jump = user_opt[1012] current_image = 0 def __init__(self): self.win = Tk() self.win.title("Oqapy 0.1.1") Pmw.initialise(self.win) self.win.resizable(False,False) # Evenement roulette de souris self.win.bind('', zoomup) self.win.bind('', zoomdown) # Barre de menus self.menu_bar = Menu(self.win) self.menu_file() # l'évenement panoramique doit se placer ici, sinon inactif self.win.bind("", pan_down) self.win.bind("", pan_move) self.win.bind("", pan_up) # Ligne d'état self.layer1 = Frame(self.win, width=600, height=1, bg='ivory', relief=GROOVE, bd=2) self.layer1.grid(row=13, column=1, columnspan=8,padx=4, pady=4, sticky=W) self.status_line = Label(self.layer1, bg='ivory', anchor=W, text="Pas de dossier source.", bd=2) self.status_line.grid(row=13, column=1, padx=2, pady=2) # zoom self.layer2 = Frame(self.win, width=40, height=1, bg='ivory', relief=GROOVE, bd=2) self.layer2.grid(row=13, column=10, padx=4, pady=4, sticky=W) self.magnif = Label(self.layer2, bg='ivory', text="0.0 %", bd=2) self.magnif.grid(row=13, column=10, padx=2, pady=2) # Tools self.frameaction = Frame(self.win, width=900, height=40, bg='grey', relief=GROOVE, bd=2) self.frameaction.grid(row=16, column=2, rowspan=1, columnspan=9) self.prevbut = Button(self.frameaction, text="Préc.", relief=GROOVE, bd=1, width=10, height=1, command=self.prev) self.prevbut.grid(row=16, column=4) self.nextbut = Button(self.frameaction, text="Suiv.", relief=GROOVE, bd=1, width=10, height=1, command=self.next) self.nextbut.grid(row=16, column=5) self.rot_leftbut = Button(self.frameaction, text="Gauche", relief=GROOVE, bd=1, width=10, height=1, command=rotate_l) self.rot_leftbut.grid(row=16, column=3) self.rot_rightbut = Button(self.frameaction, text="Droite", relief=GROOVE, bd=1, width=10, height=1, command=rotate_r) self.rot_rightbut.grid(row=16, column=6) self.freebut= Button(self.frameaction, text='free', relief=GROOVE, bd=1, width=10, height=1,) self.freebut.grid(row=16, column=7) self.delbut = Button(self.frameaction, text='Supp.', bg='orange', relief=GROOVE, bd=1, width=10, height=1) self.delbut.grid(row=16, column=8) # Canevas self.virgin_canvas() # Fenêtre de propriétés self.frameprop = Frame(self.win, width=250, height=500, relief=GROOVE, bd=2) self.frameprop.grid(row=0, column=13, rowspan=10, columnspan=3, sticky=E) self.frameprop.grid_propagate(0) self.property_tool = Pmw.NoteBook(self.frameprop) self.property_tool.grid(row=0, column=13, rowspan=15, columnspan=3, sticky=N) self.file_prop = self.property_tool.add("Image") self.group_view = Pmw.Group(self.file_prop, tag_text="Propriétés de l'image") self.group_view.pack(fill="both", expand=0, padx=1, pady=1) self.name_entry = Pmw.EntryField(self.group_view.interior(), labelpos=W, label_text="Nom :", entry_width=22, modifiedcommand=self.change_name) self.name_entry.pack(anchor='w', padx=5, pady=2) self.name_entry.focus_set() self.formlab = Pmw.Group(self.group_view.interior(), tag_text="Format") self.formlab.pack(fill="both", expand=1, padx=2, pady=5) self.format_entry = Pmw.ComboBox(self.formlab.interior(), label_text="Convertir : ", labelpos=W, labelmargin=56, selectioncommand=change_ext, scrolledlist_items=self.format_list, dropdown=1, entry_width=6) self.format_entry.pack(side='left', anchor='n', fill='x', expand=0, padx=5, pady=5) self.mode = Pmw.Group(self.group_view.interior(), tag_text="Mode") self.mode.pack(fill="both", expand=1, padx=2, pady=2) self.mode_label = Pmw.LabeledWidget(self.mode.interior(), labelpos="w", label_text="") self.mode_label.pack(anchor="w") self.size = Pmw.Group(self.group_view.interior(), tag_text="Taille") self.size.pack(fill='both', expand=0, padx=2, pady=2) self.sizel_entry = Pmw.EntryField(self.size.interior(), label_text= "Largeur : ", labelpos=W, entry_width=4, labelmargin=92, validate = {'validator' : 'numeric', 'minstrict' : 0}, modifiedcommand=self.resizel) self.sizel_entry.pack(anchor=W, padx=2, pady=2) self.sizeh_entry = Pmw.EntryField(self.size.interior(), label_text= "Hauteur : ", labelpos=W, entry_width=4, labelmargin=90, validate = {'validator' : 'numeric', 'minstrict' : 0}, modifiedcommand=self.resizeh) self.sizeh_entry.pack(anchor=W, padx=2, pady=2) self.subspace = Frame(self.size.interior(), width=231, height=4) self.subspace.pack(anchor=W) self.size_prop_check = Checkbutton(self.subspace, text="Proportionel", onvalue=1, command=self.prop) self.size_prop_check.pack(anchor=W) self.size_prop_check.select() self.size_apply_but = Button(self.subspace, text= "Appliquer", relief=GROOVE, bd=1, width=6, height=1, command=redim) self.size_apply_but.pack(anchor=S+W) self.source_check = Pmw.Group(self.group_view.interior(), tag_text="Source") self.source_check.pack(fill="both", expand=1, padx=2, pady=2) self.trans_check = Button(self.source_check.interior(), text= "Appliquer au fichier source", relief=GROOVE, bd=1, width=25, height=1, command=lambda who=0:store(self, who)) self.trans_check.pack(anchor=S) self.other = Pmw.Group(self.group_view.interior(), tag_text="Autres : ") self.other.pack(fill="both", expand=1, padx=2, pady=2) # Onglet Dossiers chain = "Démarrer avec ce dossier :" chain2 = ['vide'] chain3 = ['1', '2', '3', '4', '5', '6'] self.tab_path = self.property_tool.add("Dossiers") self.gest = Pmw.Group(self.tab_path, tag_text='Gestion des dossiers') self.gest.pack(fill="both", expand=1, padx=1, pady=1) self.sour = Pmw.Group(self.gest.interior(), tag_text="Dossier source :") self.sour.pack(fill="both", expand=0, padx=2, pady=2) self.sour_entry = Pmw.ComboBox(self.sour.interior(), label_text=chain, labelpos=N+W, scrolledlist_items=chain2, selectioncommand=self.get_source, dropdown=1, entry_width=23) self.sour_entry.pack(anchor='n', expand=0, padx=2, pady=5) self.subspace13 = Frame(self.gest.interior(), width=231, height=20) self.subspace13.pack(anchor=S) self.apply = Button(self.subspace13, text="Ouvrir maintenant", relief=GROOVE, bd=1, width=23, height=1, command=self.get_source_apply) self.apply.pack(anchor=S) self.targ = Pmw.Group(self.gest.interior(), tag_text="Dossiers cibles :") self.targ.pack(fill="both", expand=0, padx=2, pady=2) self.subspace14 = Frame(self.gest.interior(), width=231, height=20) self.subspace14.pack(anchor=S) # Onglet Options chain = "Maximum : " chain1 ="Vider la corbeille\nau démarrage" chain2 = "Ne pas utiliser la\ncorbeille du système" chain3 = "Utiliser les avertissements\nsonores" chain5 = "Dossiers récents :" self.tab_opt = self.property_tool.add("Options") self.pref = Pmw.Group(self.tab_opt, tag_text='Préférences') self.pref.pack(fill="both", expand=0, padx=1, pady=1) self.grafic = Pmw.Group(self.pref.interior(), tag_text="Interface") self.grafic.pack(fill="both", expand=1, padx=2, pady=2) self.size_label = Pmw.LabeledWidget(self.grafic.interior(), labelpos="w", label_text="Taille du canevas :") self.size_label.pack(anchor="w") self.canl = Pmw.EntryField(self.grafic.interior(), label_text= "largeur : ", labelpos=W, entry_width=5, labelmargin=78, validate = {'validator':'numeric', 'minstrict':'500'}) self.canl.pack(padx=2, pady=2) self.canh = Pmw.EntryField(self.grafic.interior(), label_text= "hauteur : ", labelpos=W, entry_width=5, labelmargin=75, validate = {'validator':'numeric', 'minstrict':'300'}) self.canh.pack(padx=2, pady=2) self.max = Pmw.LabeledWidget(self.grafic.interior(), labelpos=W, label_text=chain) self.max.pack(anchor=W) self.zinc = Pmw.EntryField(self.grafic.interior(), label_text= 'Incrément du zoom % :', labelpos=W, entry_width=4, labelmargin=18, validate={'validator':'numeric', 'minstrict':'1', 'maxstrict':'50'}) self.zinc.pack(anchor=W) self.subspace1 = Frame(self.grafic.interior(), width=231, height=4) self.subspace1.pack(anchor=W) self.bip = Checkbutton(self.subspace1, text=chain3, justify=LEFT, command=lambda who=3:self.modif(who)) self.bip.pack(anchor=W) self.target = Pmw.Group(self.pref.interior(), tag_text="Dossiers cibles") self.target.pack(fill="both", expand=1, padx=2, pady=2) self.memdir = Pmw.EntryField(self.target.interior(), label_text=chain5, labelpos=W, entry_width=4, labelmargin=29, validate={'validator':'numeric', 'minstrict':'0', 'maxstrict':'100'}) self.memdir.pack(anchor=W) Pmw.alignlabels((self.memdir,)) self.trashg = Pmw.Group(self.pref.interior(), tag_text='Corbeille') self.trashg.pack(fill="both", expand=0, padx=1, pady=1) self.subspace2 = Frame(self.trashg.interior(), width=231, height=10) self.subspace2.pack(anchor=W) self.outtrash = Checkbutton(self.subspace2, text=chain1, justify=LEFT, command=lambda who=6:self.modif(who)) self.outtrash.select() self.outtrash.pack(anchor=W) self.subspace3 = Frame(self.pref.interior(), width=231, height=20) self.subspace3.pack(anchor=W) self.apply = Button(self.subspace3, text="Appliquer", relief=GROOVE, bd=1, width=26, height=1, command=lambda who=10:self.modif(who)) self.apply.pack(anchor=CENTER) # Doit toujours se trouver à la fin self.property_tool.setnaturalsize() # Paramètres utilisateur self.init_options() #init des checkbuttons if self.user_opt[1013] == True: self.outtrash.select() else : self.outtrash.deselect() if self.user_opt[1016] == True: self.bip.select() else : self.bip.deselect() # Lecture des paramètres de l'interface def init_options(self): optfile = "viewer_options" chain = "Démarrer avec ce dossier :" if optfile in os.listdir(self.user_dir): objfile = open("viewer_options", "r") self.user_opt = pickle.load(objfile) objfile.close() scrX, scrY = self.win.winfo_screenwidth(), self.win.winfo_screenheight() self.user_opt[1010] = (scrX, scrY) self.dimc = (scrX-264, scrY-228) chain2 = "Maximum : " + str(self.dimc[0]) + " X " + str(self.dimc[1]) self.max.configure(label_text=chain2) if self.user_opt.has_key(1011): self.canvas_size = self.user_opt[1011] entrylist = ["Aucun"] self.sour_entry.destroy() self.sour_entry = Pmw.ComboBox(self.sour.interior(), label_text=chain, labelpos=N+W, scrolledlist_items=entrylist, selectioncommand=self.get_source, dropdown=1, entry_width=26) self.sour_entry.pack(anchor='n', expand=0, padx=2, pady=5) # Si dossier source à l'ouverture if self.user_opt.has_key(1051): open_orig(self) self.show_image() else : scrX, scrY = self.win.winfo_screenwidth(), self.win.winfo_screenheight() self.user_opt[1010] = (scrX, scrY) self.user_opt[1011] = (scrX-274, scrY-240) self.canvas_size = self.user_opt[1011] self.dimc = (scrX-264, scrY-228) chain = "Maximum : " + str(self.dimc[0]) + " X " + str(self.dimc[1]) self.max.configure(label_text=chain) objfile = open("viewer_options", "w") pickle.dump (self.user_opt, objfile) objfile.close() self.zoom_jump = self.user_opt[1012] self.canl.setvalue(self.user_opt[1011][0]) self.canh.setvalue(self.user_opt[1011][1]) self.zinc.setvalue(str(int(self.user_opt[1012]*100))) self.memdir.setvalue(self.user_opt[2000]) def virgin_canvas(self): try : self.canimg.destroy() except : pass if self.user_opt.has_key(1011): dim = self.user_opt[1011] else : dim = (650, 450) self.canimg = Canvas(self.win, width=dim[0], height=dim[1], bg='white', relief=GROOVE, bd=2) self.canimg.grid(row=0, column=0, rowspan=12, columnspan=12, sticky=N+S+E+W) def menu_file(self): # Création du menu "Fichier" try : self.menu_bar.destroy() except : pass self.menu_bar = Menu(self.win) self.fichier = Menu(self.menu_bar, tearoff=0) self.menu_bar.add_cascade(label="Fichier", underline=0, menu=self.fichier) self.fichier.add_command(label="Répertoire d'origine", underline=0, command=self.read_orig) self.fichier.add_separator() self.fichier.add_command(label="Quitter", underline=0, command=sys.exit) self.win.configure(menu=self.menu_bar) def get_source(self, sender): # Enregitrement du dossier source au démarrage way = self.sour_entry.getvalue()[0] if way != "Aucun": self.user_opt[1051] = way objfile = open("viewer_options", "w") pickle.dump (self.user_opt, objfile) objfile.close() def read_orig(self): # Initialisation du dossier source rep = App.user_dir path = tkFileDialog.askdirectory( title="Répertoire d'origine", initialdir=rep, mustexist=True) if path: self.open_orig(path) self.show_image() def get_source_apply(self): # Changement de source à la volée way = self.sour_entry.getvalue()[0] if way != "Aucun": # Ouverture self.open_orig(self, way) self.show_image() def open_orig(self, path): # Ouverture du dossier source head, tail = os.path.split(path) App.win.title("("+path+") - Oqapy") chain = path + "/*.*" self.c_list = glob.glob(chain) self.c_list.sort() def modif(self, sender): change = 1 # avertissements sonores if sender == 3: if self.user_opt[1016] == False: self.user_opt[1016] = True else : self.user_opt[1016] = False # vider la corbeille elif sender == 6: if self.user_opt[1013] == False: self.user_opt[1013] = True else : self.user_opt[1013] = False elif sender == 10: # largeur canevas ey = int(self.canl.getvalue()) if ey < 500 or ey > self.user_opt[1010][0]-264: self.canl.setvalue(str(self.user_opt[1011][0])) else : if ey != self.user_opt[1011][0]: change = change*2 self.user_opt[1011] = ey, self.user_opt[1011][1] # hauteur canevas ey = int(self.canh.getvalue()) if ey < 300 or ey > self.user_opt[1010][1]-228: self.canh.setvalue(str(self.user_opt[1011][1])) else : if ey != self.user_opt[1011][1]: change = change*2 self.user_opt[1011] = self.user_opt[1011][0], ey # incrément zoom ey = int(self.zinc.getvalue()) if ey < 1 or ey > 50: z = str(int(self.user_opt[1012]*100)) self.zinc.setvalue(z) else : self.user_opt[1012] = ey/100.0 self.zoom_jump = self.user_opt[1012] # dossiers cibles ey = int(self.numtarg.getvalue()) # dossiers récents ey = int(self.memdir.getvalue()) if ey < 1 or ey > 200: self.memdir.setvalue(str(self.user_opt[2000])) else : self.user_opt[2000] = ey objfile = open("viewer_options", "w") pickle.dump (self.user_opt, objfile) objfile.close() if change%2 == 0: self.canvas_size = self.user_opt[1011] if change%3 == 0: self.menu_file() # Canevas def create_canvas(self, xdim, ydim): self.layer = Frame(self.win, width=xdim, height=ydim, relief=FLAT, bd=0) self.layer.grid(row=0, column=0, rowspan=12, columnspan=12, sticky=E+N+W+S) self.canimg = Pmw.ScrolledCanvas(self.layer, usehullsize=1, hull_width=xdim,hull_height=ydim, canvas_bg='white', canvasmargin=3, borderframe=1, borderframe_borderwidth=3) self.canimg.configure(vscrollmode ='dynamic', hscrollmode ='dynamic') self.canimg.grid(row=0, column=0, rowspan=12, columnspan=12) self.canimg.create_image(xdim/2, ydim/2, image=self.photo) self.canimg.resizescrollregion() def show_image(self): while 1: try: self.Img = Image.open(self.c_list[self.current_image]) break except IOError: pass self.actual_view = self.c_list[self.current_image] self.image_property() def image_property(self): wratio, hratio = 0, 0 self.current_name = os.path.split(self.actual_view)[1] self.name, self.ext = os.path.splitext(self.current_name) self.nname, self.nwext = self.name, self.ext self.csize = self.Img.size self.cformat = self.Img.format if self.cformat == ".JPEG": self.cformat = ".jpg" self.cmode = self.Img.mode self.cinfo = self.Img.info txt = self.cmode if self.mode_dic.has_key(self.cmode): txt = self.mode_dic[self.cmode] self.mode_label.configure(label_text="mode : "+txt) self.name_entry.setentry(self.name) self.format_entry.setentry(self.cformat) self.sizel_entry.setentry(str(self.csize[0])) self.sizeh_entry.setentry(str(self.csize[1])) self.zoom_current = 1.0 #mise à dimension de la vue courante if self.csize[0] > self.canvas_size[0]: wratio = (self.csize[0]*1.0)/self.canvas_size[0] if self.csize[1] > self.canvas_size[1]: hratio = (self.csize[1]*1.0)/self.canvas_size[1] if hratio == 0 and wratio == 0: self.magnif.configure(text="100%") self.photo = ImageTk.PhotoImage(file = self.actual_view) self.create_canvas(self.canvas_size[0], self.canvas_size[1]) elif wratio > hratio: wdt = 10*int((1/wratio)*10) wdt = wdt*1.0 self.zoom_current = wdt/100+self.zoom_jump else : hgt = 10*int((1/hratio)*10) hgt = hgt*1.0 self.zoom_current = hgt/100+self.zoom_jump self.canimg.destroy() zw = int(self.csize[0]*self.zoom_current) zh = int(self.csize[1]*self.zoom_current) chain = str(100*self.zoom_current)+"%" self.magnif.configure(text=chain) self.actual_vue = self.Img.resize((zw, zh)) self.photo = ImageTk.PhotoImage(file=self.actual_view) self.create_canvas(self.canvas_size[0], self.canvas_size[1]) def prev(self): if self.current_image == 0: self.current_image = len(self.c_list) - 1 else: self.current_image -= 1 self.show_image() def next(self): if self.current_image == len(self.c_list) - 1: self.current_image = 0 else: self.current_image += 1 self.show_image() def prop(self): #redimensionnement proportionnel if self.proportional : self.proportional = False else : self.proportional = True def change_name(self): #nouveau nom de la vue courante self.nname = self.name_entry.getvalue() def resizel(self): #Dimensionnement en largeur #sflag doit être 0 if self.sflag == 0: wth, hht = self.csize[0], self.csize[1] nsl = self.sizel_entry.getvalue() if nsl != "": nsl = eval(nsl)*1.0 if nsl != 0: if nsl != wth: if self.proportional: #sflag = 1 pour empêcher un retour self.sflag = 1 div = wth/nsl y = int(hht/div) self.sizeh_entry.setvalue(y) else : self.sflag = 1 self.sizeh_entry.setvalue(hht) else : self.sflag = 0 def resizeh(self): #Dimensionnement en largeur #sflag doit être 0 if self.sflag == 0: wth, hht = self.csize[0], self.csize[1] nsh = self.sizeh_entry.getvalue() if nsh != "": nsh = eval(nsh)*1.0 if nsh != 0: if nsh != hht: if self.proportional: self.sflag = 1 div = hht/nsh y = int(wth/div) self.sizel_entry.setvalue(y) else : self.sflag = 1 self.sizel_entry.setvalue(wth) else : self.sflag = 0 def rotate_l(): App.actual_vue = App.Img.rotate(90) App.Img = App.Img.rotate(90) nw, nh = App.csize[0], App.csize[1] App.sflag = 1 App.sizel_entry.setentry(str(nh)) App.sizeh_entry.setentry(str(nw)) App.csize = (nh, nw) App.sflag = 0 reshow() def rotate_r(): App.actual_vue = App.Img.rotate(-90) App.Img = App.Img.rotate(-90) nw, nh = App.csize[0], App.csize[1] App.sflag = 1 App.sizel_entry.setentry(str(nh)) App.sizeh_entry.setentry(str(nw)) App.csize = (nh, nw) App.sflag = 0 reshow() def zoomup(event): if App.actual_view != "": App.zoom_current = App.zoom_current + App.user_opt[1012] zw = int(App.csize[0]*App.zoom_current) zh = int(App.csize[1]*App.zoom_current) if zw > 10000 or zh > 10000: return chain = str(100*App.zoom_current)+"%" App.magnif.configure(text=chain) redim(width=zw, height=zh) def zoomdown(event): if App.actual_view != "" : App.zoom_current = App.zoom_current - App.zoom_jump zw = int(App.csize[0]*App.zoom_current) zh = int(App.csize[1]*App.zoom_current) if zw < 20 or zh < 20: zoomup(None) chain = str(100*App.zoom_current)+"%" App.magnif.configure(text=chain) redim(width=zw, height=zh) def pan_down(event): App.x1, App.y1 = event.x, event.y App.entsel = App.canimg.find_closest(App.x1, App.y1) def pan_move(event): x2, y2 = event.x, event.y dx, dy = x2 -App.x1, y2 -App.y1 if App.entsel: App.canimg.move(App.entsel, dx, dy) App.x1, App.y1 = x2, y2 App.canimg.resizescrollregion() def pan_up(event): pass def reshow(): App.canimg.destroy() width, height = App.canvas_size[0], App.canvas_size[1] App.photo = ImageTk.PhotoImage(App.actual_vue) App.create_canvas(width, height) def change_ext(sender): App.nwext = sender def change_mode(sender): App.nwmode = sender def redim(width=0, height=0): #redimensionnement shade = "ANTIALIAS" ## shade requires stretch support (imToolkit & PIL 1.1.3) # imToolkit is a commercial librairy for PIL #Si l'appel vient d'un changement de taille d'image if width == 0: width = int(App.sizel_entry.getvalue()) height = int(App.sizeh_entry.getvalue()) App.Img = App.Img.resize((width, height)) App.csize = App.Img.size App.actual_vue = App.Img #si l'appel vient du zoom else : App.actual_vue = App.Img.resize((width, height)) reshow() def message(inst=0, chain=0, index=0, tot=0, direct="", imgname=""): com = ["Image %s / %s." %(index, tot), " %s enregistrée dans %s" %(imgname, direct), "Création de %s" %(direct), "Image %s supprimée de %s " %(index, direct), "Il n'y a pas (plus) d'image dans le dossier %s" %(direct)] string = com[chain] if not inst : App.status_line.configure(text=string) else : inst.status_line.configure(text=string) return App = Main_window() App.win.mainloop()