├── .hgtags
├── AddinMaker.wxg
├── LICENSE
├── README.md
├── addin.py
├── addin_assistant.pyw
├── addin_ui.py
├── i18n.py
├── images
├── AddInDesktop.ico
├── AddInDesktop48.png
└── AddInDesktop64.png
├── packaging
├── README.txt
└── makeaddin.py
├── resources
└── resource_strings.json
└── setup.py
/.hgtags:
--------------------------------------------------------------------------------
1 | d2c510239e37de29c20718cbbbfc53e94de59e0c 10.2-wizard
2 |
--------------------------------------------------------------------------------
/AddinMaker.wxg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
497 |
498 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonbot/addin-wizard/eeaf8d8988a0c24d110918241f7bd03a24644c1a/LICENSE
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # **NOTE**: This repository is no longer actively maintained as I am no longer an employee at Esri and do not have active access to Windows or any ArcGIS products. Hopefully you can get support at [Esri's official help forum](https://geonet.esri.com/groups/python-addins).
2 |
3 | # Python Add-In Wizard
4 |
5 | This is the application linked to in the help for creating Python Add-Ins in ArcGIS 10.1/10.2. It's a simple wxPython app bundled with py2exe, so it should act as a good example to learn from for doing that, too.
6 |
7 | This app does not require ArcGIS in any way to function, nor does it import or use arcpy/arcgisscripting in any way. It's just a UI to make boilerplate `.xml`/`.py` files.
8 |
9 | ## Licensing
10 | Copyright 2013 Esri
11 |
12 | Licensed under the Apache License, Version 2.0 (the "License");
13 | you may not use this file except in compliance with the License.
14 | You may obtain a copy of the License at
15 |
16 | http://www.apache.org/licenses/LICENSE-2.0
17 |
18 | Unless required by applicable law or agreed to in writing, software
19 | distributed under the License is distributed on an "AS IS" BASIS,
20 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21 | See the License for the specific language governing permissions and
22 | limitations under the License.
23 |
24 | A copy of the license is available in the repository's license.txt file.
25 |
26 | [](Esri: ArcGIS Python Add-In Wizard)
27 | [](Esri Language: Python)
28 |
--------------------------------------------------------------------------------
/addin.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 | import datetime
3 | import itertools
4 | import random
5 | import operator
6 | import os
7 | import shutil
8 | import stat
9 | import time
10 | import uuid
11 | import xml.etree.ElementTree
12 | import xml.dom.minidom
13 | import _winreg
14 |
15 | NAMESPACE = "{http://schemas.esri.com/Desktop/AddIns}"
16 |
17 | def CURRENT_VERSION():
18 | version_keys = (((_winreg.HKEY_LOCAL_MACHINE, r"SOFTWARE\Wow6432Node\ESRI\Desktop10.4"), "10.4"),
19 | ((_winreg.HKEY_LOCAL_MACHINE, r"SOFTWARE\ESRI\Desktop10.4"), "10.4"),
20 | ((_winreg.HKEY_LOCAL_MACHINE, r"SOFTWARE\Wow6432Node\ESRI\Desktop10.4"), "10.3"),
21 | ((_winreg.HKEY_LOCAL_MACHINE, r"SOFTWARE\ESRI\Desktop10.3"), "10.3"),
22 | ((_winreg.HKEY_LOCAL_MACHINE, r"SOFTWARE\Wow6432Node\ESRI\Desktop10.2"), "10.2"),
23 | ((_winreg.HKEY_LOCAL_MACHINE, r"SOFTWARE\ESRI\Desktop10.2"), "10.2"),
24 | ((_winreg.HKEY_LOCAL_MACHINE, r"SOFTWARE\Wow6432Node\ESRI\Desktop10.1"), "10.1"),
25 | ((_winreg.HKEY_LOCAL_MACHINE, r"SOFTWARE\ESRI\Desktop10.1"), "10.1"))
26 | for version_key, version_string in version_keys:
27 | try:
28 | _winreg.CloseKey(_winreg.OpenKey(*version_key))
29 | return version_string
30 | except WindowsError:
31 | pass
32 | # Default to latest version
33 | return version_keys[0][1]
34 |
35 | def makeid(prefix="id", seen=set()):
36 | if prefix[-1].isdigit():
37 | newnum = int(''.join(char for char in prefix if char.isdigit()))
38 | prefix = ''.join(char for char in prefix if not char.isdigit())
39 | seen.add(newnum)
40 | num = 1
41 | while num in seen:
42 | num += 1
43 | seen.add(num)
44 | return prefix + str(num)
45 |
46 | class DelayedGetter(object):
47 | def __init__(self, id_to_get, id_cache):
48 | self._id = id_to_get
49 | self._cache = id_cache
50 | @property
51 | def item(self):
52 | return self._cache[self._id]
53 |
54 | class XMLSerializable(object):
55 | __registry__ = {}
56 | def xmlNode(self, parent_node):
57 | raise NotImplementedError("Method not implemented for {}".format(
58 | repr(self.__class__)))
59 | @classmethod
60 | def loadNode(cls, node, id_cache=None):
61 | tagname = node.tag[len(NAMESPACE):]
62 | if 'refID' in node.attrib:
63 | if id_cache and node.attrib['refID'] in id_cache:
64 | return id_cache[node.attrib['refID']]
65 | elif isinstance(id_cache, dict):
66 | return DelayedGetter(node.attrib['refID'], id_cache)
67 | elif tagname in cls.__registry__:
68 | return cls.__registry__[tagname].fromNode(node, id_cache)
69 | raise NotImplementedError(
70 | "Deserialization not implemented for {}".format(repr(cls)))
71 | @classmethod
72 | def registerType(cls, klass):
73 | cls.__registry__[klass.__name__] = klass
74 | return klass
75 |
76 | class XMLAttrMap(XMLSerializable):
77 | def addAttrMap(self, node):
78 | if hasattr(self, '__attr_map__'):
79 | for attr_node, attr_name in self.__attr_map__.iteritems():
80 | value = getattr(self, attr_name, '')
81 | if isinstance(value, bool):
82 | value = str(value).lower()
83 | else:
84 | value = unicode(value)
85 | node.attrib[attr_node] = value
86 | @classmethod
87 | def fromNode(cls, node, id_cache=None):
88 | instance = cls()
89 | for attrib, mapping_attrib in getattr(cls, '__attr_map__', {}).iteritems():
90 | val = node.attrib.get(attrib, '')
91 | if val in ('true', 'false'): # Coerce bool?
92 | val = True if val == 'true' else False
93 | else: # Coerce int?
94 | try:
95 | val = int(val)
96 | except ValueError:
97 | pass
98 | setattr(instance, mapping_attrib, val)
99 | if hasattr(instance, 'items'):
100 | item_nodes = node.find(NAMESPACE+"Items")
101 | for item in item_nodes.getchildren() if item_nodes is not None else []:
102 | instance.items.append(XMLSerializable.loadNode(item, id_cache))
103 | if 'id' in node.attrib and isinstance(id_cache, dict):
104 | id_cache[node.attrib['id']] = instance
105 | makeid(node.attrib['id'])
106 | if 'class' in node.attrib:
107 | makeid(node.attrib['class'])
108 | help_node = node.find(NAMESPACE+"Help")
109 | if help_node is not None:
110 | instance.help_heading = help_node.attrib.get('heading', '')
111 | instance.help_string = (help_node.text or '').strip()
112 | return instance
113 |
114 | class HasPython(object):
115 | @property
116 | def python(self):
117 | methods = getattr(self, '__python_methods__', [])
118 | if hasattr(self, 'enabled_methods'):
119 | methods = [m for m in methods if m[0] in self.enabled_methods]
120 | method_string = "\n".join(
121 | " def {0}({1}):\n{2} pass".format(method,
122 | ", ".join(args),
123 | " {0}\n".format(repr(doc))
124 | if doc
125 | else '')
126 | for method, doc, args in methods
127 | )
128 | init_code = getattr(self, '__init_code__', '')
129 | if init_code:
130 | method_string = " def __init__(self):\n{0}\n{1}".format(
131 | "\n".join(" "+ line
132 | for line in init_code),
133 | method_string)
134 | if not method_string:
135 | method_string = " pass"
136 | comment_or_doc = ' # {0}'.format(self.__class__.__name__)
137 | if hasattr(self, 'id'):
138 | comment_or_doc = ' """Implementation for {0} ({1})"""'.format(self.id,
139 | self.__class__.__name__)
140 | return "class {0}(object):\n{1}\n{2}".format(self.klass, comment_or_doc, method_string)
141 |
142 | class RefID(object):
143 | def refNode(self, parent):
144 | return xml.etree.ElementTree.SubElement(parent,
145 | self.__class__.__name__,
146 | {'refID': self.id})
147 |
148 | class Command(RefID):
149 | pass
150 |
151 | class UIControl(Command, XMLSerializable, HasPython):
152 | pass
153 |
154 | @XMLSerializable.registerType
155 | class Extension(XMLAttrMap, HasPython):
156 | "Extension"
157 | __attr_map__ = {'productName': 'name',
158 | 'name': 'name',
159 | 'description': 'description',
160 | 'class': 'klass',
161 | 'id': 'id',
162 | 'category': 'category',
163 | 'showInExtensionDialog': 'show_in_dialog',
164 | 'autoLoad': 'enabled'}
165 | __python_methods__ = [('startup', '', ['self']),
166 | ('shutdown', '', ['self']),
167 | ('activeViewChanged', '', ['self']),
168 | ('mapsChanged', '', ['self']),
169 | ('newDocument', '', ['self']),
170 | ('openDocument', '', ['self']),
171 | ('beforeCloseDocument', '', ['self']),
172 | ('closeDocument', '', ['self']),
173 | ('beforePageIndexExtentChange', '', ['self', 'old_id']),
174 | ('pageIndexExtentChanged', '', ['self', 'new_id']),
175 | ('contentsChanged', '', ['self']),
176 | ('spatialReferenceChanged', '', ['self']),
177 | ('itemAdded', '', ['self', 'new_item']),
178 | ('itemDeleted', '', ['self', 'deleted_item']),
179 | ('itemReordered', '', ['self', 'reordered_item', 'new_index']),
180 | ('onEditorSelectionChanged', '', ['self']),
181 | ('onCurrentLayerChanged', '', ['self']),
182 | ('onCurrentTaskChanged', '', ['self']),
183 | ('onStartEditing', '', ['self']),
184 | ('onStopEditing', '', ['self', 'save_changes']),
185 | ('onStartOperation', '', ['self']),
186 | ('beforeStopOperation', '', ['self']),
187 | ('onStopOperation', '', ['self']),
188 | ('onSaveEdits', '', ['self']),
189 | ('onChangeFeature', '', ['self']),
190 | ('onCreateFeature', '', ['self']),
191 | ('onDeleteFeature', '', ['self']),
192 | ('onUndo', '', ['self']),
193 | ('onRedo', '', ['self'])]
194 | @property
195 | def __init_code__(self):
196 | return ['# For optimal performance, please remove all unused methods '
197 | 'in this class.',
198 |
199 | 'self.enabled = {}'.format(repr(self.enabled)),
200 |
201 | '# Starting in 10.,3, these attributes will be populated '
202 | 'when edit events are triggered',
203 | 'self.editWorkspace = None # String with workspace path',
204 | 'self.currentLayer = None # arcpy.mapping.Layer of edit layer',
205 | 'self.currentFeature = None # arcpy.Geometry',
206 | 'self.editSelection = None # List of OIDs as ints']
207 | def __init__(self, name=None, description=None, klass=None, id=None, category=None):
208 | self.name = name or 'New Extension'
209 | self.description = description or ''
210 | self.klass = klass or makeid("ExtensionClass")
211 | self.id = id or makeid("extension")
212 | self.category = category or ''
213 | self.show_in_dialog = True
214 | self.enabled = True
215 | self.enabled_methods = []
216 | def xmlNode(self, parent):
217 | newnode = xml.etree.ElementTree.SubElement(parent,
218 | self.__class__.__name__)
219 | self.addAttrMap(newnode)
220 | return newnode
221 |
222 | class ControlContainer(XMLAttrMap):
223 | def __init__(self):
224 | self.items = []
225 | def addItemsToNode(self, parent_node):
226 | elt = xml.etree.ElementTree.SubElement(parent_node, 'Items')
227 | for item in self.items:
228 | if hasattr(item, 'refNode'):
229 | item.refNode(elt)
230 | else:
231 | item.xmlNode(elt)
232 | def xmlNode(self, parent):
233 | newnode = xml.etree.ElementTree.SubElement(parent,
234 | self.__class__.__name__)
235 | self.addAttrMap(newnode)
236 | self.addItemsToNode(newnode)
237 |
238 | @XMLSerializable.registerType
239 | class Menu(ControlContainer, RefID):
240 | "Menu"
241 | __attr_map__ = {'caption': 'caption',
242 | 'isRootMenu': 'top_level',
243 | 'isShortcutMenu': 'shortcut_menu',
244 | 'separator': 'separator',
245 | 'category': 'category',
246 | 'id': 'id'}
247 | def __init__(self, caption='Menu', top_level=False, shortcut_menu=False, separator=False, category=None, id=None):
248 | super(Menu, self).__init__()
249 | self.caption = caption or ''
250 | self.top_level = bool(top_level)
251 | self.shortcut_menu = bool(shortcut_menu)
252 | self.separator = bool(separator)
253 | self.category = category or ''
254 | self.id = id or makeid("menuitem")
255 |
256 | @XMLSerializable.registerType
257 | class ToolPalette(ControlContainer, UIControl):
258 | "Tool Palette"
259 | __attr_map__ = {'columns': 'columns',
260 | 'canTearOff': 'tearoff',
261 | 'isMenuStyle': 'menu_style',
262 | 'category': 'category',
263 | 'id': 'id'}
264 | def __init__(self, caption=None, columns=2, tearoff=False, menu_style=False, category=None, id=None):
265 | super(ToolPalette, self).__init__()
266 | self.caption = caption or 'Palette'
267 | self.columns = columns
268 | self.tearoff = bool(tearoff)
269 | self.menu_style = bool(menu_style)
270 | self.category = category or ''
271 | self.id = id or makeid("tool_palette")
272 |
273 | @XMLSerializable.registerType
274 | class Toolbar(ControlContainer):
275 | "Toolbar"
276 | __attr_map__ = {'caption': 'caption',
277 | 'category': 'category',
278 | 'id': 'id',
279 | 'showInitially': 'show_initially'}
280 | def __init__(self, id=None, caption=None, category=None, show_initially=True):
281 | super(Toolbar, self).__init__()
282 | self.id = id or makeid("toolbar")
283 | self.caption = caption or 'Toolbar'
284 | self.category = category or ''
285 | self.show_initially = bool(show_initially)
286 |
287 | @XMLSerializable.registerType
288 | class Button(XMLAttrMap, UIControl):
289 | "Button"
290 | __python_methods__ = [('onClick', '', ['self'])]
291 | __init_code__ = ['self.enabled = True', 'self.checked = False']
292 | __attr_map__ = {'caption': 'caption' ,
293 | 'class': 'klass',
294 | 'category': 'category',
295 | 'image': 'image' ,
296 | 'tip': 'tip' ,
297 | 'message': 'message' ,
298 | 'id': 'id',
299 | 'message': 'message'}
300 | def __init__(self, caption=None, klass=None, category=None, image=None,
301 | tip=None, message=None, id=None):
302 | self.caption = caption or "Button"
303 | self.klass = klass or makeid("ButtonClass")
304 | self.category = category or ''
305 | self.image = image or ''
306 | self.tip = tip or ''
307 | self.message = message or ''
308 | self.id = id or makeid("button")
309 | self.help_heading = ''
310 | self.help_string = ''
311 | def xmlNode(self, parent):
312 | newnode = xml.etree.ElementTree.SubElement(parent,
313 | self.__class__.__name__)
314 | self.addAttrMap(newnode)
315 | help = xml.etree.ElementTree.SubElement(newnode, 'Help', {'heading': self.help_heading})
316 | help.text = self.help_string
317 | return newnode
318 |
319 | @XMLSerializable.registerType
320 | class ComboBox(Button):
321 | "Combo Box"
322 | __attr_map__ = {'caption': 'caption',
323 | 'category': 'category',
324 | 'id': 'id',
325 | 'class': 'klass',
326 | 'tip': 'tip',
327 | 'message': 'message',
328 | 'sizeString': 'size_string',
329 | 'itemSizeString': 'item_size_string',
330 | 'rows': 'rows',
331 | 'hint_text': 'hint'}
332 | @property
333 | def __init_code__(self):
334 | return ['self.items = ["item1", "item2"]',
335 | 'self.editable = {}'.format(repr(self.editable)),
336 | 'self.enabled = True',
337 | 'self.dropdownWidth = {}'.format(repr(self.item_size_string)),
338 | 'self.width = {}'.format(repr(self.size_string))]
339 | __python_methods__ = [('onSelChange', '', ['self', 'selection']),
340 | ('onEditChange', '', ['self', 'text']),
341 | ('onFocus', '', ['self', 'focused']),
342 | ('onEnter', '', ['self']),
343 | ('refresh', '', ['self'])
344 | ]
345 | def __init__(self, klass=None, id=None, category=None, caption=None):
346 | self.klass = klass or makeid("ComboBoxClass")
347 | self.id = id or makeid("combo_box")
348 | self.category = category or ''
349 | self.caption = caption or "ComboBox"
350 | self.message = ''
351 | self.tip = ''
352 | self.help_heading = ''
353 | self.help_string = ''
354 | self.size_string = "WWWWWW"
355 | self.item_size_string = "WWWWWW"
356 | self.hint_text = ''
357 | self.editable = True
358 | self.rows = 4
359 |
360 | @XMLSerializable.registerType
361 | class Tool(Button):
362 | "Python Tool"
363 | __init_code__ = ['self.enabled = True',
364 | 'self.shape = "NONE" # Can set to "Line", '
365 | '"Circle" or "Rectangle" '
366 | 'for interactive shape drawing '
367 | 'and to activate the onLine/Polygon/Circle event sinks.']
368 | __python_methods__ = [('onMouseDown', '', ['self', 'x', 'y', 'button', 'shift']),
369 | ('onMouseDownMap', '', ['self', 'x', 'y', 'button', 'shift']),
370 | ('onMouseUp', '', ['self', 'x', 'y', 'button', 'shift']),
371 | ('onMouseUpMap', '', ['self', 'x', 'y', 'button', 'shift']),
372 | ('onMouseMove', '', ['self', 'x', 'y', 'button', 'shift']),
373 | ('onMouseMoveMap', '', ['self', 'x', 'y', 'button', 'shift']),
374 | ('onDblClick', '', ['self']),
375 | ('onKeyDown', '', ['self', 'keycode', 'shift']),
376 | ('onKeyUp', '', ['self', 'keycode', 'shift']),
377 | ('deactivate', '', ['self']),
378 | ('onCircle', '', ['self', 'circle_geometry']),
379 | ('onLine', '', ['self', 'line_geometry']),
380 | ('onRectangle', '', ['self', 'rectangle_geometry']),
381 | ]
382 | def __init__(self, caption=None, klass=None, category=None, image=None,
383 | tip=None, message=None, id=None):
384 | super(Tool, self).__init__(caption, klass, category, image, tip, message, id)
385 | self.caption = caption or 'Tool'
386 | self.klass = klass or makeid("ToolClass")
387 | self.id = id or makeid("tool")
388 | self.image = image or ''
389 | self.help_heading = ''
390 | self.help_string = ''
391 |
392 | @XMLSerializable.registerType
393 | class MultiItem(XMLAttrMap, UIControl):
394 | "MultiItem"
395 | __attr_map__ = {'id': 'id',
396 | 'class': 'klass',
397 | 'hasSeparator': 'separator'}
398 | __python_methods__ = [('onItemClick', '', ['self'])]
399 | __init_code__ = ['self.items = ["item1", "item2"]']
400 | def __init__(self, klass=None, id=None, separator=None):
401 | self.klass = klass or makeid("MultiItemClass")
402 | self.id = id or makeid("multi_item")
403 | self.caption = 'MultiItem'
404 | self.separator = bool(separator)
405 | def xmlNode(self, parent):
406 | newnode = xml.etree.ElementTree.SubElement(parent,
407 | self.__class__.__name__)
408 | self.addAttrMap(newnode)
409 | return newnode
410 |
411 | class PythonAddin(object):
412 | __apps__ = ('ArcMap', 'ArcCatalog', 'ArcGlobe', 'ArcScene')
413 | def __init__(self, name, description, namespace, author='Untitled',
414 | company='Untitled', version='0.1', image='', app='ArcMap',
415 | backup_files = False):
416 | self.name = name
417 | self.description = description
418 | self.namespace = namespace
419 | self.author = author
420 | self.company = company
421 | assert app in self.__apps__
422 | self.app = app
423 | self.guid = "{{{}}}".format(uuid.uuid4())
424 | self.version = version
425 | self.image = image
426 | self.addinfile = self.namespace + '.py'
427 | self.items = []
428 | self.backup_files = backup_files
429 | self.last_backup = 0.0
430 | self.projectpath = ''
431 | def remove(self, target_item):
432 | def rm_(container_object, target_item):
433 | items = getattr(container_object, 'items', [])
434 | if target_item in items:
435 | container_object.items.pop(container_object.items.index(target_item))
436 | return True
437 | else:
438 | for item in (i for i in items if isinstance(i, ControlContainer)):
439 | if rm_(item, target_item):
440 | return True
441 | return False
442 | return rm_(self, target_item)
443 | @property
444 | def commands(self):
445 | seen_ids = set()
446 | for command in self:
447 | if isinstance(command, UIControl) and command.id not in seen_ids:
448 | yield command
449 | seen_ids.add(command.id)
450 | @property
451 | def allmenus(self):
452 | seen_ids = set()
453 | for menu in self:
454 | if isinstance(menu, Menu) and menu.id not in seen_ids:
455 | yield menu
456 | seen_ids.add(menu.id)
457 | @property
458 | def menus(self):
459 | return [m for m in self.items if isinstance(m, Menu) and m.top_level]
460 | @property
461 | def toolbars(self):
462 | return [toolbar for toolbar in self.items if isinstance(toolbar, Toolbar)]
463 | @property
464 | def extensions(self):
465 | return [extension for extension in self.items if isinstance(extension, Extension)]
466 | @classmethod
467 | def fromXML(cls, xmlfile, backup_files = False):
468 | new_addin = cls('unknown', 'unknown', 'unknown')
469 | new_addin.backup_files = backup_files
470 | new_addin.last_backup = 0.0
471 | id_cache = {}
472 | doc = xml.etree.ElementTree.parse(xmlfile)
473 | root = doc.getroot()
474 | assert root.tag == NAMESPACE+'ESRI.Configuration', root.tag
475 | name_node = root.find(NAMESPACE+"Name")
476 | if name_node is None:
477 | name_node = root.find(NAMESPACE+"Name")
478 | if name_node is not None:
479 | new_addin.name = name_node.text.strip()
480 | else:
481 | new_addin.name = "Untitled"
482 | new_addin.guid = (root.find(NAMESPACE+"AddInID").text or '').strip()
483 | new_addin.description = (root.find(NAMESPACE+"Description").text or '').strip()
484 | new_addin.version = (root.find(NAMESPACE+"Version").text or '').strip()
485 | new_addin.image = (root.find(NAMESPACE+"Image").text or '').strip()
486 | new_addin.author = (root.find(NAMESPACE+"Author").text or '').strip()
487 | new_addin.company = (root.find(NAMESPACE+"Company").text or '').strip()
488 | addin_node = root.find(NAMESPACE+"AddIn")
489 | new_addin.addinfile = addin_node.attrib.get('library', '')
490 | new_addin.namespace = addin_node.attrib.get('namespace', '')
491 | projectpath = os.path.join(os.path.dirname(xmlfile), 'Install', os.path.dirname(new_addin.addinfile))
492 | new_addin.projectpath = projectpath
493 | app_node = addin_node.getchildren()[0]
494 | new_addin.app = app_node.tag[len(NAMESPACE):]
495 | for command in app_node.find(NAMESPACE+"Commands").getchildren():
496 | XMLSerializable.loadNode(command, id_cache)
497 | for tag in ("Extensions", "Toolbars", "Menus"):
498 | for item in app_node.find(NAMESPACE+tag).getchildren():
499 | newobject = XMLSerializable.loadNode(item, id_cache)
500 | new_addin.items.append(newobject)
501 | def fix_references(addin_item, depth = 0):
502 | if hasattr(addin_item, 'items'):
503 | new_items = [i.item if isinstance(i, DelayedGetter)
504 | else i for i in addin_item.items]
505 | addin_item.items = new_items
506 | for item in new_items:
507 | fix_references(item, depth + 1)
508 | fix_references(new_addin)
509 | return new_addin
510 | def backup(self):
511 | addin_file = os.path.join(self.projectpath, self.addinfile)
512 | if (self.backup_files and os.path.isfile(addin_file)
513 | and os.stat(addin_file)[stat.ST_MTIME] > self.last_backup):
514 | path, ext = os.path.splitext(addin_file)
515 | new_index = 1
516 | new_file = path + "_" + str(new_index) + ext
517 | while os.path.exists(new_file):
518 | new_index += 1
519 | new_file = path + "_" + str(new_index) + ext
520 | if not hasattr(self, 'warning'):
521 | self.warning = u''
522 | else:
523 | self.warning += "\n"
524 | self.warning += u"Python script {0} already exists. Saving a backup as {1}.".format(addin_file, new_file)
525 | shutil.copyfile(addin_file, new_file)
526 | self.last_backup = time.time()
527 | return self.addinfile
528 | def fixids(self, item = None, seen_ids = None):
529 | if seen_ids is None:
530 | seen_ids = {}
531 | target = self if item is None else item
532 | for thisattr in ('id', 'klass'):
533 | if hasattr(target, thisattr):
534 | if thisattr == 'id' and '.' not in getattr(target, thisattr, '') and '{' not in getattr(target, thisattr, ''):
535 | setattr(target, thisattr, self.namespace + "." + getattr(target, thisattr, thisattr))
536 | if getattr(target, thisattr) in seen_ids and seen_ids[getattr(target, thisattr)] != target:
537 | if not hasattr(self, 'warning'):
538 | self.warning = ''
539 | else:
540 | self.warning += "\n"
541 | thisattrnum = 1
542 | while (getattr(target, thisattr) + "_" + str(thisattrnum)) in seen_ids:
543 | thisattrnum += 1
544 | newthisattr = getattr(target, thisattr) + "_" + str(thisattrnum)
545 | self.warning += u"{0} {1} is already in use. Renaming next usage to {2}.".format("Class" if thisattr == "klass" else "ID",
546 | getattr(target, thisattr),
547 | newthisattr)
548 | setattr(target, thisattr, newthisattr)
549 | seen_ids[getattr(target, thisattr)] = target
550 | if hasattr(target, 'items'):
551 | for subitem in target.items:
552 | self.fixids(subitem, seen_ids)
553 | @property
554 | def xml(self):
555 | root = xml.etree.ElementTree.Element('ESRI.Configuration',
556 | {'xmlns': "http://schemas.esri.com/Desktop/AddIns",
557 | 'xmlns:xsi': "http://www.w3.org/2001/XMLSchema-instance"})
558 | xml.etree.ElementTree.SubElement(root, 'Name').text = self.name
559 | xml.etree.ElementTree.SubElement(root, 'AddInID').text = self.guid
560 | xml.etree.ElementTree.SubElement(root, 'Description').text = self.description
561 | xml.etree.ElementTree.SubElement(root, 'Version').text = self.version
562 | xml.etree.ElementTree.SubElement(root, 'Image').text = self.image
563 | xml.etree.ElementTree.SubElement(root, 'Author').text = self.author
564 | xml.etree.ElementTree.SubElement(root, 'Company').text = self.company
565 | now = datetime.datetime.now().strftime("%m/%d/%Y")
566 | xml.etree.ElementTree.SubElement(root, 'Date').text = now
567 | targets = xml.etree.ElementTree.SubElement(root, 'Targets')
568 | arcgis_version = CURRENT_VERSION()
569 | target = xml.etree.ElementTree.SubElement(targets,
570 | 'Target',
571 | {'name': "Desktop",
572 | 'version': arcgis_version})
573 | addinnode = xml.etree.ElementTree.SubElement(root,
574 | 'AddIn',
575 | {'language': 'PYTHON',
576 | 'library': self.addinfile,
577 | 'namespace':
578 | self.namespace})
579 |
580 | self.fixids()
581 |
582 | appnode = xml.etree.ElementTree.SubElement(addinnode, self.app)
583 | commandnode = xml.etree.ElementTree.SubElement(appnode, 'Commands')
584 | for command in self.commands:
585 | command.xmlNode(commandnode)
586 | extensionnode = xml.etree.ElementTree.SubElement(appnode, 'Extensions')
587 | for extension in self.extensions:
588 | extension.xmlNode(extensionnode)
589 | toolbarnode = xml.etree.ElementTree.SubElement(appnode, 'Toolbars')
590 | for toolbar in self.toolbars:
591 | toolbar.xmlNode(toolbarnode)
592 | menunode = xml.etree.ElementTree.SubElement(appnode, 'Menus')
593 | for menu in self.allmenus:
594 | menu.xmlNode(menunode)
595 | markup = xml.etree.ElementTree.tostring(root).encode("utf-8")
596 | return xml.dom.minidom.parseString(markup).toprettyxml(" ")
597 | def __iter__(self):
598 | def ls_(item):
599 | if hasattr(item, 'items'):
600 | for item_ in item.items:
601 | for item__ in ls_(item_):
602 | yield item__
603 | yield item
604 | for bitem in self.items:
605 | for aitem in ls_(bitem):
606 | yield aitem
607 | @property
608 | def python(self):
609 | return ("# coding: utf-8\nimport arcpy\nimport pythonaddins\n\n" +
610 | "\n\n".join(
611 | sorted(
612 | set(x.python
613 | for x in self
614 | if hasattr(x, 'python')))))
615 |
616 | class PythonAddinProjectDirectory(object):
617 | def __init__(self, path, backup_files = False):
618 | self._path = path
619 | self.backup_files = backup_files
620 | if not os.path.exists(path):
621 | raise IOError(u"{0} does not exist. Please select a "
622 | u"directory that exists.".format(path))
623 | listing = os.listdir(path)
624 | if listing:
625 | if not all(item in listing for item in ('config.xml',
626 | 'Install',
627 | 'Images')):
628 | raise ValueError(u"{0} is not empty. Please select an empty "
629 | u"directory to host this new "
630 | u"addin.".format(path))
631 | else:
632 | self.addin = PythonAddin.fromXML(os.path.join(self._path,
633 | 'config.xml'),
634 | backup_files)
635 | self.warning = getattr(self.addin, 'warning', None)
636 | else:
637 | # Fix for NIM092018
638 | addin_name = os.path.basename(path)
639 | self.addin = PythonAddin("Python Addin",
640 | "New Addin",
641 | (addin_name if addin_name
642 | else 'python') + "_addin",
643 | backup_files = True)
644 | def save(self):
645 | # Make install/images dir
646 | install_dir = os.path.join(self._path, 'Install')
647 | images_dir = os.path.join(self._path, 'Images')
648 | if not os.path.exists(install_dir):
649 | os.mkdir(install_dir)
650 | if not os.path.exists(images_dir):
651 | os.mkdir(images_dir)
652 |
653 | # Copy packaging\* into project dir
654 | initial_dirname = os.path.dirname(os.path.abspath(__file__))
655 | if os.path.isfile(initial_dirname):
656 | initial_dirname = os.path.dirname(initial_dirname)
657 | packaging_dir = os.path.join(initial_dirname,
658 | 'packaging')
659 | for filename in os.listdir(packaging_dir):
660 | if not os.path.exists(os.path.join(self._path, filename)):
661 | shutil.copyfile(os.path.join(packaging_dir,
662 | filename),
663 | os.path.join(self._path, filename))
664 |
665 | # For consolidating images
666 | seen_images = set([self.addin.image])
667 |
668 | # Auto-fill category
669 | for item in self.addin:
670 | # Auto-category
671 | if hasattr(item, 'category'):
672 | item.category = self.addin.name or self.addin.description
673 | # Collect for later
674 | if getattr(item, 'image', ''):
675 | seen_images.add(item.image)
676 |
677 | # Consolidate images
678 | seen_images = filter(bool, seen_images)
679 | full_path = dict((image,
680 | os.path.abspath(
681 | os.path.join(self._path, image)))
682 | for image in seen_images)
683 | to_relocate = set(image_file
684 | for (image_name, image_file) in full_path.iteritems()
685 | if os.path.dirname(image_file) != images_dir)
686 | relocated_images = {}
687 | image_files = set(x.lower() for x in os.listdir(images_dir))
688 | for image_file in to_relocate:
689 | new_filename = os.path.basename(image_file)
690 | if new_filename.lower() in image_files:
691 | num = 1
692 | fn, format = os.path.splitext(new_filename)
693 | while new_filename.lower() in image_files:
694 | new_filename = fn + "_" + str(num) + format
695 | num += 1
696 | relocated_images[image_file] = new_filename
697 | shutil.copyfile(image_file, os.path.join(self._path,
698 | 'Images',
699 | new_filename))
700 | for item_with_image in (item
701 | for item in ([self.addin] + list(self.addin))
702 | if getattr(item, 'image', '')):
703 | item_image = item_with_image.image
704 | if item_image in relocated_images:
705 | item_with_image.image = os.path.join('Images',
706 | relocated_images[
707 | item_image])
708 | else:
709 | item_with_image.image = os.path.join('Images',
710 | os.path.basename(
711 | item_image))
712 |
713 | # Back up .py file if necessary
714 | filename = self.addin.backup()
715 |
716 | # Output XML and Python stub
717 | with open(os.path.join(self._path, 'config.xml'), 'wb') as out_handle:
718 | out_handle.write(self.addin.xml)
719 | self.addin.last_backup = time.time()
720 | with open(os.path.join(install_dir, filename), 'wb') as out_python:
721 | out_python.write(self.addin.python)
722 | self.addin.last_save = time.time()
723 |
724 | if __name__ == "__main__":
725 | myaddin = PythonAddin("My Addin", "This is a new addin", "myaddin")
726 | toolbar = Toolbar()
727 | top_menu = Menu("Top Level Menu", id="top_menu")
728 | top_menu.items.append(Button("Top Menu button 1", id="tb1"))
729 | top_menu.items.append(Button("Top Menu button 2", id="tb2"))
730 | menu = Menu("Embedded Menu", id="embedded_menu")
731 | menu.items.append(Button("Menu button", "MenuButton"))
732 | palette = ToolPalette("Awesome Palette")
733 | palette.items.append(Tool("New Fun Tool", "NewFunTool"))
734 | palette.items.append(Tool("New Not Fun Tool", "NewNotFunTool"))
735 | toolbar.items.append(Button("Hello there", "HelloButton"))
736 | toolbar.items.append(menu)
737 | toolbar.items.append(palette)
738 | myaddin.items.append(toolbar)
739 | myaddin.items.append(top_menu)
740 | print myaddin.xml
741 |
--------------------------------------------------------------------------------
/addin_assistant.pyw:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 | import addin
3 | import addin_ui
4 | import os
5 | import re
6 | import sys
7 | import traceback
8 | import wx
9 |
10 | from i18n import _
11 |
12 | class AddinMakerAppWindow(addin_ui.AddinMakerWindow):
13 | # Sentinel/singletons for the treeview
14 | class _extensiontoplevel(object):
15 | "Extensions"
16 | pass
17 | class _menutoplevel(object):
18 | "Toplevel Menus"
19 | pass
20 | class _toolbartoplevel(object):
21 | "Toolbars"
22 | pass
23 | def __init__(self, *args, **kws):
24 | super(AddinMakerAppWindow, self).__init__(*args, **kws)
25 | self.Bind(wx.EVT_CLOSE, self.OnClose)
26 |
27 | self._selected_data = None
28 | self.contents_tree.Bind(wx.EVT_RIGHT_DOWN, self.TreePopupRClick)
29 | self.contents_tree.Bind(wx.EVT_CONTEXT_MENU, self.TreePopup)
30 |
31 | self._newextensionid = wx.NewId()
32 | self._newmenuid = wx.NewId()
33 | self._newtoolbarid = wx.NewId()
34 |
35 | def Show(self):
36 | super(AddinMakerAppWindow, self).Show()
37 | self.SelectFolder(None)
38 |
39 | def AddExtension(self, event):
40 | extension = addin.Extension()
41 | self.project.addin.items.append(extension)
42 | menuitem = self.contents_tree.AppendItem(self.extensionsroot, extension.name)
43 | self.contents_tree.SetItemPyData(menuitem, extension)
44 | self.contents_tree.SelectItem(menuitem, True)
45 | self.save_button.Enable(True)
46 |
47 | def AddMenu(self, event):
48 | menu = addin.Menu("Menu", self._selected_data is self._menutoplevel,
49 | id=self.project.addin.namespace+'.menu')
50 | self.project.addin.items.append(menu)
51 | menuitem = self.contents_tree.AppendItem(self.menusroot, menu.caption)
52 | self.contents_tree.SetItemPyData(menuitem, menu)
53 | self.contents_tree.SelectItem(menuitem, True)
54 | self.save_button.Enable(True)
55 |
56 | def AddToolbar(self, event):
57 | toolbar = addin.Toolbar(id=self.project.addin.namespace+'.toolbar')
58 | self.project.addin.items.append(toolbar)
59 | toolbaritem = self.contents_tree.AppendItem(self.toolbarsroot, toolbar.caption)
60 | self.contents_tree.SetItemPyData(toolbaritem, toolbar)
61 | self.contents_tree.SelectItem(toolbaritem, True)
62 | self.save_button.Enable(True)
63 |
64 | @property
65 | def extensionmenu(self):
66 | extensionmenu = wx.Menu()
67 | cmd = extensionmenu.Append(self._newextensionid, _("New Extension"))
68 | extensionmenu.Bind(wx.EVT_MENU, self.AddExtension, cmd)
69 | return extensionmenu
70 |
71 | @property
72 | def menumenu(self):
73 | menumenu = wx.Menu()
74 | cmd = menumenu.Append(self._newmenuid, _("New Menu"))
75 | menumenu.Bind(wx.EVT_MENU, self.AddMenu, cmd)
76 | return menumenu
77 |
78 | @property
79 | def toolbarmenu(self):
80 | toolbarmenu = wx.Menu()
81 | cmd = toolbarmenu.Append(self._newtoolbarid, _("New Toolbar"))
82 | toolbarmenu.Bind(wx.EVT_MENU, self.AddToolbar, cmd)
83 | return toolbarmenu
84 |
85 | @property
86 | def controlcontainermenu(self):
87 | class ItemAppender(object):
88 | def __init__(self, tree, selection, item, cls, save_button, namespace):
89 | self.tree = tree
90 | self.selection = selection
91 | self.item = item
92 | self.cls = cls
93 | self.save_button = save_button
94 | self.namespace = namespace
95 | def __call__(self, event):
96 | try:
97 | new_item = self.cls(id=self.namespace+'.'+self.cls.__name__.lower())
98 | self.item.items.append(new_item)
99 | toolbaritem = self.tree.AppendItem(self.selection, getattr(new_item, 'caption', unicode(new_item)))
100 | self.tree.SetItemPyData(toolbaritem, new_item)
101 | self.tree.SelectItem(toolbaritem, True)
102 | self.save_button.Enable(True)
103 | except Exception as e:
104 | traceback.print_exc()
105 | print e
106 |
107 | tree = self.contents_tree
108 | selection = self.contents_tree.GetSelection()
109 | item = self._selected_data
110 |
111 | controlcontainermenu = wx.Menu()
112 | if not isinstance(item, addin.ToolPalette):
113 | buttoncmd = controlcontainermenu.Append(-1, _("New Button"))
114 | controlcontainermenu.Bind(wx.EVT_MENU, ItemAppender(tree, selection, item, addin.Button, self.save_button, self.project.addin.namespace), buttoncmd)
115 | menucmd = controlcontainermenu.Append(-1, _("New Menu"))
116 | controlcontainermenu.Bind(wx.EVT_MENU, ItemAppender(tree, selection, item, addin.Menu, self.save_button, self.project.addin.namespace), menucmd)
117 | if isinstance(item, addin.Menu):
118 | pass
119 | #multiitemcmd = controlcontainermenu.Append(-1, "New MultiItem")
120 | #controlcontainermenu.Bind(wx.EVT_MENU,
121 | # ItemAppender(tree, selection, item,
122 | # addin.MultiItem, self.save_button, self.project.addin.namespace),
123 | # multiitemcmd)
124 | else:
125 | toolcmd = controlcontainermenu.Append(-1, _("New Tool"))
126 | controlcontainermenu.Bind(wx.EVT_MENU, ItemAppender(tree, selection, item, addin.Tool, self.save_button, self.project.addin.namespace), toolcmd)
127 | if not isinstance(item, (addin.ToolPalette, addin.Menu)):
128 | palettecmd = controlcontainermenu.Append(-1, _("New Tool Palette"))
129 | controlcontainermenu.Bind(wx.EVT_MENU, ItemAppender(tree, selection, item, addin.ToolPalette, self.save_button, self.project.addin.namespace), palettecmd)
130 | if isinstance(item, addin.Toolbar):
131 | comboboxcmd = controlcontainermenu.Append(-1, _("New Combo Box"))
132 | controlcontainermenu.Bind(wx.EVT_MENU, ItemAppender(tree, selection, item, addin.ComboBox, self.save_button, self.project.addin.namespace), comboboxcmd)
133 | return controlcontainermenu
134 |
135 | def loadTreeView(self):
136 | def populate(parent, item):
137 | child = self.contents_tree.AppendItem(parent,
138 | unicode(getattr(item,
139 | 'caption',
140 | getattr(item,
141 | 'name',
142 | unicode(item)))))
143 | self.contents_tree.SetItemPyData(child, item)
144 | if hasattr(item, 'items'):
145 | for child_item in item.items:
146 | populate(child, child_item)
147 | self.contents_tree.Expand(parent)
148 | # Set up treeview control
149 | self.contents_tree.DeleteAllItems()
150 | self.treeroot = self.contents_tree.AddRoot("Root")
151 | self.extensionsroot = self.contents_tree.AppendItem(self.treeroot,
152 | _("EXTENSIONS"))
153 | self.contents_tree.SetItemPyData(self.extensionsroot,
154 | self._extensiontoplevel)
155 | for item in self.project.addin.extensions:
156 | populate(self.extensionsroot, item)
157 | self.menusroot = self.contents_tree.AppendItem(self.treeroot,
158 | _("MENUS"))
159 | self.contents_tree.SetItemPyData(self.menusroot,
160 | self._menutoplevel)
161 | for item in self.project.addin.menus:
162 | populate(self.menusroot, item)
163 | self.toolbarsroot = self.contents_tree.AppendItem(self.treeroot,
164 | _("TOOLBARS"))
165 | self.contents_tree.SetItemPyData(self.toolbarsroot,
166 | self._toolbartoplevel)
167 | for item in self.project.addin.toolbars:
168 | populate(self.toolbarsroot, item)
169 | self.contents_tree.SetItemBold(self.extensionsroot, True)
170 | self.contents_tree.SetItemBold(self.menusroot, True)
171 | self.contents_tree.SetItemBold(self.toolbarsroot, True)
172 |
173 | def loadProject(self):
174 | if getattr(self.project, 'warning', None):
175 | msgdlg = wx.MessageDialog(self, unicode(self.project.warning),
176 | _("Project Information"),
177 | wx.OK | wx.ICON_INFORMATION)
178 | msgdlg.ShowModal()
179 | msgdlg.Destroy()
180 | del self.project.warning
181 | # Repopulate the tree view control et al with settings from the loaded project
182 | self.loadTreeView()
183 | # Product selection
184 | self.product_combo_box.SetStringSelection(self.project.addin.app)
185 | # Set up metadata text entry
186 | self.project_name.SetLabel(self.project.addin.name)
187 | self.project_version.SetLabel(self.project.addin.version)
188 | self.project_company.SetLabel(self.project.addin.company)
189 | self.project_description.SetLabel(self.project.addin.description)
190 | self.project_author.SetLabel(self.project.addin.author)
191 | self.updateProjectImage()
192 |
193 | def OnClose(self, event):
194 | if self.save_button.IsEnabled():
195 | confirmdlg = wx.MessageDialog(self, _("Save your changes before exiting?"),
196 | _("Save before exiting?"),
197 | wx.YES_NO | wx.ICON_QUESTION)
198 | res = confirmdlg.ShowModal()
199 | confirmdlg.Destroy()
200 | if res == wx.ID_YES:
201 | self.SaveProject(event)
202 | self.Destroy()
203 |
204 | @property
205 | def _path_file(self):
206 | return os.path.join((os.environ.get('APPDATA', '') or
207 | os.path.expanduser('~')),
208 | '.ESRI.PythonAddinMaker.Path')
209 | @property
210 | def lastPath(self):
211 | try:
212 | if os.path.isfile(self._path_file):
213 | new_path = open(self._path_file, 'rb').read()
214 | if os.path.isdir(new_path):
215 | return new_path
216 | except:
217 | pass
218 | return wx.StandardPaths.Get().GetDocumentsDir()
219 | @lastPath.setter
220 | def lastPath(self, new_value):
221 | try:
222 | with open(self._path_file, 'wb') as out_path:
223 | out_path.write(new_value)
224 | except:
225 | pass
226 | def SelectFolder(self, event):
227 | import traceback
228 | dlg = wx.DirDialog(self, _("Choose a directory to use as an Add-In project root:"),
229 | style=wx.DD_DEFAULT_STYLE)
230 | dlg.SetPath(self.lastPath)
231 | if dlg.ShowModal() == wx.ID_OK:
232 | self.path = dlg.GetPath()
233 | try:
234 | self.project = addin.PythonAddinProjectDirectory(self.path, True)
235 | self.lastPath = self.path
236 | except Exception as e:
237 | traceback.print_exc()
238 | print repr(e.message)
239 | errdlg = wx.MessageDialog(self, e.message,
240 | _("Error initializing Add-In"),
241 | wx.OK | wx.ICON_ERROR)
242 | errdlg.ShowModal()
243 | errdlg.Destroy()
244 | traceback.print_exc()
245 | else:
246 | self.folder_button.SetLabel(self.path)
247 | self.loadProject()
248 | return
249 | self.SelectFolder(event)
250 | elif event is None:
251 | sys.exit(0)
252 | else:
253 | return
254 |
255 | def ProjectNameText(self, event):
256 | newvalue = self.project_name.GetLabel().strip()
257 | if newvalue:
258 | self.project.addin.name = newvalue
259 | self.project_name.SetBackgroundColour('White')
260 | else:
261 | self.project_name.SetBackgroundColour('#FF8080')
262 | self.Refresh()
263 | self.save_button.Enable(True)
264 | event.Skip()
265 |
266 | def ProjectCompanyText(self, event):
267 | newvalue = self.project_company.GetLabel()
268 | self.project.addin.company = newvalue
269 | self.save_button.Enable(True)
270 | event.Skip()
271 |
272 | def ProjectDescriptionText(self, event):
273 | newvalue = self.project_description.GetLabel()
274 | self.project.addin.description = newvalue
275 | self.save_button.Enable(True)
276 | event.Skip()
277 |
278 | def ProjectAuthorText(self, event):
279 | newvalue = self.project_author.GetLabel()
280 | self.project.addin.author = newvalue
281 | self.save_button.Enable(True)
282 | event.Skip()
283 |
284 | def ProjectVersionText(self, event):
285 | newvalue = self.project_version.GetLabel().strip()
286 | if newvalue:
287 | self.project.addin.version = newvalue
288 | self.project_version.SetBackgroundColour('White')
289 | else:
290 | self.project_version.SetBackgroundColour('#FF8080')
291 | self.Refresh()
292 | self.save_button.Enable(True)
293 | event.Skip()
294 |
295 | def ComboBox(self, event):
296 | self.project.addin.app = self.product_combo_box.GetValue()
297 | self.save_button.Enable(True)
298 | event.Skip()
299 |
300 | def setupPropsDialog(self):
301 | sizer = self.item_property_panel.GetSizer()
302 | sizer.Clear(True)
303 | if hasattr(self._selected_data, '__doc__') and self._selected_data.__doc__:
304 | st = wx.StaticText(self.item_property_panel, -1, _(self._selected_data.__doc__))
305 | st.SetFont(wx.Font(16, wx.DEFAULT, wx.NORMAL, wx.NORMAL, 0, ""))
306 | sizer.Add(st, 0, wx.ALL, 8)
307 | pythonliteral = re.compile("^[_A-Za-z][_A-Za-z0-9]*$").match
308 | namespacedpythonliteral = re.compile("^([_A-Za-z][_A-Za-z0-9]*[.])*[_A-Za-z][_A-Za-z0-9]+$").match
309 | def isinteger(val):
310 | try:
311 | int(val)
312 | return True
313 | except:
314 | return False
315 | def nonemptystring(val):
316 | return bool((val or '').strip())
317 | class Namespacer(object):
318 | def __init__(self, project, control):
319 | self.project = project
320 | self.control = control
321 | def __call__(self, event):
322 | oldlabel = self.control.GetLabel()
323 | if '.' not in oldlabel:
324 | newlabel = self.project.addin.namespace + '.' + oldlabel
325 | self.control.SetLabel(newlabel)
326 | proplist = [p for p in (('name', _("Name"), unicode, nonemptystring, None),
327 | ('caption', _("Caption"), unicode, nonemptystring, None),
328 | ('klass', _("Class Name"), unicode, pythonliteral, None),
329 | ('id', _("ID (Variable Name)"), unicode, namespacedpythonliteral, Namespacer),
330 | ('description', _("Description"), unicode, None, None),
331 | ('tip', _("Tooltip"), unicode, None, None),
332 | ('message', _("Message"), unicode, None, None),
333 | ('hint_text', _("Hint Text"), unicode, None, None),
334 | ('help_heading', _("Help Heading"), unicode, None, None),
335 | ('help_string', _("Help Content"), unicode, None, None),
336 | ('enabled_methods', _("Methods to Implement"), list, None, None),
337 | ('separator', _("Has Separator"), bool, None, None),
338 | ('show_initially', _("Show Initially"), bool, None, None),
339 | ('enabled', _("Load Automatically"), bool, None, None),
340 | ('menu_style', _("Menu Style"), bool, None, None),
341 | ('shortcut_menu', _("Is Shortcut Menu"), bool, None, None),
342 | ('columns', _("Column Count"), unicode, isinteger, None),
343 | ('image', _("Image for Control"), wx.Bitmap, None, None))
344 | if hasattr(self._selected_data, p[0])]
345 | for prop, caption, datatype, validator, fixer in proplist:
346 | # This is all kind of hairy, sorry
347 | newsizer = wx.BoxSizer(wx.HORIZONTAL)
348 | # Text entry
349 | if datatype in (unicode, int):
350 | st = wx.StaticText(self.item_property_panel, -1, caption + ":", style=wx.ALIGN_RIGHT)
351 | st.SetMinSize((175, 16))
352 | newsizer.Add(st, 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 2)
353 | text = wx.TextCtrl(self.item_property_panel, -1, unicode(getattr(self._selected_data, prop, '')) or '')
354 | text.SetBackgroundColour('White')
355 | class edittext(object):
356 | def __init__(self, edit_object, command, app, propname, validator, datatype):
357 | self.edit_object = edit_object
358 | self.command = command
359 | self.app = app
360 | self.propname = propname
361 | self.validator = validator
362 | self.datatype = datatype
363 | def __call__(self, event):
364 | newvalue = self.command.GetLabel()
365 | if self.validator is None or self.validator(newvalue):
366 | try:
367 | setattr(self.edit_object, self.propname, self.datatype(newvalue))
368 | except Exception as e:
369 | traceback.print_exc()
370 | print e
371 | self.app.contents_tree.SetItemText(self.app.contents_tree.GetSelection(),
372 | unicode(getattr(self.edit_object, 'caption',
373 | getattr(self.edit_object, 'name',
374 | unicode(self.edit_object)))))
375 | self.app.save_button.Enable(True)
376 | self.command.SetBackgroundColour('White')
377 | self.app.Refresh()
378 | else:
379 | self.command.SetBackgroundColour('#FF8080')
380 | self.app.Refresh()
381 | event.Skip()
382 | self.Bind(wx.EVT_TEXT, edittext(self._selected_data, text, self, prop, validator, datatype), text)
383 | if fixer:
384 | newfixer = fixer(self.project, text)
385 | text.Bind(wx.EVT_SET_FOCUS, newfixer)
386 | text.Bind(wx.EVT_KILL_FOCUS, newfixer)
387 | newsizer.Add(text, 1, wx.RIGHT, 8)
388 | # Checkbox
389 | elif datatype is bool:
390 | class toggle(object):
391 | def __init__(self, edit_object, propname, control, app):
392 | self.edit_object = edit_object
393 | self.propname = propname
394 | self.control = control
395 | self.app = app
396 | def __call__(self, event):
397 | setattr(self.edit_object, self.propname, self.control.GetValue())
398 | self.app.save_button.Enable(True)
399 | boolcheck = wx.CheckBox(self.item_property_panel, -1, caption)
400 | boolcheck.SetValue(getattr(self._selected_data, prop))
401 | self.Bind(wx.EVT_CHECKBOX, toggle(self._selected_data, prop, boolcheck, self), boolcheck)
402 | newsizer.Add(boolcheck, 1, wx.LEFT, 178)
403 | # Image selection
404 | elif datatype is wx.Bitmap:
405 | class pickbitmap(object):
406 | def __init__(self, edit_object, propname, control, app):
407 | self.edit_object = edit_object
408 | self.propname = propname
409 | self.control = control
410 | self.app = app
411 | def __call__(self, event):
412 | images_path = os.path.join(self.app.path, 'Images')
413 | potentialdir = getattr(self.edit_object, self.propname, '')
414 | if potentialdir:
415 | images_path = os.path.join(self.app.path,
416 | os.path.dirname(potentialdir))
417 | default_path = (images_path
418 | if os.path.exists(images_path)
419 | else self.app.path)
420 | dlg = wx.FileDialog(
421 | self.app, message=_("Choose an image file for control"),
422 | defaultDir=default_path,
423 | defaultFile="",
424 | wildcard=(_("PNG images (*.png)")+'|*.png|'+
425 | _("GIF images (*.gif)")+'|*.gif|'+
426 | _("BMP images (*.bmp)")+'|*.bmp'),
427 | style=wx.OPEN)
428 | if dlg.ShowModal() == wx.ID_OK:
429 | image_file = dlg.GetPath()
430 | bitmap = wx.Bitmap(image_file, wx.BITMAP_TYPE_ANY)
431 | self.app.save_button.Enable(True)
432 | self.control.SetBitmapLabel(bitmap)
433 | setattr(self.edit_object, self.propname, image_file)
434 | self.app.Fit()
435 | self.app.Layout()
436 | self.app.Refresh()
437 |
438 | st = wx.StaticText(self.item_property_panel, -1, _("Image for control:"), style=wx.ALIGN_RIGHT)
439 | st.SetMinSize((175, 16))
440 | newsizer.Add(st, 0, wx.ALL|wx.ALIGN_TOP, 2)
441 | if getattr(self._selected_data, prop, ''):
442 | bitmap = wx.Bitmap(os.path.join(self.path, getattr(self._selected_data, prop)), wx.BITMAP_TYPE_ANY)
443 | else:
444 | bitmap = wx.EmptyBitmap(16, 16, 32)
445 | bitmap.SetMaskColour((0, 0, 0))
446 | if bitmap.HasAlpha():
447 | print "ALPHA"
448 | for xc in xrange(bitmap.GetWidth()):
449 | for yc in xrange(bitmap.GetHeight()):
450 | bitmap.SetAlpha(xc, yc, 255)
451 | choosefilebutton = wx.BitmapButton(self.item_property_panel, -1, bitmap)
452 | self.Bind(wx.EVT_BUTTON, pickbitmap(self._selected_data, prop, choosefilebutton, self), choosefilebutton)
453 | newsizer.Add(choosefilebutton, 1, wx.ALL|wx.EXPAND, 0)
454 | # Check list for enabled methods
455 | elif datatype is list:
456 | st = wx.StaticText(self.item_property_panel, -1, caption + ":", style=wx.ALIGN_RIGHT)
457 | st.SetMinSize((175, 16))
458 | newsizer.Add(st, 0, wx.ALL|wx.ALIGN_TOP, 2)
459 |
460 | clb = wx.CheckListBox(self.item_property_panel, -1)
461 | clb.Set([x[0] for x in self._selected_data.__python_methods__])
462 | clb.SetCheckedStrings(self._selected_data.enabled_methods)
463 | class Checker(object):
464 | def __init__(self, data, ctrl, app):
465 | self._data = data
466 | self._ctrl = ctrl
467 | self._app = app
468 | def __call__(self, event):
469 | index = event.GetSelection()
470 | method = self._ctrl.GetString(index)
471 | if self._ctrl.IsChecked(index):
472 | if method not in self._data.enabled_methods:
473 | self._data.enabled_methods.append(method)
474 | else:
475 | if method in self._data.enabled_methods:
476 | self._data.enabled_methods.pop(self._data.enabled_methods.index(method))
477 | self._app.save_button.Enable(True)
478 | checker = Checker(self._selected_data, clb, self)
479 | self.Bind(wx.EVT_CHECKLISTBOX, checker, clb)
480 | newsizer.Add(clb, 1, wx.RIGHT|wx.EXPAND, 8)
481 |
482 | # WHO KNOWS!
483 | else:
484 | newsizer.Add(wx.StaticText(self.item_property_panel, -1, caption + ": " + unicode(getattr(self._selected_data, prop))), 0, wx.EXPAND)
485 | sizer.Add(newsizer, 0, wx.EXPAND|wx.BOTTOM, 8)
486 | sizer.Layout()
487 | self.Refresh()
488 |
489 | def SelChanged(self, event):
490 | try:
491 | self._selected_data = self.contents_tree.GetItemPyData(self.contents_tree.GetSelection())
492 | except:
493 | self._selected_data = None
494 | self.setupPropsDialog()
495 |
496 | def DeleteItem(self, event):
497 | pass
498 |
499 | def ChangeTab(self, event):
500 | pass
501 |
502 | def SelectProjectImage(self, event):
503 | dlg = wx.FileDialog(
504 | self, message=_("Choose an image file"),
505 | defaultDir=(os.path.join(self.path,
506 | os.path.dirname(self.project.addin.image))
507 | if self.project.addin.image
508 | else self.path),
509 | defaultFile="",
510 | wildcard=(_("PNG images (*.png)")+u'|*.png|'+
511 | _("GIF images (*.gif)")+u'|*.gif|'+
512 | _("BMP images (*.bmp)")+u'|*.bmp'),
513 | style=wx.OPEN)
514 | if dlg.ShowModal() == wx.ID_OK:
515 | image_file = dlg.GetPath()
516 | self.project.addin.image = image_file
517 | try:
518 | self.updateProjectImage()
519 | except Exception as e:
520 | print "IMAGE LOAD", e
521 | self.save_button.Enable(True)
522 | event.Skip()
523 |
524 | def updateProjectImage(self):
525 | if self.project.addin.image:
526 | image_file = os.path.join(self.project._path, self.project.addin.image)
527 | bitmap = wx.Bitmap(image_file, wx.BITMAP_TYPE_ANY)
528 | self.icon_bitmap.SetBitmap(bitmap)
529 | self.Fit()
530 | self.Refresh()
531 |
532 | def SaveProject(self, event):
533 | try:
534 | print self.project.addin.xml
535 | print
536 | print self.project.addin.python
537 | self.project.save()
538 | if getattr(self.project.addin, 'warning', None):
539 | msgdlg = wx.MessageDialog(self, unicode(self.project.addin.warning),
540 | _("Project Information"),
541 | wx.OK | wx.ICON_INFORMATION)
542 | msgdlg.ShowModal()
543 | msgdlg.Destroy()
544 | del self.project.addin.warning
545 | self.save_button.Enable(False)
546 | self.setupPropsDialog()
547 | except Exception as e:
548 | traceback.print_exc()
549 | print e
550 |
551 | def OpenFolder(self, event):
552 | if getattr(self, 'path', None):
553 | os.startfile(self.path)
554 |
555 | def TreePopupRClick(self, event):
556 | id = self.contents_tree.HitTest(event.GetPosition())[0]
557 | self.contents_tree.SelectItem(id, True) # Set right-clicked item as selection for popups
558 |
559 | def TreePopup(self, event):
560 | menu = None
561 | sd = self._selected_data
562 | if sd is self._extensiontoplevel:
563 | menu = self.extensionmenu
564 | elif sd is self._menutoplevel:
565 | menu = self.menumenu
566 | elif sd is self._toolbartoplevel:
567 | menu = self.toolbarmenu
568 | elif isinstance(sd, addin.ControlContainer):
569 | menu = self.controlcontainermenu
570 | elif isinstance(sd, (addin.UIControl, addin.XMLAttrMap)):
571 | menu = wx.Menu()
572 | else:
573 | print sd
574 | if menu:
575 | if sd not in (self._extensiontoplevel, self._menutoplevel, self._toolbartoplevel):
576 | if menu.GetMenuItemCount():
577 | menu.AppendSeparator()
578 | removecmd = menu.Append(-1, _("Remove"))
579 | def remove(event):
580 | if self.project.addin.remove(sd):
581 | self.contents_tree.Delete(self.contents_tree.GetSelection())
582 | self.save_button.Enable(True)
583 | menu.Bind(wx.EVT_MENU, remove, removecmd)
584 | self.PopupMenu(menu)
585 | menu.Destroy()
586 |
587 | if __name__ == "__main__":
588 | app = wx.App(redirect=False)
589 | addin_window = AddinMakerAppWindow(None, -1)
590 | app.SetTopWindow(addin_window)
591 | addin_window.Show()
592 | app.MainLoop()
593 |
--------------------------------------------------------------------------------
/addin_ui.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | # generated by wxGlade 0.6.3 on Mon Sep 19 12:33:19 2011
4 |
5 | import wx
6 |
7 | # begin wxGlade: extracode
8 | _msize = wx.Size(700, 550)
9 | _msize = wx.Size(700, 550)
10 | # end wxGlade
11 |
12 |
13 |
14 | class AddinMakerWindow(wx.Frame):
15 | def __init__(self, *args, **kwds):
16 | # begin wxGlade: AddinMakerWindow.__init__
17 | kwds["style"] = wx.DEFAULT_FRAME_STYLE
18 | wx.Frame.__init__(self, *args, **kwds)
19 | self.full_app_panel = wx.Panel(self, -1)
20 | self.bottom_buttons_pane = wx.Panel(self.full_app_panel, -1)
21 | self.propsheet_panel = wx.Panel(self.full_app_panel, -1)
22 | self.tabs_notebook = wx.Notebook(self.propsheet_panel, -1, style=0)
23 | self.project_items_pane = wx.Panel(self.tabs_notebook, -1)
24 | self.item_property_panel = wx.Panel(self.project_items_pane, -1)
25 | self.project_settings_pane = wx.Panel(self.tabs_notebook, -1)
26 | self.logo_panel = wx.Panel(self.propsheet_panel, -1)
27 | self.properties_rows_holder_staticbox = wx.StaticBox(self.project_settings_pane, -1, "Project Properties:")
28 | self.title_panel = wx.Panel(self.full_app_panel, -1)
29 | self.title_label = wx.StaticText(self.title_panel, -1, "Python Add-In Wizard", style=wx.ALIGN_RIGHT)
30 | self.title_divider_line = wx.StaticLine(self.full_app_panel, -1)
31 | self.logo_bitmap = wx.StaticBitmap(self.logo_panel, -1, wx.Bitmap("images\\AddInDesktop64.png", wx.BITMAP_TYPE_ANY))
32 | self.folder_label = wx.StaticText(self.project_settings_pane, -1, "Working Folder:", style=wx.ALIGN_RIGHT)
33 | self.folder_button = wx.Button(self.project_settings_pane, -1, "Select Folder...")
34 | self.product_label = wx.StaticText(self.project_settings_pane, -1, "Select Product:", style=wx.ALIGN_RIGHT)
35 | self.product_combo_box = wx.ComboBox(self.project_settings_pane, -1, choices=["ArcMap", "ArcGlobe", "ArcScene", "ArcCatalog"], style=wx.CB_DROPDOWN|wx.CB_READONLY)
36 | self.static_line_1 = wx.StaticLine(self.project_settings_pane, -1)
37 | self.project_name_label = wx.StaticText(self.project_settings_pane, -1, "Name*:", style=wx.ALIGN_RIGHT)
38 | self.project_name = wx.TextCtrl(self.project_settings_pane, -1, "")
39 | self.project_version_label = wx.StaticText(self.project_settings_pane, -1, "Version*:", style=wx.ALIGN_RIGHT)
40 | self.project_version = wx.TextCtrl(self.project_settings_pane, -1, "")
41 | self.project_company_label = wx.StaticText(self.project_settings_pane, -1, "Company:", style=wx.ALIGN_RIGHT)
42 | self.project_company = wx.TextCtrl(self.project_settings_pane, -1, "")
43 | self.project_description_label = wx.StaticText(self.project_settings_pane, -1, "Description:", style=wx.ALIGN_RIGHT)
44 | self.project_description = wx.TextCtrl(self.project_settings_pane, -1, "")
45 | self.project_author_label = wx.StaticText(self.project_settings_pane, -1, "Author:", style=wx.ALIGN_RIGHT)
46 | self.project_author = wx.TextCtrl(self.project_settings_pane, -1, "")
47 | self.image_section_divider = wx.StaticLine(self.project_settings_pane, -1)
48 | self.project_image_label = wx.StaticText(self.project_settings_pane, -1, "Image:", style=wx.ALIGN_RIGHT)
49 | self.select_project_image = wx.Button(self.project_settings_pane, -1, "Select Image...")
50 | self.icon_bitmap = wx.StaticBitmap(self.project_settings_pane, -1, wx.Bitmap("images\\AddInDesktop48.png", wx.BITMAP_TYPE_ANY), style=wx.SIMPLE_BORDER)
51 | self.contents_tree = wx.TreeCtrl(self.project_items_pane, -1, style=wx.TR_HAS_BUTTONS|wx.TR_LINES_AT_ROOT|wx.TR_HIDE_ROOT|wx.TR_DEFAULT_STYLE|wx.RAISED_BORDER)
52 | self.bottom_buttons_spacer_panel = wx.Panel(self.bottom_buttons_pane, -1)
53 | self.open_folder = wx.Button(self.bottom_buttons_pane, -1, "Open Folder")
54 | self.save_button = wx.Button(self.bottom_buttons_pane, -1, "Save")
55 |
56 | self.__set_properties()
57 | self.__do_layout()
58 |
59 | self.Bind(wx.EVT_BUTTON, self.SelectFolder, self.folder_button)
60 | self.Bind(wx.EVT_COMBOBOX, self.ComboBox, self.product_combo_box)
61 | self.Bind(wx.EVT_TEXT_ENTER, self.ProjectNameText, self.project_name)
62 | self.Bind(wx.EVT_TEXT, self.ProjectNameText, self.project_name)
63 | self.Bind(wx.EVT_TEXT_ENTER, self.ProjectVersionText, self.project_version)
64 | self.Bind(wx.EVT_TEXT, self.ProjectVersionText, self.project_version)
65 | self.Bind(wx.EVT_TEXT_ENTER, self.ProjectCompanyText, self.project_company)
66 | self.Bind(wx.EVT_TEXT, self.ProjectCompanyText, self.project_company)
67 | self.Bind(wx.EVT_TEXT_ENTER, self.ProjectDescriptionText, self.project_description)
68 | self.Bind(wx.EVT_TEXT, self.ProjectDescriptionText, self.project_description)
69 | self.Bind(wx.EVT_TEXT_ENTER, self.ProjectAuthorText, self.project_author)
70 | self.Bind(wx.EVT_TEXT, self.ProjectAuthorText, self.project_author)
71 | self.Bind(wx.EVT_BUTTON, self.SelectProjectImage, self.select_project_image)
72 | self.Bind(wx.EVT_TREE_BEGIN_DRAG, self.BeginDrag, self.contents_tree)
73 | self.Bind(wx.EVT_TREE_END_DRAG, self.EndDrag, self.contents_tree)
74 | self.Bind(wx.EVT_TREE_DELETE_ITEM, self.DeleteItem, self.contents_tree)
75 | self.Bind(wx.EVT_TREE_SEL_CHANGED, self.SelChanged, self.contents_tree)
76 | self.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGING, self.ChangeTab, self.tabs_notebook)
77 | self.Bind(wx.EVT_BUTTON, self.OpenFolder, self.open_folder)
78 | self.Bind(wx.EVT_BUTTON, self.SaveProject, self.save_button)
79 | # end wxGlade
80 |
81 | def __set_properties(self):
82 | # begin wxGlade: AddinMakerWindow.__set_properties
83 | self.SetTitle("ArcGIS Python Add-In Wizard")
84 | self.SetMinSize(_msize)
85 | self.title_label.SetFont(wx.Font(16, wx.DEFAULT, wx.NORMAL, wx.NORMAL, 0, ""))
86 | self.title_panel.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_3DHIGHLIGHT))
87 | self.product_combo_box.SetSelection(0)
88 | self.project_name_label.SetMinSize((72, 16))
89 | self.project_version_label.SetMinSize((72, 16))
90 | self.project_company_label.SetMinSize((72, 16))
91 | self.project_description_label.SetMinSize((72, 16))
92 | self.project_author_label.SetMinSize((72, 16))
93 | self.project_image_label.SetMinSize((72, 16))
94 | self.contents_tree.SetMinSize((200, 484))
95 | # end wxGlade
96 |
97 | def __do_layout(self):
98 | # begin wxGlade: AddinMakerWindow.__do_layout
99 | full_window_sizer = wx.BoxSizer(wx.HORIZONTAL)
100 | main_sizer = wx.BoxSizer(wx.VERTICAL)
101 | bottom_buttons_sizer = wx.BoxSizer(wx.HORIZONTAL)
102 | content_sizer = wx.BoxSizer(wx.HORIZONTAL)
103 | splitter_sizer = wx.BoxSizer(wx.HORIZONTAL)
104 | items_sizer = wx.BoxSizer(wx.HORIZONTAL)
105 | item_property_sizer = wx.BoxSizer(wx.VERTICAL)
106 | fields_sizer = wx.BoxSizer(wx.VERTICAL)
107 | properties_rows_holder = wx.StaticBoxSizer(self.properties_rows_holder_staticbox, wx.VERTICAL)
108 | project_bitmap_display_sizer = wx.BoxSizer(wx.HORIZONTAL)
109 | properties_rows = wx.BoxSizer(wx.VERTICAL)
110 | project_image_sizer = wx.BoxSizer(wx.HORIZONTAL)
111 | project_author_sizer = wx.BoxSizer(wx.HORIZONTAL)
112 | project_description_sizer = wx.BoxSizer(wx.HORIZONTAL)
113 | project_company_sizer = wx.BoxSizer(wx.HORIZONTAL)
114 | project_version_sizer = wx.BoxSizer(wx.HORIZONTAL)
115 | project_name_sizer = wx.BoxSizer(wx.HORIZONTAL)
116 | product_sizer = wx.BoxSizer(wx.HORIZONTAL)
117 | folder_sizer = wx.BoxSizer(wx.HORIZONTAL)
118 | logo_sizer = wx.BoxSizer(wx.HORIZONTAL)
119 | title_sizer = wx.BoxSizer(wx.HORIZONTAL)
120 | title_sizer.Add(self.title_label, 0, wx.ALL|wx.ALIGN_RIGHT|wx.ALIGN_CENTER_HORIZONTAL, 8)
121 | self.title_panel.SetSizer(title_sizer)
122 | main_sizer.Add(self.title_panel, 0, wx.EXPAND|wx.ALIGN_RIGHT, 0)
123 | main_sizer.Add(self.title_divider_line, 0, wx.EXPAND, 0)
124 | logo_sizer.Add(self.logo_bitmap, 0, wx.TOP, 8)
125 | self.logo_panel.SetSizer(logo_sizer)
126 | splitter_sizer.Add(self.logo_panel, 0, wx.EXPAND, 0)
127 | folder_sizer.Add(self.folder_label, 0, wx.ALIGN_CENTER_VERTICAL, 8)
128 | folder_sizer.Add(self.folder_button, 1, wx.EXPAND, 8)
129 | fields_sizer.Add(folder_sizer, 0, wx.ALL|wx.EXPAND, 4)
130 | product_sizer.Add(self.product_label, 0, wx.ALIGN_CENTER_VERTICAL, 3)
131 | product_sizer.Add(self.product_combo_box, 0, 0, 3)
132 | fields_sizer.Add(product_sizer, 0, wx.ALL|wx.EXPAND, 4)
133 | fields_sizer.Add(self.static_line_1, 0, wx.ALL|wx.EXPAND, 8)
134 | project_name_sizer.Add(self.project_name_label, 0, wx.ALL|wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL, 2)
135 | project_name_sizer.Add(self.project_name, 1, wx.EXPAND, 0)
136 | properties_rows.Add(project_name_sizer, 1, wx.EXPAND, 0)
137 | project_version_sizer.Add(self.project_version_label, 0, wx.ALL|wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL, 2)
138 | project_version_sizer.Add(self.project_version, 1, wx.EXPAND, 0)
139 | properties_rows.Add(project_version_sizer, 1, wx.EXPAND, 0)
140 | project_company_sizer.Add(self.project_company_label, 0, wx.ALL|wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL, 2)
141 | project_company_sizer.Add(self.project_company, 1, wx.EXPAND, 0)
142 | properties_rows.Add(project_company_sizer, 1, wx.EXPAND, 0)
143 | project_description_sizer.Add(self.project_description_label, 0, wx.ALL|wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL, 2)
144 | project_description_sizer.Add(self.project_description, 1, wx.EXPAND, 0)
145 | properties_rows.Add(project_description_sizer, 1, wx.EXPAND, 0)
146 | project_author_sizer.Add(self.project_author_label, 0, wx.ALL|wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL, 2)
147 | project_author_sizer.Add(self.project_author, 1, wx.EXPAND, 0)
148 | properties_rows.Add(project_author_sizer, 1, wx.EXPAND, 0)
149 | properties_rows.Add(self.image_section_divider, 0, wx.ALL|wx.EXPAND, 2)
150 | project_image_sizer.Add(self.project_image_label, 0, wx.ALL|wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL, 2)
151 | project_image_sizer.Add(self.select_project_image, 0, 0, 0)
152 | properties_rows.Add(project_image_sizer, 1, wx.EXPAND, 3)
153 | properties_rows_holder.Add(properties_rows, 0, wx.EXPAND, 0)
154 | project_bitmap_display_sizer.Add(self.icon_bitmap, 0, wx.TOP, 3)
155 | properties_rows_holder.Add(project_bitmap_display_sizer, 1, wx.LEFT|wx.EXPAND, 80)
156 | fields_sizer.Add(properties_rows_holder, 0, wx.EXPAND, 0)
157 | self.project_settings_pane.SetSizer(fields_sizer)
158 | items_sizer.Add(self.contents_tree, 0, wx.EXPAND, 0)
159 | self.item_property_panel.SetSizer(item_property_sizer)
160 | items_sizer.Add(self.item_property_panel, 1, wx.EXPAND, 0)
161 | self.project_items_pane.SetSizer(items_sizer)
162 | self.tabs_notebook.AddPage(self.project_settings_pane, "Project Settings")
163 | self.tabs_notebook.AddPage(self.project_items_pane, "Add-In Contents")
164 | splitter_sizer.Add(self.tabs_notebook, 1, wx.EXPAND, 4)
165 | content_sizer.Add(splitter_sizer, 1, wx.EXPAND, 4)
166 | self.propsheet_panel.SetSizer(content_sizer)
167 | main_sizer.Add(self.propsheet_panel, 1, wx.EXPAND, 0)
168 | bottom_buttons_sizer.Add(self.bottom_buttons_spacer_panel, 1, wx.EXPAND, 0)
169 | bottom_buttons_sizer.Add(self.open_folder, 0, wx.ALL, 2)
170 | bottom_buttons_sizer.Add(self.save_button, 0, wx.ALL, 2)
171 | self.bottom_buttons_pane.SetSizer(bottom_buttons_sizer)
172 | main_sizer.Add(self.bottom_buttons_pane, 0, wx.EXPAND, 4)
173 | self.full_app_panel.SetSizer(main_sizer)
174 | full_window_sizer.Add(self.full_app_panel, 1, wx.ALL|wx.EXPAND, 1)
175 | self.SetSizer(full_window_sizer)
176 | full_window_sizer.Fit(self)
177 | self.Layout()
178 | self.Centre()
179 | # end wxGlade
180 |
181 | def SelectFolder(self, event): # wxGlade: AddinMakerWindow.
182 | print "Event handler `SelectFolder' not implemented!"
183 | event.Skip()
184 |
185 | def ComboBox(self, event): # wxGlade: AddinMakerWindow.
186 | print "Event handler `ComboBox' not implemented!"
187 | event.Skip()
188 |
189 | def ProjectNameText(self, event): # wxGlade: AddinMakerWindow.
190 | print "Event handler `ProjectNameText' not implemented!"
191 | event.Skip()
192 |
193 | def ProjectVersionText(self, event): # wxGlade: AddinMakerWindow.
194 | print "Event handler `ProjectVersionText' not implemented!"
195 | event.Skip()
196 |
197 | def ProjectCompanyText(self, event): # wxGlade: AddinMakerWindow.
198 | print "Event handler `ProjectCompanyText' not implemented!"
199 | event.Skip()
200 |
201 | def ProjectDescriptionText(self, event): # wxGlade: AddinMakerWindow.
202 | print "Event handler `ProjectDescriptionText' not implemented!"
203 | event.Skip()
204 |
205 | def ProjectAuthorText(self, event): # wxGlade: AddinMakerWindow.
206 | print "Event handler `ProjectAuthorText' not implemented!"
207 | event.Skip()
208 |
209 | def SelectProjectImage(self, event): # wxGlade: AddinMakerWindow.
210 | print "Event handler `SelectProjectImage' not implemented!"
211 | event.Skip()
212 |
213 | def BeginDrag(self, event): # wxGlade: AddinMakerWindow.
214 | print "Event handler `BeginDrag' not implemented!"
215 | event.Skip()
216 |
217 | def EndDrag(self, event): # wxGlade: AddinMakerWindow.
218 | print "Event handler `EndDrag' not implemented!"
219 | event.Skip()
220 |
221 | def DeleteItem(self, event): # wxGlade: AddinMakerWindow.
222 | print "Event handler `DeleteItem' not implemented!"
223 | event.Skip()
224 |
225 | def SelChanged(self, event): # wxGlade: AddinMakerWindow.
226 | print "Event handler `SelChanged' not implemented!"
227 | event.Skip()
228 |
229 | def ChangeTab(self, event): # wxGlade: AddinMakerWindow.
230 | print "Event handler `ChangeTab' not implemented!"
231 | event.Skip()
232 |
233 | def OpenFolder(self, event): # wxGlade: AddinMakerWindow.
234 | print "Event handler `OpenFolder' not implemented!"
235 | event.Skip()
236 |
237 | def SaveProject(self, event): # wxGlade: AddinMakerWindow.
238 | print "Event handler `SaveProject' not implemented!"
239 | event.Skip()
240 |
241 | # end of class AddinMakerWindow
242 |
243 |
244 | if __name__ == "__main__":
245 | app = wx.PySimpleApp(0)
246 | wx.InitAllImageHandlers()
247 | addin_window = AddinMakerWindow(None, -1, "")
248 | app.SetTopWindow(addin_window)
249 | addin_window.Show()
250 | app.MainLoop()
251 |
--------------------------------------------------------------------------------
/i18n.py:
--------------------------------------------------------------------------------
1 | __all__ = ['_']
2 |
3 | import json
4 | import os
5 |
6 | try:
7 | translations_dict = json.loads(open(os.path.join('resources', 'resource_strings.json'), 'rb').read().decode('utf-8'))
8 | except:
9 | print "Can't load translation strings file."
10 |
11 | def _(the_text):
12 | return translations_dict.get(the_text, the_text)
13 |
--------------------------------------------------------------------------------
/images/AddInDesktop.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonbot/addin-wizard/eeaf8d8988a0c24d110918241f7bd03a24644c1a/images/AddInDesktop.ico
--------------------------------------------------------------------------------
/images/AddInDesktop48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonbot/addin-wizard/eeaf8d8988a0c24d110918241f7bd03a24644c1a/images/AddInDesktop48.png
--------------------------------------------------------------------------------
/images/AddInDesktop64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonbot/addin-wizard/eeaf8d8988a0c24d110918241f7bd03a24644c1a/images/AddInDesktop64.png
--------------------------------------------------------------------------------
/packaging/README.txt:
--------------------------------------------------------------------------------
1 | This is a stub project created by the ArcGIS Desktop Python AddIn Wizard.
2 |
3 | MANIFEST
4 | ========
5 |
6 | README.txt : This file
7 |
8 | makeaddin.py : A script that will create a .esriaddin file out of this
9 | project, suitable for sharing or deployment
10 |
11 | config.xml : The AddIn configuration file
12 |
13 | Images/* : all UI images for the project (icons, images for buttons,
14 | etc)
15 |
16 | Install/* : The Python project used for the implementation of the
17 | AddIn. The specific python script to be used as the root
18 | module is specified in config.xml.
19 |
20 | NOTE TO ADD-IN AUTHORS: Please edit this file to be relevant documentation
21 | for your project or delete it before distributing.
22 |
--------------------------------------------------------------------------------
/packaging/makeaddin.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 | import os
3 | import re
4 | import zipfile
5 |
6 | current_path = os.path.dirname(os.path.abspath(__file__))
7 |
8 | out_zip_name = os.path.join(current_path,
9 | os.path.basename(current_path) + ".esriaddin")
10 |
11 | BACKUP_FILE_PATTERN = re.compile(".*_addin_[0-9]+[.]py$", re.IGNORECASE)
12 |
13 | def looks_like_a_backup(filename):
14 | return bool(BACKUP_FILE_PATTERN.match(filename))
15 |
16 | with zipfile.ZipFile(out_zip_name, 'w', zipfile.ZIP_DEFLATED) as zip_file:
17 | for filename in ('config.xml', 'README.txt', 'makeaddin.py'):
18 | full_filename = os.path.join(current_path, filename)
19 | if os.path.exists(full_filename):
20 | zip_file.write(full_filename, filename)
21 | dirs_to_add = ['Images', 'Install']
22 | for directory in dirs_to_add:
23 | for (path, dirs, files) in os.walk(os.path.join(current_path,
24 | directory)):
25 | archive_path = os.path.relpath(path, current_path)
26 | found_file = False
27 | for file in (f for f in files if not looks_like_a_backup(f)):
28 | archive_file = os.path.join(archive_path, file)
29 | print archive_file
30 | zip_file.write(os.path.join(path, file), archive_file)
31 | found_file = True
32 | if not found_file:
33 | zip_file.writestr(os.path.join(archive_path,
34 | 'placeholder.txt'),
35 | "(Empty directory)")
36 |
--------------------------------------------------------------------------------
/resources/resource_strings.json:
--------------------------------------------------------------------------------
1 | {
2 |
3 | "**** THIS STRING RESOURCE FILE IS EXPECTED TO BE ENCODED IN UTF-8 ***": "(Do not translate this string)",
4 | "It is arranged into ORIGINAL: TRANSLATION pairs.": "(Do not translate this string)",
5 |
6 | "Add-In Contents": "Add-In Contents",
7 | "ArcGIS Python Add-In Wizard": "ArcGIS Python Add-In Wizard",
8 | "Author:": "Author:",
9 | "Button": "Button",
10 | "Caption": "Caption",
11 | "Choose a directory to use as an Add-In project root:": "Choose a directory to use as an Add-In project root:",
12 | "Class Name": "Class Name",
13 | "Column Count": "Column Count",
14 | "Company:": "Company:",
15 | "Description": "Description",
16 | "Description:": "Description:",
17 | "EXTENSIONS": "EXTENSIONS",
18 | "Extension": "Extension",
19 | "Extensions": "Extensions",
20 | "Has Separator": "Has Separator",
21 | "Help Content": "Help Content",
22 | "Help Heading": "Help Heading",
23 | "Hint Text": "Hint Text",
24 | "ID (Variable Name)": "ID (Variable Name)",
25 | "Image for Control": "Image for Control",
26 | "Image for control:": "Image for control:",
27 | "Image:": "Image:",
28 | "Is Shortcut Menu": "Is Shortcut Menu",
29 | "Load Automatically": "Load Automatically",
30 | "MENUS": "MENUS",
31 | "Menu": "Menu",
32 | "Menu Style": "Menu Style",
33 | "Message": "Message",
34 | "Methods to Implement": "Methods to Implement",
35 | "Name": "Name",
36 | "Name*:": "Name*:",
37 | "New Extension": "New Extension",
38 | "New Menu": "New Menu",
39 | "Open Folder": "Open Folder",
40 | "Project Properties:": "Project Properties:",
41 | "Project Settings": "Project Settings",
42 | "Python Add-In Wizard": "Python Add-In Wizard",
43 | "Save": "Save",
44 | "Save before exiting?": "Save before exiting?",
45 | "Save your changes before exiting?": "Save your changes before exiting?",
46 | "Select Folder...": "Select Folder...",
47 | "Select Image...": "Select Image...",
48 | "Select Product:": "Select Product:",
49 | "Show Initially": "Show Initially",
50 | "TOOLBARS": "TOOLBARS",
51 | "Toolbar": "Toolbar",
52 | "Toolbars": "Toolbars",
53 | "Tooltip": "Tooltip",
54 | "Toplevel Menus": "Toplevel Menus",
55 | "Version*:": "Version*:",
56 | "Working Folder:": "Working Folder:"
57 | }
58 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | from distutils.core import setup
2 | import py2exe
3 | import glob
4 |
5 | setup(windows=[
6 | {'script': 'addin_assistant.pyw',
7 | 'icon_resources': [(1, "images\\AddInDesktop.ico")]
8 | }
9 | ],
10 | options={ "py2exe": { "dll_excludes": ["MSVCP90.dll"] }},
11 | data_files=[('images', glob.glob("images\\*.png") +
12 | glob.glob("images\\*.ico")),
13 | ('packaging', glob.glob("packaging\\*.*")),
14 | ('resources', glob.glob("resources\\*.*"))]
15 | )
16 |
--------------------------------------------------------------------------------