#!/usr/bin/env python
#-*- coding: utf-8 -*-
authors='Sean Bogie, MareroQ, Ofnuts'
import pygtk,gtk,os,glob,shutil,ConfigParser,codecs,copy,zipfile,time
pygtk.require('2.0')
from gimpfu import *
def trace(s):
# print s
pass
def brushes_callback():
call_back('brushes')
def dynamics_callback():
call_back('dynamics')
def fonts_callback():
call_back('fonts')
def gradients_callback():
call_back('gradients')
def palettes_callback():
call_back('palettes')
def patterns_callback():
call_back('patterns')
def scripts_callback():
call_back('scripts')
def call_back(addonType):
trace('Called in %s callback' % addonType)
configuration=Configuration(builtinSections)
configuration.loadConfiguration()
manager=AddonCollectionManager(configuration,commonConfigVars,addonType)
manager.showMainDialog()
manager.main()
builtinSections={
'all': {
#'enable':'brushes scripts',
'enable':'brushes dynamics fonts gradients palettes patterns scripts',
'addons_active':'{GimpUser}/{type}',
'addons_stored':'{GimpUser}/{type}_storage',
'menu_location':'<{Type}>',
'menu_entry':'{Type} sets...',
'menu_description':'Manage {type} sets...',
'dialog_title':'{Type} sets manager'
},
'brushes':{
'extensions':'.gbr .vbr .gih .abr .GBR .VBR .GIH'
},
'dynamics':{
'extensions':'.gdyn .GDYN'
},
'fonts':{
'extensions':'.ttf .otf .TTF .OTF'
},
'gradients':{
'extensions':'.ggr .GGR'
},
'palettes':{
'extensions':'.pal .PAL'
},
'patterns':{
'extensions':'.png .pat .PNG .PAT'
},
'scripts':{
'menu_location':'<Image>/Help',
'extensions':'.scm .SCM',
},
}
# gradients, dynamics, scripts, palettes
# Technical data specific to each type
initData={
'brushes':{
'gimpRefresh':'gimp_brushes_refresh',
'gimpProcedure':'addon-manager-brushes',
'gimpCallback':brushes_callback
},
'dynamics':{
'gimpRefresh':'gimp_dynamics_refresh',
'gimpProcedure':'addon-manager-dynamics',
'gimpCallback':dynamics_callback
},
'fonts':{
'gimpRefresh':'gimp_fonts_refresh',
'gimpProcedure':'addon-manager-fonts',
'gimpCallback':fonts_callback
},
'gradients':{
'gimpRefresh':'gimp_gradients_refresh',
'gimpProcedure':'addon-manager-gradients',
'gimpCallback':gradients_callback
},
'palettes':{
'gimpRefresh':'gimp_palettes_refresh',
'gimpProcedure':'addon-manager-palettes',
'gimpCallback':palettes_callback
},
'patterns':{
'gimpRefresh':'gimp_patterns_refresh',
'gimpProcedure':'addon-manager-patterns',
'gimpCallback':patterns_callback
},
'scripts':{
'gimpRefresh':'script-fu-refresh',
'gimpProcedure':'addon-manager-scripts',
'gimpCallback':scripts_callback
}
}
commonConfigVars={
'{UserHome}':os.path.expanduser('~/')[:-1],
'{GimpUser}':gimp.directory,
'{GimpData}':gimp.data_directory,
'{GimpPlugin}':gimp.plug_in_directory
}
class Configuration(object):
'''Class handling all the configuration data'''
def __init__(self,defaults):
self.configData=ConfigParser.ConfigParser()
for section in defaults:
self.configData.add_section(section)
options=defaults[section]
for option in options:
self.configData.set(section,option,options[option])
trace('Configuration initialized OK')
def loadConfiguration(self):
pluginDir=os.path.join(gimp.directory,'plug-ins')
configFilePath=os.path.join(pluginDir,'addonCollectionManager.ini')
trace('Reading configuration file %s' % configFilePath)
try:
with codecs.open(configFilePath, 'r', encoding='utf-8') as configFile:
self.configData.readfp(configFile)
trace('Configuration file %s read successfully' % configFilePath)
except Exception as e:
trace('Configuration file %s not found or not readable: %s' % (configFilePath,e))
def getValue(self,section,option,configVars):
value=self.configData.get(section,option)
if configVars:
for v in configVars:
value=value.replace(v,configVars[v])
return value
def getAddonTypeSection(self,addonType,configVars):
addonTypeData={}
for option in self.configData.options('all'):
addonTypeData[option]=self.getValue('all',option,configVars)
for option in self.configData.options(addonType):
addonTypeData[option]=self.getValue(addonType,option,configVars)
return addonTypeData
# Classes to handle the places where we keep the addOns
class AddonContainer(object):
'''Parent of classes that handle the places where we keep the addons '''
def __init__(self,containerPath,activeDir,extensions):
self.containerPath=containerPath
self.containerName=os.path.basename(containerPath)
self.activeDir=activeDir
self.extensions=extensions
def storedAddons(self):
'''returns the list of names of stored addons.
This method is implemented by the derived classes'''
pass
def addonName(self,fullname):
'''Returns just the file name of any addons.
This method is implemented by the derived classes'''
pass
def activeAddonPath(self,storedAddonName):
'''computes the activaed addon name'''
return self.activeDir+'/'+self.containerName+'-'+self.addonName(storedAddonName)
def deactivate(self,storedAddon):
'''Removes the addon from active storage'''
filePath=self.activeAddonPath(storedAddon)
try:
os.remove(filePath)
except:
trace('Cannot de-activate %s' % filePath)
def activate(self,storedAddon):
'''Implemented by derived classes'''
pass
class AddonDirectory(AddonContainer):
'''Class to handle directory with addons'''
def __init__(self,containerPath,activeDir,extensions):
super(AddonDirectory, self).__init__(containerPath,activeDir,extensions)
def storedAddons(self):
storedAddons=[f for f in glob.glob(self.containerPath+u'/*') if f.endswith(self.extensions)]
trace('%d addons stored in %s' % (len(storedAddons),self.containerPath))
return storedAddons
def addonName(self,fullname):
return fullname.split('/')[-1]
def activate(self,storedAddon):
activeAddonPath=self.activeAddonPath(storedAddon)
trace('Activating %s as %s' % (storedAddon,activeAddonPath))
if 'symlink' in dir(os):
os.symlink(storedAddon,activeAddonPath)
else:
shutil.copyfile(storedAddon,activeAddonPath)
class AddonZip(AddonContainer):
'''Class to handle zip with addons'''
def __init__(self,containerPath,activeDir,extensions):
super(AddonZip, self).__init__(containerPath,activeDir,extensions)
self.zipFile=zipfile.ZipFile(containerPath)
def storedAddons(self):
storedAddons=[f for f in self.zipFile.namelist() if f.endswith(self.extensions)]
trace('%d addons stored in %s' % (len(storedAddons),self.containerPath))
return storedAddons
def addonName(self,fullname):
return fullname.split('/')[-1]
def activate(self,storedAddon):
activeAddonPath=self.activeAddonPath(storedAddon)
trace('Activating %s as %s' % (storedAddon,activeAddonPath))
try:
activeAddon=open(activeAddonPath,"wb")
activeAddon.write(self.zipFile.read(storedAddon))
except Exception as e:
trace('Error activating %s from %s/%s: %s' % (activeAddonPath,self.containerName,storedAddon,e))
finally:
activeAddon.close()
class AddonCollectionManager(object):
def __init__(self,config,commonConfigVars,addonType):
self.addonType=addonType
self.configVars=self.extendConfigVars(commonConfigVars)
self.config=config.getAddonTypeSection(addonType,self.configVars)
self.data=initData[addonType]
self.addonsActive=self.config['addons_active']
self.addonsStored=self.config['addons_stored']
self.extensions=tuple(self.config['extensions'].split())
self.activelist=os.path.join(self.addonsStored,'.active')
self.containers = self.getContainers()
self.activeContainersNames=self.getActiveContainersNames()
self.statusDialog=None
trace('Manager of %s created OK' % addonType)
def extendConfigVars(self,commonConfigVars):
configVars=copy.copy(commonConfigVars)
configVars['{type}']=self.addonType
configVars['{Type}']=self.addonType.capitalize()
return configVars
def getContainers(self):
containers={}
trace('Searching directories as %s for %s' % (self.addonsStored+u'/*',self.addonType))
dirs=[d for d in glob.glob(self.addonsStored+u'/*') if os.path.isdir(d)]
trace('%d directories as %s for %s' % (len(dirs),self.addonsStored+u'/*',self.addonType))
for d in dirs:
containers[os.path.basename(d)]=AddonDirectory(d,self.addonsActive,self.extensions)
zips=[z for z in glob.glob(self.addonsStored+u'/*.zip') if os.path.isfile(z) and zipfile.is_zipfile(z)]
for z in zips:
containers[os.path.basename(z)]=AddonZip(z,self.addonsActive,self.extensions)
return containers
def getActiveContainersNames(self):
activeContainersNames=[]
try:
with codecs.open(self.activelist,'r',encoding='utf-8') as f:
activeContainersNames=f.read().splitlines()
except IOError:
pass;
return activeContainersNames
def saveActiveContainersNames(self,activeContainersNames):
try:
with codecs.open(self.activelist,'w',encoding='utf-8') as f:
f.write('\n'.join(activeContainersNames))
except Exception as e:
trace('Cannot write back active containers list to %s: %s' % (activelist,e))
def deleteStatusDialog(self,widget,event,data=None):
return True
def showStatusDialog(self):
if not self.statusDialog:
self.statusDialog = gtk.Dialog("Please Wait...", self.mainDialog,gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT)
self.statusDialog.connect("delete_event", self.deleteStatusDialog)
self.statusDialog.set_size_request(200, 100)
self.statuslabel = gtk.Label("Status:")
self.statusDialog.vbox.pack_start(self.statuslabel, False, False, 0)
self.progressbar = gtk.ProgressBar()
self.statusDialog.vbox.pack_start(self.progressbar, False, True, 0)
self.statuslabel.show()
self.progressbar.show()
self.statusDialog.show()
def hideStatusDialog(self):
self.statuslabel.hide()
self.progressbar.hide()
self.statusDialog.hide()
def doUpdateActiveAddons(self,deactivatedContainersNames,activatedContainersNames):
totalUpdates=0
deactivatedContainersLists={}
for containerName in deactivatedContainersNames:
addons=self.containers[containerName].storedAddons()
totalUpdates=totalUpdates+len(addons)
deactivatedContainersLists[containerName]=addons
activatedContainersLists={}
for containerName in activatedContainersNames:
addons=self.containers[containerName].storedAddons()
totalUpdates=totalUpdates+len(addons)
activatedContainersLists[containerName]=addons
self.showStatusDialog()
currentUpdates = 0
self.progressbar.set_fraction(0.)
self.statuslabel.set_text("Deactivating removed addons...")
for containerName in deactivatedContainersLists:
container=self.containers[containerName]
addons=deactivatedContainersLists[containerName]
for addon in addons:
container.deactivate(addon)
currentUpdates=currentUpdates+1
self.progressbar.set_fraction(float(currentUpdates)/totalUpdates)
gtk.main_iteration()
self.statuslabel.set_text("Activating added addons...")
for containerName in activatedContainersLists:
container=self.containers[containerName]
addons=activatedContainersLists[containerName]
for addon in addons:
container.activate(addon)
currentUpdates=currentUpdates+1
self.progressbar.set_fraction(float(currentUpdates)/totalUpdates)
gtk.main_iteration()
self.statuslabel.set_text("Refreshing list...")
gtk.main_iteration()
try:
trace('Refresh: %s' % self.data['gimpRefresh'])
pdb[self.data['gimpRefresh']]()
trace('Refresh OK')
except:
pass
self.hideStatusDialog()
def clickedOK(self, widget, data=None):
trace('OK sensed')
oldActiveContainersNames = set(self.getActiveContainersNames())
newActiveContainersNames = set([unicode(button.get_label()) for button in self.checkButtons if button.get_active()])
activatedContainersNames=newActiveContainersNames-oldActiveContainersNames
deactivatedContainersNames=oldActiveContainersNames-newActiveContainersNames
trace('Old containers; %s' % oldActiveContainersNames)
trace('New containers; %s' % newActiveContainersNames)
trace('Activated containers; %s' % activatedContainersNames)
trace('Deactivated containers; %s' % deactivatedContainersNames)
self.doUpdateActiveAddons(deactivatedContainersNames,activatedContainersNames)
self.saveActiveContainersNames(newActiveContainersNames)
def destroyMainDialog(self, widget, data=None):
trace('Destroying main')
gtk.main_quit()
def showMainDialog(self):
trace('Creating main dialog')
self.mainDialog=gtk.Dialog()
self.mainDialog.connect("destroy", self.destroyMainDialog)
self.mainDialog.set_title(self.config['dialog_title'])
self.mainDialog.set_border_width(10)
self.mainDialog.set_size_request(200, 400)
trace('Main dialog created')
scrollBox = gtk.ScrolledWindow()
scrollBox.set_border_width(10)
scrollBox.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
self.mainDialog.vbox.pack_start(scrollBox, True, True, 0)
scrollBox.show()
trace('Scrollbox created')
vbox = gtk.VBox()
self.checkButtons=[]
for containerName in sorted(self.containers.keys()):
button=gtk.CheckButton(containerName)
button.set_active(containerName in self.activeContainersNames)
self.checkButtons.append(button)
vbox.pack_start(button, False, False, 1)
button.show()
scrollBox.add_with_viewport(vbox)
vbox.show()
trace('VBox created')
cancelButton = gtk.Button(stock=gtk.STOCK_CLOSE)
cancelButton.connect_object("clicked", gtk.Widget.destroy, self.mainDialog)
self.mainDialog.action_area.pack_start(cancelButton, True, True, 0)
cancelButton.show()
trace('Cancel button created')
okButton = gtk.Button(stock=gtk.STOCK_OK)
okButton.connect("clicked", self.clickedOK)
self.mainDialog.action_area.pack_start(okButton, True, True, 0)
okButton.show()
trace('OK button created')
self.mainDialog.show()
gtk.main_iteration()
def main(self):
gtk.main()
def register(self):
register(
self.data['gimpProcedure'],
self.config['menu_description'],
'Add-on manager. This code takes no arguments, but isn\'t meant to be called programmatically.',
authors,authors,'2013',
self.config['menu_entry'],
'',[],[],
self.data['gimpCallback'],
menu=self.config['menu_location']
)
### Initialization
configuration=Configuration(builtinSections)
configuration.loadConfiguration()
# Initialize the managers listed in the ini file
enabled=configuration.getValue('all','enable',None).split()
for addonType in initData:
if addonType in enabled:
trace('Creating manager for %s' % addonType)
manager=AddonCollectionManager(configuration,commonConfigVars,addonType)
manager.register()
main()