home *** CD-ROM | disk | FTP | other *** search
/ PC Extra 07 & 08 / pca1507.iso / Software / psp8 / Data1.cab / ScriptManager.PspScript < prev    next >
Encoding:
Text File  |  2003-04-22  |  19.0 KB  |  438 lines

  1. from JascApp import *
  2. from Tkinter import *
  3. from tkMessageBox import *
  4. from stat import *
  5.  
  6. import os.path
  7. import os
  8. import sys
  9. import glob
  10.  
  11. ScriptVersion = '1.1'
  12. # Revision log
  13. # 1.1 Added call to StartForeignWindow
  14. # 1.0 Removed the run and gui edit buttons, since there is a problem running nested Tk dialogs.
  15. # 0.3 Added word wrapping on the description, copyright and location fields.
  16. #     Added ability to send the script to the GUI editor
  17. # 0.2 Fixed bug in rename.  GetString was not using interactive mode.
  18. #     Added ability to run either silently or interactively
  19. # 0.1 Initial
  20.  
  21. # ScriptManager exists to help people manage and organize their scripts.
  22. # It provides the following functions:
  23. # 1. Displays all scripts found on the configured paths
  24. # 2. For the selected script, shows the author, copyright and description strings
  25. # 3. Allows Delete, Rename, Edit and Run of the selected script
  26. # 4. Can move a script to any configured directory
  27.  
  28. # I use a tuple to encapsulate everything I know about a script:
  29. # 0 - script name
  30. # 1 - script path
  31. # 2 - 1 if trusted, 0 if restricted
  32. # 3 - dictionary returned by script properties, None if not initialized
  33.  
  34. def ScriptProperties():
  35.     return {
  36.         'Author': 'Joe Fromm',
  37.         'Copyright': 'Copyright (C) 2002-2003, Jasc Software Inc., All Rights Reserved. Permission to create derivative works of this script is granted provided this copyright notice is included',
  38.         'Description': 'Manage existing script files - delete, rename, move, edit.',
  39.         'Host': 'Paint Shop Pro',
  40.         'Host Version': '8.00'
  41.         }
  42.  
  43. class ChooseDir(Toplevel):
  44.     'dialog class for selecting one of the defined file locations'
  45.  
  46.     def __init__(self, Current, Trusted, Restricted ):
  47.         Toplevel.__init__(self)
  48.         self.title( 'Move Script' )
  49.         
  50.         # mark the current dialog as the foreign window again        
  51.         App.Do( GlobalEnv, 'StartForeignWindow', { 'WindowHandle': int(self.winfo_id()) } )
  52.         
  53.         self.NewDir = Current        # on exit, this will be set to the selected dir
  54.         self.AllDirs = Restricted + Trusted # save the complete set so I can do the move later
  55.         
  56.         Label( self, text='Choose a directory to move to:' ).pack()
  57.  
  58.         ListFrame = Frame(self)
  59.         ListFrame.pack(side=TOP, expand=YES,fill=BOTH)
  60.         self.DirList = Listbox(ListFrame, height=10, width=75, selectmode=SINGLE, exportselection=FALSE)
  61.         self.DirList.pack(side=LEFT, expand=YES, fill=BOTH)
  62.         DirListScroll = Scrollbar(ListFrame, command = self.DirList.yview)
  63.         DirListScroll.pack(side=LEFT, fill=Y)
  64.         self.DirList.configure(yscrollcommand=DirListScroll.set)
  65.         self.DirList.bind("<Button-1>", self.OnSelectDir)
  66.         
  67.         ButtonFrame = Frame( self )
  68.         ButtonFrame.pack( expand=YES, fill = X)
  69.         Button(ButtonFrame, text='OK', width = 10, command=self.OnOK ).pack(side=LEFT, padx=10, pady=10)
  70.         Button(ButtonFrame, text='Cancel', width= 10, command=self.OnCancel ).pack(side=RIGHT, padx=10, pady=10)
  71.         
  72.         self.index = 0
  73.         for dir in Restricted:
  74.             text = 'Restricted: %s' % dir
  75.             self.DirList.insert( END, text )
  76.             if dir == Current:
  77.                 self.index = self.DirList.size() - 1
  78.             
  79.         for dir in Trusted:
  80.             text = 'Trusted: %s' % dir 
  81.             self.DirList.insert( END, text )
  82.             if dir == Current:
  83.                 self.index = self.DirList.size() - 1
  84.  
  85.         self.DirList.select_set(self.index)
  86.  
  87.         self.focus_set()
  88.         self.grab_set()
  89.         self.wait_window()
  90.  
  91.     def OnSelectDir( self, event ):
  92.         'just update the directory in the listbox'
  93.         self.index = self.DirList.nearest(event.y)
  94.     
  95.     def OnOK( self ):
  96.         'on OK we record the selected dir in self.NewDir'
  97.         self.NewDir = self.AllDirs[ self.index ]
  98.         self.destroy()
  99.         pass
  100.     
  101.     def OnCancel( self ):
  102.         'on cancel, just leave without setting a new dir'
  103.         self.destroy()
  104.         pass
  105.                 
  106.     
  107. class ScriptManager(Frame):
  108.     ''' Script manager dialog.  Puts list of script names in the left column,
  109.         with details and command buttons on the right hand side
  110.     '''
  111.     def __init__(self, TrustedDirs, RestrictedDirs, AllScripts):
  112.         Frame.__init__(self)
  113.         self.pack(expand=YES, fill=BOTH)
  114.         self.master.title('Script Manager Version %s' % ScriptVersion )
  115.  
  116.         self.TrustedDirs = TrustedDirs
  117.         self.RestrictedDirs = RestrictedDirs
  118.         self.AllScripts = AllScripts
  119.         self.index = 0
  120.         
  121.         UpperFrame = Frame(self)
  122.         UpperFrame.pack(side=TOP, expand=YES, fill=BOTH)
  123.         # Add a label field telling the user what to do.
  124.         Label(UpperFrame, text= 'Select a script to work with:',
  125.                justify=LEFT).pack(side=TOP, expand=NO, anchor=W)
  126.  
  127.         # left side of the dialog is a listbox containing the scripts
  128.         self.ScriptList = Listbox(UpperFrame, height=15, width=35, selectmode=SINGLE, exportselection=FALSE)
  129.         self.ScriptList.pack(side=LEFT, expand=YES, fill=BOTH)
  130.         ScriptListScroll = Scrollbar(UpperFrame, command = self.ScriptList.yview)
  131.         ScriptListScroll.pack(side=LEFT, fill=Y)
  132.         self.ScriptList.configure(yscrollcommand=ScriptListScroll.set)
  133.  
  134.         # The user can select a script by clicking on it
  135.         self.ScriptList.bind("<Button-1>", self.OnSelectScript)
  136.  
  137.         # add command buttons
  138.         CommandFrame = Frame(UpperFrame)
  139.         CommandFrame.pack(side=RIGHT)
  140.         Button(CommandFrame, text='Delete', command=self.OnDelete, width = 20 ).pack(side=TOP, padx=10, pady=5)
  141.         Button(CommandFrame, text='Rename', command=self.OnRename, width = 20 ).pack(side=TOP, padx=10, pady=5)
  142.         Button(CommandFrame, text='Move', command=self.OnMoveScript, width = 20 ).pack(side=TOP, padx=10, pady=5)
  143.         
  144. ##        Button(CommandFrame, text='Run Silent', command=self.OnRunSilent, width = 20 ).pack(side=TOP, padx=10, pady=5)
  145. ##        Button(CommandFrame, text='Run Interactive', command=self.OnRunInteractive, width = 20 ).pack(side=TOP, padx=5, pady=5)
  146.         Button(CommandFrame, text='Text Edit', command=self.OnSourceEdit, width = 20 ).pack(side=TOP, padx=10, pady=5)
  147. ##        Button(CommandFrame, text='Edit w/PSP', command=self.OnGUIEdit, width = 20 ).pack(side=TOP, padx=10, pady=5)
  148.         
  149.         # Put the details on the bottom
  150.         InfoFrame = Frame(self)
  151.         InfoFrame.pack(side=TOP, anchor=W, expand=YES, fill=BOTH)
  152.         self.Author = Label(InfoFrame, text='Author:', width=80, anchor=W)
  153.         self.Author.pack(side=TOP, padx = 5, expand=YES, fill=X)
  154.         self.Copyright = Label(InfoFrame, text='Copyright:', width = 80,
  155.                                height=2, anchor=W, justify=LEFT)
  156.         self.Copyright.pack(side=TOP, padx = 5, expand=YES, fill=X)
  157.         self.Description = Label(InfoFrame, text='Description:', width=80,
  158.                                  height=2, anchor=W, justify=LEFT)
  159.         self.Description.pack(side=TOP, padx = 5, expand=YES, fill=X)
  160.         self.Location = Label( InfoFrame, text='Location:', width = 80,
  161.                                height=2,anchor=W, justify=LEFT)
  162.         self.Location.pack(side=TOP, padx = 5, expand=YES, fill=X)
  163.         self.Trusted = Label( InfoFrame, text='Trusted:', width = 80, anchor=W)
  164.         self.Trusted.pack(side=TOP, padx = 5, expand=YES, fill=X)
  165.         
  166.         self.Description.bind("<Configure>", self.UpdateWidth)
  167.         self.Copyright.bind("<Configure>", self.UpdateWidth)
  168.         self.Location.bind("<Configure>", self.UpdateWidth)
  169.         
  170.         # now drop the names into the list box
  171.         for Script in self.AllScripts:
  172.             self.ScriptList.insert(END, Script[0])
  173.  
  174.         # select the first entry            
  175.         self.ScriptList.select_set(0)
  176.         self.SelectIndex(0)
  177.         
  178.     def UpdateWidth(self, e):
  179.         'Resize the message widgets based on the width of the window (thanks to Gary Barton)'
  180.         WidgetWidth = e.widget.winfo_width() - 2*int(e.widget['bd'])
  181.         e.widget.config(wraplength = WidgetWidth)
  182.  
  183.     def OnSelectScript(self, event):
  184.         ' respond to a click event in the listbox - just determines the index and call SelectIndex'
  185.         if self.ScriptList.size() > 0:
  186.             # Find out which one is selected
  187.             self.SelectIndex( self.ScriptList.nearest(event.y) )
  188.  
  189.     def SelectIndex( self, index ):
  190.         'select a new script in the list box'
  191.         self.index = index
  192.         if self.AllScripts[index][3] is None: # lookup the properties if we don't have them already
  193.             self.GetScriptProperties( index )
  194.                 
  195.         Selected = self.AllScripts[index]
  196.         self.Author.config( text = 'Author: %s' % Selected[3]['Author'] )
  197.         self.Copyright.config( text = 'Copyright: %s' % Selected[3]['Copyright'] ) 
  198.         self.Description.config( text = 'Description: %s' % Selected[3]['Description'] )
  199.         self.Location.config( text = 'Location: %s' % Selected[1] )
  200.         self.Trusted.config( text= 'Trusted: %s' % ( ('No', 'Yes')[ Selected[2] ] ) )
  201.             
  202.  
  203.     def FullPath( self, index ):
  204.         'return the full path of a given index'
  205.         Path = os.path.join( self.AllScripts[index][1], self.AllScripts[index][0] )
  206.         Path += '.PspScript'
  207.         return Path
  208.  
  209.     def OnRename(self):
  210.         ''' prompt for a new scriptname.  I use the PSP GetString command
  211.             just because it is easier to specify a default value that way
  212.         '''
  213.         OldName = self.AllScripts[ self.index ][0]
  214.         OldPath = self.FullPath( self.index )
  215.         Result = App.Do( GlobalEnv, 'GetString', {
  216.             'DefaultText': OldName,
  217.             'DialogTitle': 'Rename script:',
  218.             'Prompt': 'New name:',
  219.             'MaxLength': 80,
  220.             'GeneralSettings': {
  221.                 'ExecutionMode': App.Constants.ExecutionMode.Interactive, 
  222.                 'AutoActionMode': App.Constants.AutoActionMode.Match
  223.                 }
  224.             })
  225.         
  226.         NewName = Result[ 'EnteredText' ]
  227.         if Result[ 'OKButton' ] and NewName != OldName:
  228.             # replace the name in our AllScripts list
  229.             Current = self.AllScripts[self.index]
  230.             NewScript = ( NewName, Current[1], Current[2], Current[3] )
  231.             self.AllScripts[ self.index ] = NewScript
  232.             
  233.             # replace the entry in the list box
  234.             self.ScriptList.delete( self.index )
  235.             self.ScriptList.insert( self.index, NewName )
  236.             self.ScriptList.select_set(self.index)
  237.  
  238.             # finally rename the file on disk
  239.             NewPath = os.path.join( self.AllScripts[self.index][1], NewName )
  240.             NewPath += '.PspScript'
  241.             os.rename( OldPath, NewPath )
  242.     
  243.     def OnDelete(self):
  244.         'delete the current selected script'
  245.         # better confirm it
  246.         index = self.index
  247.  
  248.         mode = os.stat( self.FullPath( index ) )
  249.         if mode[0] & S_IWRITE:
  250.             MakeWriteable = 0
  251.             prompt = 'OK to delete script %s?' % self.AllScripts[index][0]
  252.         else:
  253.             MakeWriteable = 1
  254.             prompt = 'OK to delete readonly script %s?'  % self.AllScripts[index][0]
  255.             
  256.         if askyesno( 'Delete Script', prompt ):
  257.             # remove it from disk
  258.             path = self.FullPath( index )
  259.             if MakeWriteable:
  260.                 os.chmod( path, S_IMODE(mode[0]) | S_IWRITE )
  261.             os.remove( path )            
  262.  
  263.             self.AllScripts.remove( self.AllScripts[index] ) # remove it from our list
  264.             self.ScriptList.delete(index)   # remove it from the list box
  265.             index = max(0, index-1)         # move the selection up
  266.             self.SelectIndex( index )       # select the next script
  267.             self.ScriptList.select_set(index)
  268.  
  269.     def OnMoveScript(self):
  270.         ''' bring up a list box with all of the configured directories and let the
  271.             user designate a new directory for the script
  272.         '''
  273.         DirDlg = ChooseDir( self.AllScripts[ self.index ][1], self.TrustedDirs, self.RestrictedDirs )
  274.  
  275.         # mark the current dialog as the foreign window again        
  276.         App.Do( GlobalEnv, 'StartForeignWindow', { 'WindowHandle': int(self.winfo_id()) } )
  277.         if DirDlg.NewDir != self.AllScripts[ self.index ][1]:
  278.             # we are moving the file
  279.             SrcPath = self.FullPath( self.index )   # where it starts
  280.             
  281.             DstPath = os.path.join( DirDlg.NewDir, self.AllScripts[ self.index ][0] ) # where it goes
  282.             DstPath += '.PspScript'
  283.  
  284.             # update the list            
  285.             Current = self.AllScripts[self.index]
  286.             NewScript = ( Current[0], DirDlg.NewDir, Current[2], Current[3] )
  287.             self.AllScripts[ self.index ] = NewScript
  288.            
  289.             os.rename( SrcPath, DstPath )
  290.         
  291.     def OnSourceEdit(self):
  292.         'launch the configured source editor on the selected file'
  293.         path = '"%s"' % self.FullPath( self.index )
  294.         os.spawnl( os.P_NOWAIT, SourceEditor, SourceEditor, path )  
  295.     
  296.     def OnGUIEdit(self):
  297.         'launch the PSP editor on the current file'
  298.         App.Do( GlobalEnv, 'EditScript', {
  299.             'ScriptPath': self.FullPath( self.index ),
  300.             'GeneralSettings': {
  301.                 'ExecutionMode': App.Constants.ExecutionMode.Silent, 
  302.                 'AutoActionMode': App.Constants.AutoActionMode.Match
  303.                 }
  304.             })
  305.         
  306.     def OnRunInteractive(self):
  307.         ''' run the current script.  This is not intended to be a good way to launch
  308.             scripts, but having the option here makes it easier to determine if a script
  309.             should be kept or deleted.
  310.             This variant runs interactively.  
  311.         '''
  312.         App.Do( GlobalEnv, 'RunScript', {
  313.             'FileName': self.FullPath( self.index ),
  314.             'ScriptExecutionMode': App.Constants.ExecutionMode.Interactive, 
  315.             'GeneralSettings': {
  316.                 'ExecutionMode': App.Constants.ExecutionMode.Silent, 
  317.                 'AutoActionMode': App.Constants.AutoActionMode.Match
  318.                 }
  319.             })
  320.         
  321.     def OnRunSilent(self):
  322.         ''' run the current script.  This is not intended to be a good way to launch
  323.             scripts, but having the option here makes it easier to determine if a script
  324.             should be kept or deleted.
  325.             This variant runs silently.  
  326.         '''
  327.         App.Do( GlobalEnv, 'RunScript', {
  328.             'FileName': self.FullPath( self.index ),
  329.             'ScriptExecutionMode': App.Constants.ExecutionMode.Silent, 
  330.             'GeneralSettings': {
  331.                 'ExecutionMode': App.Constants.ExecutionMode.Silent, 
  332.                 'AutoActionMode': App.Constants.AutoActionMode.Match
  333.                 }
  334.             })
  335.         
  336.     def GetScriptProperties( self, index ):
  337.         ''' open the script file from disk and extract the data from the script properties
  338.             method.  Updates the self.AllScripts member to include the properties
  339.         '''
  340.         script = self.AllScripts[index]
  341.         sname = self.FullPath( index )
  342.         file = open( sname, 'r' )
  343.         ScriptContents = ''
  344.         lines = file.readlines()
  345.         file.close()
  346.         for line in lines:
  347.             trimmed = line.rstrip()
  348.             trimmed += '\n'
  349.             ScriptContents += trimmed
  350.  
  351.         # load the script and try executing the script properties method        
  352.         try:
  353.             gn = {}
  354.             ln = {}
  355.             Props = {}
  356.             Props[ 'Author' ] = 'Unknown'
  357.             Props[ 'Copyright' ] = 'Unknown'
  358.             Props[ 'Description' ] = 'Unknown'
  359.             exec( ScriptContents, gn, ln )
  360.             if ln.has_key( 'ScriptProperties' ):
  361.                 Props = ln[ 'ScriptProperties' ]()
  362.             else:
  363.                 raise ValueError
  364.         except:
  365.             print 'Unable to determine script properties for %s' % script[0]
  366.             
  367.         NewEntry = ( script[0], script[1], script[2], Props )
  368.         self.AllScripts[index] = NewEntry            
  369.     
  370. def Do(Environment):
  371.     global GlobalEnv
  372.     global SourceEditor
  373.     
  374.     GlobalEnv = Environment
  375.     
  376.     # start by getting all the script directories.  The behavior of the file locations object
  377.     # is that for locations that include subdirectories it will return all of the subdirectories
  378.     # as well as the parent location. So there is no need for us to search recursively.
  379.     DirSet = App.Do( Environment, 'ReturnFileLocations' )
  380.     SourceEditor = DirSet[ 'PythonSourceEditor'][0].replace( '\\\\', '\\' )
  381.     
  382.     TrustedDirs = []
  383.     RestrictedDirs = []
  384.     AllScripts = []
  385.    
  386.     # the return value of Return file locations has double backslashes, so lets convert
  387.     # them to singles
  388.     for dir in DirSet[ 'TrustedScripts' ]:
  389.         TrustedDirs.append( dir.replace( '\\\\', '\\' ) )
  390.     for dir in DirSet[ 'RestrictedScripts' ]:
  391.         RestrictedDirs.append( dir.replace( '\\\\', '\\' ) )
  392.         
  393.     AllDirs = TrustedDirs + RestrictedDirs    
  394.     for SearchDir in AllDirs:
  395.         # concatenate the dir and file spec together
  396.         SearchPath = os.path.join( SearchDir, '*.PspScript' )
  397.         
  398.         # glob will search the directory and return a list
  399.         GlobbedFiles = glob.glob( SearchPath )
  400.         
  401.         for FileSpec in GlobbedFiles:               # now iterate the list
  402.             # break the full pathspec apart            
  403.             (Path, ScriptName) = os.path.split(FileSpec)
  404.             BareName = os.path.splitext(ScriptName)[0]
  405.  
  406.             # keep track of what is restricted vs trusted            
  407.             if Path in TrustedDirs:
  408.                 Trusted = 1
  409.             else:
  410.                 Trusted = 0
  411.  
  412.             # assemble what we know about the script so far                
  413.             ScriptInfo = ( BareName, Path, Trusted, None )
  414.             
  415.             AllScripts.append( ScriptInfo )       # and add each one to our script list
  416.      
  417.     if len(AllScripts) == 0:                # going to have a problem if there aren't any scripts
  418.         App.Do(Environment,  'MsgBox', {
  419.                     'Buttons': App.Constants.MsgButtons.OK, 
  420.                     'Icon': App.Constants.MsgIcons.Stop, 
  421.                     'Text': 'No scripts found to manage', 
  422.                     })
  423.         return
  424.  
  425.     # sort the scripts by name
  426.     AllScripts.sort( lambda a, b: cmp( a[0].lower(), b[0].lower() ))
  427.   
  428.     Mgr = ScriptManager( TrustedDirs, RestrictedDirs, AllScripts )
  429.  
  430.     # tell PSP that a foreign dialog is running.  This causes PSP to do some additional
  431.     # work to keep the UI updating properly and to prevent the script window from going
  432.     # behind PSP.
  433.     App.Do( Environment, 'StartForeignWindow', { 'WindowHandle': int(Mgr.winfo_id()) } )
  434.     Mgr.mainloop()
  435.     App.Do( Environment, 'StartForeignWindow', { 'WindowHandle': 0 } )
  436.  
  437.         
  438.