1#A* -------------------------------------------------------------------
2#B* This file contains source code for the PyMOL computer program
3#C* copyright 1998-2000 by Warren Lyford Delano of DeLano Scientific.
4#D* -------------------------------------------------------------------
5#E* It is unlawful to modify or remove this copyright notice.
6#F* -------------------------------------------------------------------
7#G* Please see the accompanying LICENSE file for further information.
8#H* -------------------------------------------------------------------
9#I* Additional authors of this source file include:
10#-*
11#-* NOTE: Based on code by John E. Grayson which was in turn
12#-* based on code written by Doug Hellmann.
13#Z* -------------------------------------------------------------------
14
15from __future__ import print_function
16
17import sys
18import os
19from glob import glob
20import re
21import traceback
22import Pmw
23
24if sys.version_info[0] == 2:
25    import Queue
26    from Tkinter import *
27    from tkFileDialog import *
28    import tkMessageBox
29else:
30    import queue as Queue
31    from tkinter import *
32    from tkinter.filedialog import *
33    import tkinter.messagebox as tkMessageBox
34
35try:
36    # monkey patch Pmw's error message box
37    def _reporterror(func, args):
38        import pymol
39
40        exc_type, exc_value, exc_traceback = sys.exc_info()
41        msg = "Sorry this command was not successful at this time"
42        if issubclass(exc_type, (pymol.cmd.QuietException)):
43            tkMessageBox.showerror('Error', msg)
44        elif issubclass(exc_type, (pymol.CmdException)):
45            tkMessageBox.showerror(getattr(exc_value, 'label', 'Error'), str(exc_value.message).strip() or msg)
46        else:
47            _reporterror.orig(func, args)
48
49    PmwBase = sys.modules[Pmw.MegaWidget.__module__]
50    _reporterror.orig = PmwBase._reporterror
51    PmwBase._reporterror = _reporterror
52
53    del PmwBase
54except Exception as e:
55    print('monkey patching _reporterror failed:', e)
56
57class PMGApp(Pmw.MegaWidget):
58
59    def initOS(self):
60         # Initialize platform-specific options
61         if sys.platform == 'darwin':
62             self.initializeTk_mac()
63         elif sys.platform[:3] == 'win':
64             self.initializeTk_win32()
65         elif sys.platform[:5] == 'linux':
66             self.initializeTk_unix()
67         else:
68             self.initializeTk_unix()
69#       self.root.tk.call('tk','scaling',1)
70
71         # try to get the windows properly aligned...
72         osFrame = { 'win32' : (4,60), 'irix'   : (0,41),
73                     'darwin': (0,51), 'cygwin' : (0,60),
74                     'linux' : (0,31), 'linux2' : (0,31) }
75
76         self.frameXAdjust, self.frameYAdjust = osFrame.get(sys.platform, (0, 51))
77
78    def initializeTk_win32(self):
79        pass
80
81    def initializeTk_mac(self):
82        pass
83
84    def initializeTk_unix(self):
85        pass
86
87    def getLoadableFileTypes(self):
88        return [("All Readable","*.pdb"),
89                ("All Readable","*.pdb1"),
90                ("All Readable","*.ccp4"),
91                ("All Readable","*.xplor"),
92                ("All Readable","*.mol"),
93                ("All Readable","*.mol2"),
94                ("All Readable","*.sdf"),
95                ("All Readable","*.xyz"),
96                ("All Readable","*.r3d"),
97                ("All Readable","*.cc1"),
98                ("All Readable","*.cc2"),
99                ("All Readable","*.ent"),
100                ("All Readable","*.dat"),
101                ("All Readable","*.out"),
102                ("All Readable","*.mmd"),
103                ("All Readable","*.mmod"),
104                ("All Readable","*.pse"),
105                ("All Readable","*.psw"),
106                ("All Readable","*.phi"),
107                ("All Readable","*.fld"),
108                ("All Readable","*.grd"),
109                ("All Readable","*.o"),
110                ("All Readable","*.omap"),
111                ("All Readable","*.brix"),
112                ("All Readable","*.dx"),
113                ("All Readable","*.pqr"),
114                ("All Readable","*.p5m"),
115                ("All Readable","*.p1m"),
116                ("All Readable","*.cube"),
117                ("All Readable","*.cif"),
118                ("All Readable","*.moe"), # proprietary format
119                ("All Readable","*.mae"), # proprietary format
120                ("All Readable","*.maegz"), # proprietary format
121                ("All Readable","*.cms"), # proprietary format
122                ("All Readable","*.idx"), # proprietary format
123                ("All Readable","*.fasta"),
124                ("All Readable","*.aln"),
125                ("All Readable","*.acnt"),
126                ("All Readable","*.mtz"),
127                ("All Readable","*.vis"),
128                ("All Readable","*.psf"),
129                ("All Readable","*.pdbml"),
130                ("All Readable","*.xml"),
131                ("All Readable","*.xml.gz"),
132                ("All Readable","*.pdbqt"),
133                ("All Readable","*.cml"),
134                ("All Readable","*.mmtf"),
135                ("PDB File","*.pdb"),
136                ("PDB1 File","*.pdb1"),
137                ("All Files","*.*"),
138                ("All Files","*"),
139                ("PDB File","*.ent"),
140                ("PyMOL Session","*.pse"),
141                ("PyMOL Show","*.psw"),
142                ("CCP4 Map","*.ccp4"),
143                ("XPLOR Map","*.xplor"),
144                ("MOL2/Multi-MOL2","*.mol2"),
145                ("Macromodel File","*.dat"),
146                ("Macromodel File","*.out"),
147                ("Macromodel File","*.mmd"),
148                ("Macromodel File","*.mmod"),
149#                ("MTZ Reflection File","*.mtz"),
150                ("BRIX/O Map","*.o"),
151                ("BRIX/O Map","*.omap"),
152                ("BRIX/O Map","*.brix"),
153                ("CIF","*.cif"),
154                ("Gaussian Cube Map","*.cube"),
155                ("DX Map","*.dx"),
156                ("AVS (MEAD) Field","*.fld"),
157                ("MOL File","*.mol"),
158                ("MOE File","*.moe"), # proprietary format
159                ("MAE File","*.mae"), # proprietary format
160                ("MAE File","*.maegz"), # proprietary format
161                ("MAE File","*.cms"), # proprietary format
162                ("Desmond Trajectory","*.idx"), # proprietary format
163                ("VIS File","*.vis"),
164                ("ChemPy Model","*.pkl"),
165                ("Raster3D Scene","*.r3d"),
166                ("SDF File","*.sdf"),
167                ("ChemDraw3D File","*.cc1"),
168                ("ChemDraw3D File","*.cc2"),
169                ("XYZ File","*.xyz"),
170                ("Fasta File","*.fasta"),
171                ("CLUSTAL file","*.aln"),
172                ("ACNT Map","*.acnt"),
173                ("Protein Structure File","*.psf"),
174                ("PDBML","*.pdbml"),
175                ("PDBML","*.xml"),
176                ("PDBML","*.xml.gz"),
177                ("PDBQT","*.pdbqt"),
178                ("Chemical Markup Language","*.cml"),
179                ("MMTF","*.mmtf"),
180                ("MMTF","*.mmtf.gz"),
181                ]
182
183    def initializeTk_colors_common(self):
184        #self.root.option_add('*background', 'grey')   #let system decide
185        self.root.option_add('*foreground', 'black')
186        self.root.option_add('*EntryField.Entry.background', 'white')
187        self.root.option_add('*Entry.background', 'white')
188        self.root.option_add('*MessageBar.Entry.background', 'gray85')
189        self.root.option_add('*Listbox*background', 'white')
190        self.root.option_add('*Listbox*selectBackground', 'dark slate blue')
191        self.root.option_add('*Listbox*selectForeground', 'white')
192
193    def quit_app(self):
194        self.pymol.cmd.log_close()
195        self.pymol.cmd.quit()  # avoid logging this - it's inconvenient...
196
197    def flush_fifo_once(self):
198        # flush the external GUI fifo command queue
199        while not self.fifo.empty():
200            try:
201                cmmd = self.fifo.get(0)
202                if isinstance(cmmd, str):
203                    exec(cmmd)
204                else:
205                    cmmd()
206            except:
207                traceback.print_exc()
208
209    def flush_fifo(self):
210        self.flush_fifo_once()
211        if self.allow_after:
212            self.root.after(20,self.flush_fifo) # 50X a second
213
214    def run(self,poll=0):
215        # this call to mainloop needs to be replaced with something revocable
216        self.flush_fifo_once()
217        keep_alive = 1
218        if poll:
219            import time
220            while keep_alive:
221                self.root.update()
222                time.sleep(0.05)
223        else:
224            self.root.mainloop()
225
226        self.quit_app()
227
228    def execute(self,cmmd):
229        self.fifo.put(cmmd)
230
231    def my_show(self,win,center=1):
232        if sys.platform!='linux2':
233            win.show()
234        else: # autocenter, deiconify, and run mainloop
235            # this is a workaround for a bug in the
236            # interaction between Tcl/Tk and common Linux
237            # window managers (namely KDE/Gnome) which causes
238            # an annoying 1-2 second delay in opening windows!
239            if center:
240                tw = win.winfo_reqwidth()+100
241                th = win.winfo_reqheight()+100
242                vw = win.winfo_vrootwidth()
243                vh = win.winfo_vrootheight()
244                x = max(0,(vw-tw)/2)
245                y = max(0,(vh-th)/2)
246                win.geometry(newGeometry="+%d+%d"%(x,y))
247            win.deiconify()
248
249    def my_withdraw(self,win):
250        if sys.platform!='linux2':
251            win.withdraw()
252        else:
253            win.destroy()
254
255    def _initializePlugins(self):
256        from pymol.plugins import legacysupport
257        return legacysupport.initializePlugins(self)
258
259    def addSkinMenuItems(self,menuBar,menuName):
260        if not hasattr(self,'skinNameList'):
261            # find installed skins
262            skin_pattern = re.sub(r"[\/\\][^\/\\]*$","/skins/*/__init__.py*",__file__)
263            raw_list = glob(skin_pattern)
264            unique = {}
265            for a in raw_list:
266                key = re.sub(r"[\/\\]__init__\.py.*$","",a)
267                unique[re.sub(r".*[\/\\]","",key)] = 1
268            name_list = list(unique.keys())
269            name_list.sort()
270            self.skinNameList = name_list
271        if hasattr(self,'skinNameList'):
272            for name in self.skinNameList:
273                caps_name = name.capitalize()
274                menuBar.addmenuitem(menuName, 'command', name,
275                                    label=caps_name,
276                                    command=lambda s=self,n=name:s.setSkin(n))
277
278
279    def setSkin(self,skin,run=1):
280        if isinstance(skin,str):
281            inv = sys.modules.get("pymol.invocation",None)
282            if inv!=None:
283                module_path = inv.options.gui +".skins."+ skin
284                __import__(inv.options.gui +".skins."+ skin)
285                skin = sys.modules[module_path].__init__(self)
286        if skin != self.skin:
287            if self.skin != None:
288                self.skin.takedown()
289            self.skin = skin
290
291        if run:
292            self.runSkin()
293
294    def runSkin(self):
295        if self.skin != None:
296            self.skin.setup()
297
298    def __init__(self, pymol_instance, skin):
299
300        # prevent overloading
301        self.initializePlugins = self._initializePlugins
302
303        self.allow_after = 1 # easy switch for troubleshooting threads
304
305        self.pymol = pymol_instance
306
307        if self.pymol._ext_gui != None:
308
309            raise RuntimeError  # only one PMGApp should ever be instantiated
310
311        else:
312
313            # create a FIFO so that PyMOL can send code to be executed by the GUI thread
314
315            self.fifo = Queue.Queue(0)
316
317            # create a pymol global so that PyMOL can find the external GUI
318
319            self.pymol._ext_gui = self
320
321            self.skin = None
322
323            # initialize Tcl/Tk
324
325            self.root = Tk() # creates the root window for the application
326
327            # color scheme
328
329            self.initializeTk_colors_common()
330
331             # operating-system dependencies
332
333            self.initOS()
334
335            # Python megawigit initialization
336
337            Pmw.initialise(self.root)
338
339            # Initialize the base class
340
341            Pmw.MegaWidget.__init__(self, parent=self.root)
342
343            # read the command line arguments regarding:
344            # - the size of the root window
345            # - the skin to use
346
347            inv = sys.modules.get("pymol.invocation",None)
348            if inv != None:
349                if skin == None:
350                    skin = inv.options.skin
351                self.frameWidth = inv.options.win_x + 220
352                self.frameXPos = inv.options.win_px - self.frameXAdjust
353                self.frameHeight = inv.options.ext_y
354                self.frameYPos = inv.options.win_py - (
355                         self.frameHeight + self.frameYAdjust)
356                self.setSkin(skin,run=0)
357
358            # define the size of the root window
359
360            import platform
361            if sys.platform == 'darwin' and platform.mac_ver()[0] >= '10.9':
362                # let OS X Maverics place the window automatically, to avoid
363                # off-screen placement in multi-monitor setup
364                self.root.geometry('%dx%d' % (self.frameWidth, self.frameHeight))
365            else:
366                self.root.geometry('%dx%d+%d+%d' % (
367                self.frameWidth, self.frameHeight, self.frameXPos, self.frameYPos))
368
369            # activate polling on the fifo
370
371            if self.allow_after:
372                self.root.after(1000,self.flush_fifo)
373
374            # and let 'er rip
375
376            self.runSkin()
377
378