Salome HOME
updated copyright message
[modules/kernel.git] / src / AppQuickStart / app-quickstart.py
1 #! /usr/bin/env python3
2 #  -*- coding: iso-8859-1 -*-
3 # Copyright (C) 2007-2023  CEA, EDF, OPEN CASCADE
4 #
5 # This library is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU Lesser General Public
7 # License as published by the Free Software Foundation; either
8 # version 2.1 of the License, or (at your option) any later version.
9 #
10 # This library is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 # Lesser General Public License for more details.
14 #
15 # You should have received a copy of the GNU Lesser General Public
16 # License along with this library; if not, write to the Free Software
17 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
18 #
19 # See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
20 #
21
22 import os
23 import shutil
24 import argparse
25
26 # Options of this script
27 def profileQuickStartParser() :
28
29     parser = argparse.ArgumentParser( usage = "usage: python app-quickstart.py [options]" )
30
31     parser.add_argument('-p',
32                       "--prefix",
33                       metavar="</Path/to/the/sources/of/application>",
34                       action="store",
35                       dest="prefix",
36                       default='.',
37                       help="Where the application's sources will be generated. [Default : '.']")
38
39     parser.add_argument('-m',
40                       "--modules",
41                       metavar="<module1,module2,...>",
42                       action="store",
43                       dest="modules",
44                       default='KERNEL,GUI',
45                       help="List of the application's modules. [Default : KERNEL,GUI]")
46
47     parser.add_argument('-n',
48                       "--name",
49                       action="store",
50                       dest="name",
51                       help="Name of the application")
52
53     parser.add_argument('-v',
54                       "--version",
55                       action="store",
56                       dest="version",
57                       default='1.0',
58                       help="Version of the application. [Default : 1.0]")
59
60     parser.add_argument('-s',
61                       "--slogan",
62                       action="store",
63                       dest="slogan",
64                       default='',
65                       help="Slogan of the application.")
66
67     parser.add_argument('-f',
68                       "--force",
69                       action="store_true",
70                       dest="force",
71                       help="Overwrites existing sources")
72
73     return parser
74
75
76
77 #Create the splash screen
78 def profileGenerateSplash( resources_dir, appname, version, subtext ):
79     import Image
80     import ImageDraw
81     import ImageFont
82
83     if isinstance(appname, bytes):
84         uname = str(appname, 'UTF-8')
85     else:
86         uname = appname
87     if isinstance(version, bytes):
88         uversion = str(version, 'UTF-8')
89     else:
90         uversion = version
91
92     # fonts
93     fontbig = ImageFont.truetype( os.path.join( resources_dir, 'Anita semi square.ttf' ), 64)
94     fontsmall = ImageFont.truetype( os.path.join( resources_dir, 'Anita semi square.ttf' ), 20)
95     textColor = "rgb(255, 250, 250)"
96     shadowColor = "rgb(0, 0, 0)"
97
98     # dimensions
99     nbcar = len(uname)
100     width = 600
101     if nbcar > 12:
102         width = min( width*nbcar//12, 1024) #a little more
103     height = 300
104     borderX = 30 #50
105     borderY = 3 #30
106     shadowX = 2
107     shadowY = shadowX
108
109     # load background image 
110     f0 = os.path.join( resources_dir, "background.png" )
111     im = Image.open(f0)
112     im = im.resize( ( width, height ) )
113     draw = ImageDraw.Draw(im)
114
115     # add the name of the application
116     iw, ih = draw.textsize(uname, font=fontbig)
117     x = (width - iw) / 2.0 # horizontal center
118     y = (height - ih) / 2.0 # vertical center
119     draw.text((x+shadowX, y+shadowY), uname, font=fontbig, fill=shadowColor)
120     draw.text((x, y), uname, font=fontbig, fill=textColor)
121
122     # add subtext
123     if len(subtext) > 0:
124         iw, ih = draw.textsize(subtext, font=fontsmall)
125         draw.text((borderX+shadowX, height+shadowY-borderY-ih),
126                   subtext, font=fontsmall, fill=shadowColor)
127         draw.text((borderX, height-borderY-ih),
128                   subtext, font=fontsmall, fill=textColor)
129
130     # add the version if any
131     if len(version) > 0:
132         iw, ih = draw.textsize(uversion, font=fontsmall)
133         draw.text((width+shadowX-borderX-iw, height+shadowY-borderY-ih),
134                   uversion, font=fontsmall, fill=shadowColor)
135         draw.text((width-borderX-iw, height-borderY-ih),
136                   uversion, font=fontsmall, fill=textColor)
137
138     del draw
139     return im
140
141
142 #Create the application logo
143 def profileGenerateLogo( appname, font ):
144     import Image
145     import ImageDraw
146
147     if isinstance(appname, bytes):
148         uname = str(appname, 'UTF-8')
149     else:
150         uname = appname
151
152     # evaluate size before deleting draw
153     im = Image.new( "RGBA", (1, 1), (0, 0, 0, 0) )
154     draw = ImageDraw.Draw( im )
155
156     im = Image.new( "RGBA", draw.textsize( uname, font=font ), (0, 0, 0, 0) )
157     draw = ImageDraw.Draw(im)
158     draw.text( (0+1, 0), uname, font=font, fill="rgb(0, 0, 0)" )
159     draw.text( (0, -1), uname, font=font, fill="rgb(191, 191, 191)" )
160
161     del draw
162     return im
163
164
165 # Check if filename is a binary file
166 def is_binary(filename):
167     """ returns True if filename is a binary file
168     (from https://stackoverflow.com/a/7392391/2531279)
169     """
170     textchars = bytearray({7,8,9,10,12,13,27} | set(range(0x20, 0x100)) - {0x7f})
171     with open(filename, 'rb') as f:
172         s = f.read(512)
173     return bool(s.translate(None, textchars))
174
175
176 #Replace strings in the template
177 def profileReplaceStrings( src, dst, args) :
178     if is_binary(src):
179         shutil.copyfile(src, dst)
180     else:
181         with open( dst, "w") as fout, \
182                 open( src, "r") as fin:
183             for line in fin:
184                 if args.modules == '_NO_' and '[LIST_OF_MODULES]' in line:
185                     line = ''
186                 l = line.replace( '[LIST_OF_MODULES]', args.modules )
187                 l = l.replace( '[VERSION]', args.version )
188                 l = l.replace( '[SLOGAN]', args.slogan )
189                 l = l.replace( '[NAME_OF_APPLICATION]', args.name.upper() )
190                 l = l.replace( '[Name_of_Application]', args.name )
191                 l = l.replace( '(name_of_application)', args.name.lower() )
192                 fout.write( l )
193
194
195 #Generation of a template profile sources
196 def profileGenerateSources( args ) :
197
198     #Set name of several directories
199     app_dir = args.prefix
200     app_resources_dir = os.path.join( app_dir, "resources" )
201     kernel_root_dir = os.environ["KERNEL_ROOT_DIR"]
202     bin_salome_dir = os.path.join( kernel_root_dir, "bin", "salome" )
203     kernel_resources_dir = os.path.join( kernel_root_dir, "share", "salome", "resources", "kernel" )
204     template_dir = os.path.join( kernel_resources_dir, "app-template" )
205
206     #Check if the directory of the sources already exists and delete it
207     if os.path.exists( app_dir ) :
208         if not args.force :
209             print("Directory %s already exists." %app_dir)
210             print("Use option --force to overwrite it.")
211             return
212         else :
213             shutil.rmtree( app_dir )
214
215     #Copy template directory
216     os.mkdir( app_dir )
217     for root, dirs, files in os.walk( template_dir ) :
218         dst_dir = root.replace( template_dir, app_dir ) 
219         for d in dirs :
220             os.mkdir( os.path.join( dst_dir, d ) )
221         for f in files :
222             profileReplaceStrings( os.path.join( root, f ), os.path.join( dst_dir, f ), args)
223
224     #Complete source directory
225     contextFiles = [ "salomeContext.py", "salomeContextUtils.py", "parseConfigFile.py" ]
226     for f in contextFiles :
227         shutil.copy( os.path.join( bin_salome_dir, f ), os.path.join( app_dir, "src" ) )
228
229     #Search for python modules Image, ImageDraw and ImageFont
230     try:
231         import imp
232         imp.find_module('Image')
233         imp.find_module('ImageDraw')
234         imp.find_module('ImageFont')
235         found = True
236     except ImportError:
237         found = False
238
239     #Generate logo and splash
240     logo_destination = os.path.join( app_resources_dir, 'app_logo.png')
241     splash_destination = os.path.join( app_resources_dir, 'splash.png')
242     about_destination = os.path.join( app_resources_dir, 'about.png')
243     if found :
244         import ImageFont
245         font = ImageFont.truetype( os.path.join( kernel_resources_dir, "Anita semi square.ttf" ) , 18 )
246
247         #Generate and save logo
248         app_logo = profileGenerateLogo( args.name, font )
249         app_logo.save( logo_destination, "PNG" )
250
251         #Generate and splash screen and about image
252         if args.slogan :
253             subtext = args.slogan
254         else :
255             subtext = "Powered by SALOME"
256         im = profileGenerateSplash( kernel_resources_dir, args.name, args.version, subtext )
257         im.save( splash_destination, "PNG" )
258         im.save( about_destination, "PNG" )
259     else :
260         gui_resources_dir = os.path.join( os.environ["GUI_ROOT_DIR"], "share", "salome", "resources", "gui" )
261         logo_name = os.path.join( gui_resources_dir, "icon_applogo.png" )
262         if os.path.exists( logo_name ) :
263             shutil.copy( logo_name, logo_destination )
264         about_name = os.path.join( gui_resources_dir, "icon_about.png" )
265         if os.path.exists( about_name ) :
266             shutil.copy( about_name, about_destination )
267             shutil.copy( about_name, splash_destination )
268
269     #End of script
270     print("Sources of %s were generated in %s." %( args.name, app_dir ))
271
272
273 # -----------------------------------------------------------------------------
274
275 if __name__ == '__main__':
276     #Get optional and positional args
277     args = profileQuickStartParser().parse_args()
278
279     #Check name of the application
280     if not args.name :
281         raise RuntimeError( "A name must be given to the application. Please use option --name." )
282
283     #Check if the prefix's parent is a directory
284     if not os.path.isdir( os.path.dirname( args.prefix ) ) :
285         raise RuntimeError( "%s is not a directory." % os.path.dirname( args.prefix ) )
286
287     #Generate sources of the profile
288     profileGenerateSources( args )