1#!/usr/bin/env python
2# Copyright (c) 2012 The Chromium Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6'''The 'grit resize' tool.
7'''
8
9import getopt
10import os
11
12from grit import grd_reader
13from grit import pseudo
14from grit import util
15from grit.format import rc
16from grit.format import rc_header
17from grit.node import include
18from grit.tool import interface
19
20
21# Template for the .vcproj file, with a couple of [[REPLACEABLE]] parts.
22PROJECT_TEMPLATE = '''\
23<?xml version="1.0" encoding="Windows-1252"?>
24<VisualStudioProject
25	ProjectType="Visual C++"
26	Version="7.10"
27	Name="[[DIALOG_NAME]]"
28	ProjectGUID="[[PROJECT_GUID]]"
29	Keyword="Win32Proj">
30	<Platforms>
31		<Platform
32			Name="Win32"/>
33	</Platforms>
34	<Configurations>
35		<Configuration
36			Name="Debug|Win32"
37			OutputDirectory="Debug"
38			IntermediateDirectory="Debug"
39			ConfigurationType="1"
40			CharacterSet="2">
41		</Configuration>
42	</Configurations>
43	<References>
44	</References>
45	<Files>
46		<Filter
47			Name="Resource Files"
48			Filter="rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx"
49			UniqueIdentifier="{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}">
50			<File
51				RelativePath=".\[[DIALOG_NAME]].rc">
52			</File>
53		</Filter>
54	</Files>
55	<Globals>
56	</Globals>
57</VisualStudioProject>'''
58
59
60# Template for the .rc file with a couple of [[REPLACEABLE]] parts.
61# TODO(joi) Improve this (and the resource.h template) to allow saving and then
62# reopening of the RC file in Visual Studio.  Currently you can only open it
63# once and change it, then after you close it you won't be able to reopen it.
64RC_TEMPLATE = '''\
65// This file is automatically generated by GRIT and intended for editing
66// the layout of the dialogs contained in it.  Do not edit anything but the
67// dialogs.  Any changes made to translateable portions of the dialogs will
68// be ignored by GRIT.
69
70#include "resource.h"
71#include <winresrc.h>
72#ifdef IDC_STATIC
73#undef IDC_STATIC
74#endif
75#define IDC_STATIC (-1)
76
77LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL
78
79#pragma code_page([[CODEPAGE_NUM]])
80
81[[INCLUDES]]
82
83[[DIALOGS]]
84'''
85
86
87# Template for the resource.h file with a couple of [[REPLACEABLE]] parts.
88HEADER_TEMPLATE = '''\
89// This file is automatically generated by GRIT.  Do not edit.
90
91#pragma once
92
93// Edit commands
94#define ID_EDIT_CLEAR                   0xE120
95#define ID_EDIT_CLEAR_ALL               0xE121
96#define ID_EDIT_COPY                    0xE122
97#define ID_EDIT_CUT                     0xE123
98#define ID_EDIT_FIND                    0xE124
99#define ID_EDIT_PASTE                   0xE125
100#define ID_EDIT_PASTE_LINK              0xE126
101#define ID_EDIT_PASTE_SPECIAL           0xE127
102#define ID_EDIT_REPEAT                  0xE128
103#define ID_EDIT_REPLACE                 0xE129
104#define ID_EDIT_SELECT_ALL              0xE12A
105#define ID_EDIT_UNDO                    0xE12B
106#define ID_EDIT_REDO                    0xE12C
107
108
109[[DEFINES]]
110'''
111
112
113class ResizeDialog(interface.Tool):
114  '''Generates an RC file, header and Visual Studio project that you can use
115with Visual Studio's GUI resource editor to modify the layout of dialogs for
116the language of your choice.  You then use the RC file, after you resize the
117dialog, for the language or languages of your choice, using the <skeleton> child
118of the <structure> node for the dialog.  The translateable bits of the dialog
119will be ignored when you use the <skeleton> node (GRIT will instead use the
120translateable bits from the original dialog) but the layout changes you make
121will be used.  Note that your layout changes must preserve the order of the
122translateable elements in the RC file.
123
124Usage: grit resize [-f BASEFOLDER] [-l LANG] [-e RCENCODING] DIALOGID*
125
126Arguments:
127  DIALOGID        The 'name' attribute of a dialog to output for resizing.  Zero
128                  or more of these parameters can be used.  If none are
129                  specified, all dialogs from the input .grd file are output.
130
131Options:
132
133  -f BASEFOLDER   The project will be created in a subfolder of BASEFOLDER.
134                  The name of the subfolder will be the first DIALOGID you
135                  specify.  Defaults to '.'
136
137  -l LANG         Specifies that the RC file should contain a dialog translated
138                  into the language LANG.  The default is a cp1252-representable
139                  pseudotranslation, because Visual Studio's GUI RC editor only
140                  supports single-byte encodings.
141
142  -c CODEPAGE     Code page number to indicate to the RC compiler the encoding
143                  of the RC file, default is something reasonable for the
144                  language you selected (but this does not work for every single
145                  language).  See details on codepages below.  NOTE that you do
146                  not need to specify the codepage unless the tool complains
147                  that it's not sure which codepage to use.  See the following
148                  page for codepage numbers supported by Windows:
149                  http://www.microsoft.com/globaldev/reference/wincp.mspx
150
151  -D NAME[=VAL]   Specify a C-preprocessor-like define NAME with optional
152                  value VAL (defaults to 1) which will be used to control
153                  conditional inclusion of resources.
154
155
156IMPORTANT NOTE:  For now, the tool outputs a UTF-8 encoded file for any language
157that can not be represented in cp1252 (i.e. anything other than Western
158European languages).  You will need to open this file in a text editor and
159save it using the codepage indicated in the #pragma code_page(XXXX) command
160near the top of the file, before you open it in Visual Studio.
161
162'''
163
164  # TODO(joi) It would be cool to have this tool note the Perforce revision
165  # of the original RC file somewhere, such that the <skeleton> node could warn
166  # if the original RC file gets updated without the skeleton file being updated.
167
168  # TODO(joi) Would be cool to have option to add the files to Perforce
169
170  def __init__(self):
171    self.lang = pseudo.PSEUDO_LANG
172    self.defines = {}
173    self.base_folder = '.'
174    self.codepage_number = 1252
175    self.codepage_number_specified_explicitly = False
176
177  def SetLanguage(self, lang):
178    '''Sets the language code to output things in.
179    '''
180    self.lang = lang
181    if not self.codepage_number_specified_explicitly:
182      self.codepage_number = util.LanguageToCodepage(lang)
183
184  def GetEncoding(self):
185    if self.codepage_number == 1200:
186      return 'utf_16'
187    if self.codepage_number == 65001:
188      return 'utf_8'
189    return 'cp%d' % self.codepage_number
190
191  def ShortDescription(self):
192    return 'Generate a file where you can resize a given dialog.'
193
194  def Run(self, opts, args):
195    self.SetOptions(opts)
196
197    own_opts, args = getopt.getopt(args, 'l:f:c:D:')
198    for key, val in own_opts:
199      if key == '-l':
200        self.SetLanguage(val)
201      if key == '-f':
202        self.base_folder = val
203      if key == '-c':
204        self.codepage_number = int(val)
205        self.codepage_number_specified_explicitly = True
206      if key == '-D':
207        name, val = util.ParseDefine(val)
208        self.defines[name] = val
209
210    res_tree = grd_reader.Parse(opts.input, debug=opts.extra_verbose)
211    res_tree.OnlyTheseTranslations([self.lang])
212    res_tree.RunGatherers()
213
214    # Dialog IDs are either explicitly listed, or we output all dialogs from the
215    # .grd file
216    dialog_ids = args
217    if not len(dialog_ids):
218      for node in res_tree:
219        if node.name == 'structure' and node.attrs['type'] == 'dialog':
220          dialog_ids.append(node.attrs['name'])
221
222    self.Process(res_tree, dialog_ids)
223
224  def Process(self, grd, dialog_ids):
225    '''Outputs an RC file and header file for the dialog 'dialog_id' stored in
226    resource tree 'grd', to self.base_folder, as discussed in this class's
227    documentation.
228
229    Arguments:
230      grd: grd = grd_reader.Parse(...); grd.RunGatherers()
231      dialog_ids: ['IDD_MYDIALOG', 'IDD_OTHERDIALOG']
232    '''
233    grd.SetOutputLanguage(self.lang)
234    grd.SetDefines(self.defines)
235
236    project_name = dialog_ids[0]
237
238    dir_path = os.path.join(self.base_folder, project_name)
239    if not os.path.isdir(dir_path):
240      os.mkdir(dir_path)
241
242    # If this fails then we're not on Windows (or you don't have the required
243    # win32all Python libraries installed), so what are you doing mucking
244    # about with RC files anyway? :)
245    import pythoncom
246
247    # Create the .vcproj file
248    project_text = PROJECT_TEMPLATE.replace(
249      '[[PROJECT_GUID]]', str(pythoncom.CreateGuid())
250      ).replace('[[DIALOG_NAME]]', project_name)
251    fname = os.path.join(dir_path, '%s.vcproj' % project_name)
252    self.WriteFile(fname, project_text)
253    print "Wrote %s" % fname
254
255    # Create the .rc file
256    # Output all <include> nodes since the dialogs might depend on them (e.g.
257    # for icons and bitmaps).
258    include_items = []
259    for node in grd.ActiveDescendants():
260      if isinstance(node, include.IncludeNode):
261        include_items.append(rc.FormatInclude(node, self.lang, '.'))
262    rc_text = RC_TEMPLATE.replace('[[CODEPAGE_NUM]]',
263                                  str(self.codepage_number))
264    rc_text = rc_text.replace('[[INCLUDES]]', ''.join(include_items))
265
266    # Then output the dialogs we have been asked to output.
267    dialogs = []
268    for dialog_id in dialog_ids:
269      node = grd.GetNodeById(dialog_id)
270      assert node.name == 'structure' and node.attrs['type'] == 'dialog'
271      # TODO(joi) Add exception handling for better error reporting
272      dialogs.append(rc.FormatStructure(node, self.lang, '.'))
273    rc_text = rc_text.replace('[[DIALOGS]]', ''.join(dialogs))
274
275    fname = os.path.join(dir_path, '%s.rc' % project_name)
276    self.WriteFile(fname, rc_text, self.GetEncoding())
277    print "Wrote %s" % fname
278
279    # Create the resource.h file
280    header_defines = ''.join(rc_header.FormatDefines(grd))
281    header_text = HEADER_TEMPLATE.replace('[[DEFINES]]', header_defines)
282    fname = os.path.join(dir_path, 'resource.h')
283    self.WriteFile(fname, header_text)
284    print "Wrote %s" % fname
285
286  def WriteFile(self, filename, contents, encoding='cp1252'):
287    with open(filename, 'wb') as f:
288      writer = util.WrapOutputStream(f, encoding)
289      writer.write(contents)
290