Permalink
Browse files

Initial open source release.

  • Loading branch information...
0 parents commit e20a70d9638796afdca7b6203c74e45bff756563 @codewarrior0 codewarrior0 committed Feb 14, 2012
Showing with 21,600 additions and 0 deletions.
  1. +19 −0 .gitignore
  2. +3 −0 .gitmodules
  3. +13 −0 LICENSE.txt
  4. +238 −0 README.html
  5. +42 −0 albow/README.txt
  6. +1 −0 albow/__init__.py
  7. +362 −0 albow/controls.py
  8. +140 −0 albow/dialogs.py
  9. +450 −0 albow/fields.py
  10. +330 −0 albow/file_dialogs.py
  11. +51 −0 albow/grid_view.py
  12. +49 −0 albow/image_array.py
  13. +159 −0 albow/layout.py
  14. +180 −0 albow/menu.py
  15. +66 −0 albow/menu_bar.py
  16. +206 −0 albow/music.py
  17. +108 −0 albow/openglwidgets.py
  18. +188 −0 albow/palette_view.py
  19. +167 −0 albow/resource.py
  20. +405 −0 albow/root.py
  21. +24 −0 albow/screen.py
  22. +30 −0 albow/shell.py
  23. +24 −0 albow/sound.py
  24. +151 −0 albow/tab_panel.py
  25. +182 −0 albow/table_view.py
  26. +106 −0 albow/text_screen.py
  27. +216 −0 albow/theme.py
  28. +44 −0 albow/utils.py
  29. +19 −0 albow/vectors.py
  30. +1 −0 albow/version.py
  31. +749 −0 albow/widget.py
  32. +3 −0 art.txt
  33. +44 −0 bresenham.py
  34. BIN char.png
  35. +277 −0 config.py
  36. +30 −0 depths.py
  37. +111 −0 directories.py
  38. BIN doc/images/brush0.jpg
  39. BIN doc/images/chunk.jpg
  40. BIN doc/images/clone0.jpg
  41. BIN doc/images/clone1.jpg
  42. BIN doc/images/clone2.jpg
  43. BIN doc/images/clone3.jpg
  44. BIN doc/images/crane0.jpg
  45. BIN doc/images/crane1.jpg
  46. BIN doc/images/crane2.jpg
  47. BIN doc/images/replace.jpg
  48. BIN doc/images/ruinedcittic.jpg
  49. BIN doc/images/selection0.jpg
  50. BIN doc/images/selection1.jpg
  51. BIN doc/images/selection2.jpg
  52. +65 −0 editortools/__init__.py
  53. +1,132 −0 editortools/brush.py
  54. +502 −0 editortools/chunk.py
  55. +1,116 −0 editortools/clone.py
  56. +370 −0 editortools/fill.py
  57. +302 −0 editortools/filter.py
  58. +509 −0 editortools/player.py
  59. +1,119 −0 editortools/select.py
  60. +878 −0 editortools/toolbasics.py
  61. +163 −0 errorreporting.py
  62. BIN favicon.png
  63. +1,297 −0 filters/Forester.py
  64. +107 −0 filters/MCEditForester.py
  65. +271 −0 filters/decliff.py
  66. +115 −0 filters/demo/filterdemo.py
  67. +75 −0 filters/floodwater.py
  68. +50 −0 filters/mcInterface.py
  69. +67 −0 filters/smooth.py
  70. +60 −0 filters/surfacerepair.py
  71. +76 −0 filters/topsoil.py
  72. +124 −0 fonts/COPYRIGHT.TXT
  73. +11 −0 fonts/README.TXT
  74. +162 −0 fonts/RELEASENOTES.TXT
  75. BIN fonts/Vera.ttf
  76. BIN fonts/VeraBd.ttf
  77. +165 −0 frustum.py
  78. +44 −0 glbackground.py
  79. +217 −0 glutils.py
  80. BIN gui.png
  81. +341 −0 history.txt
  82. +400 −0 history_alpha94.txt
  83. +3,792 −0 leveleditor.py
  84. +17 −0 main.py
  85. BIN mcedit.ico
  86. +1,321 −0 mcedit.py
  87. +706 −0 mceutils.py
  88. +427 −0 mcplatform.py
  89. +411 −0 oldhistory.txt
Sorry, we could not display the entire diff because it was too big.
19 .gitignore
@@ -0,0 +1,19 @@
+
+# git-ls-files --others --exclude-from=.git/info/exclude
+# Lines that start with '#' are comments.
+# For a project mostly in C, the following would be a good set of
+# exclude patterns (uncomment them if you want to use them):
+# *.[oa]
+# *~
+
+*.pyc
+*.pyo
+*~
+
+dist
+build
+
+#profiling/outputs
+mcedit.ini
+*.log
+mcedit.profile
3 .gitmodules
@@ -0,0 +1,3 @@
+[submodule "pymclevel"]
+ path = pymclevel
+ url = git://github.com/codewarrior0/pymclevel
13 LICENSE.txt
@@ -0,0 +1,13 @@
+Copyright (c) 2010-2012 David Rio Vierra
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
238 README.html
@@ -0,0 +1,238 @@
+<html>
+<title>
+MCEdit Alpha
+</title>
+<body>
+<div style="width:800px;margin:0px auto;">
+<img src="MCEditData/favicon.png">
+For info about the latest version, be sure to visit the <a href="http://www.minecraftforum.net/viewtopic.php?f=25&t=15522">
+thread on the official forums.</a><br>
+
+<br>
+This program is shareware. If you enjoy it or find it useful, please support the author.<br>
+<a href="https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=codewarrior@hawaii.rr.com&item_name=MCEdit,%20a%20Minecraft%20World%20Editor"><img src="https://www.paypal.com/en_GB/i/btn/btn_donate_LG.gif"></a>
+
+<div class="content">
+ <br />MCEdit is a versatile map utility, designed for moving blocks from one level to another. With it, you can open a level of nearly any type and fly around in glorious 3D with multiple levels of detail! Select millions of blocks and clone them anywhere else in the level. Fill a boxy selection with the block of your choice, or replace one kind of block with another. Export blocks to a &quot;schematic&quot; file for later use. Import a schematic or an entire level into any world using the crane. Move the player or his spawn point. Create and and remove chunks from the world. Fill blackened areas with light. Find bugs!
+ <pre>
+Controls:
+ WASD - Move
+ Q / Z - Altitude
+ SPACE - Stop
+
+ Right-Click - Toggle Look-Mode
+
+ Right-Click and Drag - Quick Look
+
+ Left-Click - Use Tool
+ 1-9 - Select Tool
+ Mousewheel - Change Tool Distance
+
+ E - Rotate Blocks
+ R - Roll Blocks
+ F - Flip Blocks
+
+ ESCAPE - Holster Tools
+ DELETE - Remove Selected Blocks
+
+ F1-F5 - Open World1-World5
+ O - Open Level...
+ N - Create World...
+ Control-S - Save World
+
+ Control-A - Select All
+ Control-D - Deselect
+
+ Control-F - Change view distance
+
+ Alt-F4 - Leave
+
+ Control-Z - Undo
+</pre>
+ <span id="Tools">
+ </span><br />Here's a quick overview of a few of the different tools we have:<br /><br />
+ <ul>
+ <li>
+ <a href="#Selection">Selection</a>: Make a selection by marking its corners. Press again to switch corners.<br />
+ </li>
+ <li>
+ <a href="#Clone">Clone</a>: Copies the selected blocks. Click to set the copy down, then press &quot;Clone&quot; to clone.
+ <span style="font-weight: bold">E R F
+ </span> to transform blocks.<br />
+ </li>
+ <li>
+ <a href="#Fill">Fill</a>: Fills the selection with a chosen block type, optionally replacing another type.
+ <span style="font-weight: bold">R
+ </span> to start replacing,
+ <span style="font-weight: bold">E
+ </span> to swap materials.
+ </li>
+ <li>
+ <a href="#Pencil">Pencil</a>: Export the selection as a Schematic file for later use.
+ </li>
+ <li>
+ <a href="#Crane">Crane</a>: Import a Schematic file or an entire level. Use this to convert older levels by importing them into an Alpha world.
+ </li>
+ <li>
+ <a href="#Player">Player</a>: Click to move the player. Press twice to teleport.
+ </li>
+ <li>
+ <a href="#Spawn">Spawn</a>: Click to move the player's spawn. Press twice to teleport.
+ </li>
+ <li>
+ <a href="#Chunk">Chunk</a>: Create, delete, and light chunks. Prune away all of the chunks outside the selection.
+ </li>
+ </ul><br />There are a few
+ <span style="font-weight: bold">Nudge
+ </span> buttons scattered around. To use them, click and hold down on one. While holding the mouse button, use the movement keys (defaults:
+ <span style="font-weight: bold">WASDQZ
+ </span>) to move the related item around. Hold Shift to nudge further.<br /><br />If you have a powerful computer, press Control-F to see farther.<br /><br />
+ <span id="Selection">
+ <span style="font-weight: bold">Selection tools:
+ </span>
+ </span><br />Mark a selection using blue and yellow cubes. Here is a selection in progress.<br />
+ <a href="doc/images/selection0.jpg">
+ <img src="doc/images/selection0.jpg" class="simg" alt="Image"></a><br /><br />A selection.<br />
+ <a href="doc/images/selection1.jpg">
+ <img src="doc/images/selection1.jpg" class="simg" alt="Image"></a><br /><br />
+ <span id="Clone">
+ <span style="font-weight: bold">Clone tool:
+ </span>
+ </span><br />Quickly copy blocks within the same level. The selected blocks are cloned into the green box and will follow your cursor around until you click. <br />
+ <a href="doc/images/clone0.jpg">
+ <img src="doc/images/clone0.jpg" class="simg" alt="Image"></a><br /><br />Click once, and your duplicate blocks are set down and will begin pulsating. Click again to pick them back up.<br />
+ <a href="doc/images/clone1.jpg">
+ <img src="doc/images/clone1.jpg" class="simg" alt="Image"></a><br /><br />Use the keys
+ <span style="font-weight: bold">E R F
+ </span> to rotate, roll, and flip the blocks. <br />
+ <a href="doc/images/clone2.jpg">
+ <img src="doc/images/clone2.jpg" class="simg" alt="Image"></a><br /><br />Press ENTER or click
+ <span style="font-weight: bold">Clone
+ </span> to expand public housing. The clone is finished when the selection is moved to the newly cloned object.<br />
+ <a href="doc/images/clone3.jpg">
+ <img src="doc/images/clone3.jpg" class="simg" alt="Image"></a><br /><br />
+ <span id="Fill">
+ <span style="font-weight: bold">Fill tool:
+ </span>
+ </span><br />
+ <a href="doc/images/replace.jpg">
+ <img src="doc/images/replace.jpg" class="simg" alt="Image"></a><br />You'll have to play around with this one on your own.<br /><br />
+ <span id="Pencil">
+ <span style="font-weight: bold">Pencil tool:
+ </span>
+ </span><br />Save the current selection as a &quot;schematic&quot; file. This file can be loaded into another level using the Crane. Nothing interesting to see here, unless you want a pic of a &quot;Save file...&quot; window.<br /><br />
+ <span id="Crane">
+ <span style="font-weight: bold">Crane tool:
+ </span>
+ </span><br />The crane tool lets you import blocks from different sources: It can place blocks previously saved by the pencil to a schematic. It can import
+ <span style="font-style: italic">.inv
+ </span> files created by
+ <a href="http://www.minecraftforum.net/viewtopic.php?t=15921" class="postlink">INVEdit</a> as chests. It can import an entire level into another level. It will ask you to choose a level or schematic. After you've chosen, the level or schematic will appear in the green box. The blocks are fully loaded and ready to use, even though they appear slowly. Click to set it down. <br />
+ <a href="doc/images/crane0.jpg">
+ <img src="doc/images/crane0.jpg" class="simg" alt="Image"></a><br />Here, I've placed
+ <a href="http://www.minecraftforum.net/viewtopic.php?f=23&amp;t=13796" class="postlink">the destroyed city.</a><br /><br />As before, you can click to pick the blocks up, and use
+ <span style="font-weight: bold">E R F
+ </span> to rotate, roll, and flip them.<br />
+ <a href="doc/images/crane1.jpg">
+ <img src="doc/images/crane1.jpg" class="simg" alt="Image"></a><br /><br />Press ENTER or click
+ <span style="font-weight: bold">Import
+ </span> to construct. Constructions of up to 64 million blocks should take less than a minute. The copy is done once you regain control. You should press Control-S to save your level now. The lighting recalculation for this city took about 3 minutes on a Core i5.<br />
+ <a href="doc/images/crane2.jpg">
+ <img src="doc/images/crane2.jpg" class="simg" alt="Image"></a><br /><br />Et voila.<br /><br />
+ <a href="http://img413.imageshack.us/img413/4492/voila.jpg">
+ <img src="http://img413.imageshack.us/img413/4492/voila.jpg" class="simg" alt="Image"></a><br /><br />
+ <span id="Chunk">
+ <span style="font-weight: bold">Chunk tool:
+ </span>
+ </span><br />
+ <img src="doc/images/chunk.jpg" alt="Image" /><br />When you pick the Chunk tool, the visible selection is automatically expanded to cover all of the chunks it touches. Press
+ <span style="font-style: italic">Create
+ </span> to create all missing chunks in the selection, ignoring any chunks already present. Press
+ <span style="font-style: italic">Destroy
+ </span> to remove any chunks within the selection. Use
+ <span style="font-style: italic">Prune
+ </span> to keep all of the chunks in the selection, and delete the ones outside. All of these will ask you for confirmation because there's no way to undo them.<br /><br />Finally, you can press
+ <span style="font-style: italic">Relight
+ </span> to fix any bad lighting within the selected chunks. If other programs leave your level full of black spots, you can fix it with this.<br /><br />
+ <span id="Player">
+ </span><br /><br />
+ <span id="Spawn">
+ </span><br /><br />(not pictured: Player and Player Spawn tools, DELETE operation)<br /><br /><br />
+ <a href="http://www.minecraftforum.net/viewtopic.php?f=25&amp;t=15522#p271802" class="postlink">Click here for downloads</a><br /><br />A few people can't get MCEdit to start up or do anything at all. If MCEdit doesn't work for you, please post here or keep in touch via PM or e-mail. I'll try to work with you and figure out what's wrong.<br /><br />
+ <span style="font-size: 85%; line-height: 116%;">MCEdit: over one trillion blocks cloned
+ </span>
+</div>
+
+
+<br>
+This program is shareware. If you enjoy it or find it useful, please support the author.<br>
+<a href="https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=codewarrior@hawaii.rr.com&item_name=MCEdit,%20a%20Minecraft%20World%20Editor"><img src="https://www.paypal.com/en_GB/i/btn/btn_donate_LG.gif"></a>
+
+
+Open and save the following formats, compressed or non:
+(saving not guaranteed)
+
+<br>
+<br>
+Alpha Creative/Survival/Multiplayer (level.dat + folders)
+<br>
+
+Classic Indev (*.mclevel)
+<br>
+Classic Creative/Survival (*.mine, server_level.dat) **
+<br>
+<br>
+
+
+** If your Classic level fails to load, or loads with the blocks all
+out of order, put the dimensions of the map into the filename, e.g.
+server_level_256_256_128.dat.
+<br>
+<br>
+
+
+Import schematics created by MCEdit, schematics created by Redstone Simulator, and
+inventory files created by INVEdit. (as chests!)
+
+<br>
+<br>
+Make backups!
+
+<br>
+<br>
+Report any bugs to codewarrior0@gmail.com or post on the <a href=http://www.minecraftforum.net/viewtopic.php?f=25&t=15522>official forum thread</a>
+
+<pre>
+
+Caveats:
+
+After increasing the window size, mouse input no longer works correctly.
+Restart MCEdit to fix this.
+
+Support for non-English computers and foreign character sets is shaky. For best results,
+put the MCEdit folder directly into your C:\
+
+Creating and deleting chunks will save them to disk or delete them immediately.
+
+Cannot resize finite levels.
+
+Many tiles are replaced when converting from the older maps to
+Minecraft Alpha maps. All cloth tiles are bleached white and any
+"Infinite" liquids are replaced with still liquids.
+
+The conversion also works the other way: Blocks introduced since Alpha are
+converted to white cloth when moved to a Classic level. There is no way to control
+which blocks are replaced during a conversion.
+
+There is no conversion between the old Indev and the older Creative/Survival.
+Take caution when copying between them.
+
+Classic levels require their dimensions to be in the file name, or
+else MCEdit will attempt to guess them based on the file's size.
+
+
+Copyright 2010 David Rio Vierra
+</pre>
+</div>
+</body>
+</html>
42 albow/README.txt
@@ -0,0 +1,42 @@
+ALBOW - A Little Bit of Widgetry for PyGame
+-------------------------------------------
+
+Version 1.1
+
+This is a rather basic, no-frills widget set for creating a GUI using
+PyGame. It has been developed over the course of my last three
+PyWeek competition entries.
+
+Contents
+--------
+
+albow Package containing the Python modules. Put it
+ on your PYTHONPATH or in the top level directory
+ of your PyGame application.
+
+demo.py A demonstration of most of Albow's functionality.
+ Run it using
+
+ pythonw albow.py
+
+doc Documentation in html format. Start with index.html.
+
+Resources Some resources used by demo.py. Also contains some
+ default fonts (Vera.ttf and VeraBd.ttf) that you can
+ use in your applications if you wish.
+
+License
+-------
+
+This is free software. You may use it, redistribute it and create and
+distribute derivative works from it without restriction.
+
+The Bitstream Vera fonts in the Resources/fonts directory are covered
+by their own (very liberal) license, a copy of which is included in
+that directory.
+
+Author
+------
+
+Gregory Ewing
+greg.ewing@canterbury.ac.nz
1 albow/__init__.py
@@ -0,0 +1 @@
+"""ALBOW - A Little Bit of Widgetry for PyGame
362 albow/controls.py
@@ -0,0 +1,362 @@
+#
+# Albow - Controls
+#
+
+from pygame import Rect, draw
+from widget import Widget, overridable_property
+from theme import ThemeProperty
+from utils import blit_in_rect, frame_rect
+import resource
+
+#---------------------------------------------------------------------------
+
+class Control(object):
+
+ highlighted = overridable_property('highlighted')
+ enabled = overridable_property('enabled')
+ value = overridable_property('value')
+
+ enable = None
+ ref = None
+ _highlighted = False
+ _enabled = True
+ _value = None
+
+ def get_value(self):
+ ref = self.ref
+ if ref:
+ return ref.get()
+ else:
+ return self._value
+
+ def set_value(self, x):
+ ref = self.ref
+ if ref:
+ ref.set(x)
+ else:
+ self._value = x
+
+ def get_highlighted(self):
+ return self._highlighted
+
+
+ def get_enabled(self):
+ enable = self.enable
+ if enable:
+ return enable()
+ else:
+ return self._enabled
+
+ def set_enabled(self, x):
+ self._enabled = x
+
+#---------------------------------------------------------------------------
+
+class AttrRef(object):
+
+ def __init__(self, obj, attr):
+ self.obj = obj
+ self.attr = attr
+
+ def get(self):
+ return getattr(self.obj, self.attr)
+
+ def set(self, x):
+ setattr(self.obj, self.attr, x)
+
+#---------------------------------------------------------------------------
+
+class ItemRef(object):
+
+ def __init__(self, obj, item):
+ self.obj = obj
+ self.item = item
+
+ def get(self):
+ return self.obj[self.item]
+
+ def set(self, x):
+ self.obj[self.item] = x
+
+#---------------------------------------------------------------------------
+
+class Label(Widget):
+
+ text = overridable_property('text')
+ align = overridable_property('align')
+
+ hover_color = ThemeProperty('hover_color')
+ highlight_color = ThemeProperty('highlight_color')
+ disabled_color = ThemeProperty('disabled_color')
+ highlight_bg_color = ThemeProperty('highlight_bg_color')
+ hover_bg_color = ThemeProperty('hover_bg_color')
+ enabled_bg_color = ThemeProperty('enabled_bg_color')
+ disabled_bg_color = ThemeProperty('disabled_bg_color')
+
+ enabled = True
+ highlighted = False
+ _align = 'l'
+
+ def __init__(self, text, width=None, **kwds):
+ Widget.__init__(self, **kwds)
+ font = self.font
+ lines = text.split("\n")
+ tw, th = 0, 0
+ for line in lines:
+ w, h = font.size(line)
+ tw = max(tw, w)
+ th += h
+ if width is not None:
+ tw = width
+ else:
+ tw = max(1, tw)
+ d = 2 * self.margin
+ self.size = (tw + d, th + d)
+ self._text = text
+
+ def __repr__(self):
+ return "Label {0}, child of {1}".format(self.text, self.parent)
+ def get_text(self):
+ return self._text
+
+ def set_text(self, x):
+ self._text = x
+
+ def get_align(self):
+ return self._align
+
+ def set_align(self, x):
+ self._align = x
+
+ def draw(self, surface):
+ if not self.enabled:
+ fg = self.disabled_color
+ bg = self.disabled_bg_color
+ else:
+ fg = self.fg_color
+ bg = self.enabled_bg_color
+ if self.is_default:
+ fg = self.default_choice_color or fg
+ bg = self.default_choice_bg_color or bg
+ if self.is_hover:
+ fg = self.hover_color or fg
+ bg = self.hover_bg_color or bg
+ if self.highlighted:
+ fg = self.highlight_color or fg
+ bg = self.highlight_bg_color or bg
+
+ self.draw_with(surface, fg, bg)
+
+ is_default = False
+ def draw_with(self, surface, fg, bg=None):
+ if bg:
+ r = surface.get_rect()
+ b = self.border_width
+ if b:
+ e = -2 * b
+ r.inflate_ip(e, e)
+ surface.fill(bg, r)
+ m = self.margin
+ align = self.align
+ width = surface.get_width()
+ y = m
+ lines = self.text.split("\n")
+ font = self.font
+ dy = font.get_linesize()
+ for line in lines:
+ if len(line):
+ size = font.size(line)
+ if size[0] == 0: continue
+
+ image = font.render(line, True, fg)
+ r = image.get_rect()
+ r.top = y
+ if align == 'l':
+ r.left = m
+ elif align == 'r':
+ r.right = width - m
+ else:
+ r.centerx = width // 2
+ surface.blit(image, r)
+ y += dy
+
+class GLLabel(Label):
+ pass
+class SmallLabel(Label):
+ """Small text size. See theme.py"""
+
+#---------------------------------------------------------------------------
+
+class ButtonBase(Control):
+
+ align = 'c'
+ action = None
+ default_choice_color = ThemeProperty('default_choice_color')
+ default_choice_bg_color = ThemeProperty('default_choice_bg_color')
+
+ def mouse_down(self, event):
+ if self.enabled:
+ self._highlighted = True
+
+ def mouse_drag(self, event):
+ state = event in self
+ if state <> self._highlighted:
+ self._highlighted = state
+ self.invalidate()
+
+ def mouse_up(self, event):
+ if event in self:
+ if self is event.clicked_widget or (event.clicked_widget and self in event.clicked_widget.all_parents()):
+ self._highlighted = False
+ if self.enabled:
+ self.call_handler('action')
+
+#---------------------------------------------------------------------------
+
+class Button(ButtonBase, Label):
+
+ def __init__(self, text, action=None, enable=None, **kwds):
+ if action:
+ kwds['action'] = action
+ if enable:
+ kwds['enable'] = enable
+ Label.__init__(self, text, **kwds)
+
+#---------------------------------------------------------------------------
+
+class Image(Widget):
+ # image Image to display
+
+ highlight_color = ThemeProperty('highlight_color')
+
+ image = overridable_property('image')
+
+ highlighted = False
+
+ def __init__(self, image=None, rect=None, **kwds):
+ Widget.__init__(self, rect, **kwds)
+ if image:
+ if isinstance(image, basestring):
+ image = resource.get_image(image)
+ w, h = image.get_size()
+ d = 2 * self.margin
+ self.size = w + d, h + d
+ self._image = image
+
+ def get_image(self):
+ return self._image
+
+ def set_image(self, x):
+ self._image = x
+
+ def draw(self, surf):
+ frame = surf.get_rect()
+ if self.highlighted:
+ surf.fill(self.highlight_color)
+ image = self.image
+ r = image.get_rect()
+ r.center = frame.center
+ surf.blit(image, r)
+
+# def draw(self, surf):
+# frame = self.get_margin_rect()
+# surf.blit(self.image, frame)
+
+#---------------------------------------------------------------------------
+
+class ImageButton(ButtonBase, Image):
+ pass
+
+#---------------------------------------------------------------------------
+
+class ValueDisplay(Control, Label):
+
+ format = "%s"
+ align = 'l'
+
+ def __init__(self, width=100, **kwds):
+ Label.__init__(self, "", **kwds)
+ self.set_size_for_text(width)
+
+# def draw(self, surf):
+# value = self.value
+# text = self.format_value(value)
+# buf = self.font.render(text, True, self.fg_color)
+# frame = surf.get_rect()
+# blit_in_rect(surf, buf, frame, self.align, self.margin)
+
+ def get_text(self):
+ return self.format_value(self.value)
+
+ def format_value(self, value):
+ if value is not None:
+ return self.format % value
+ else:
+ return ""
+
+class SmallValueDisplay(ValueDisplay): pass
+
+class ValueButton(ButtonBase, ValueDisplay):
+
+ align = 'c'
+ def get_text(self):
+ return self.format_value(self.value)
+
+#---------------------------------------------------------------------------
+
+class CheckControl(Control):
+
+ def mouse_down(self, e):
+ self.value = not self.value
+
+ def get_highlighted(self):
+ return self.value
+
+#---------------------------------------------------------------------------
+
+class CheckWidget(Widget):
+
+ default_size = (16, 16)
+ margin = 4
+ border_width = 1
+ check_mark_tweak = 2
+
+ smooth = ThemeProperty('smooth')
+
+ def __init__(self, **kwds):
+ Widget.__init__(self, Rect((0, 0), self.default_size), **kwds)
+
+ def draw(self, surf):
+ if self.highlighted:
+ r = self.get_margin_rect()
+ fg = self.fg_color
+ d = self.check_mark_tweak
+ p1 = (r.left, r.centery - d)
+ p2 = (r.centerx - d, r.bottom)
+ p3 = (r.right, r.top - d)
+ if self.smooth:
+ draw.aalines(surf, fg, False, [p1, p2, p3])
+ else:
+ draw.lines(surf, fg, False, [p1, p2, p3])
+
+#---------------------------------------------------------------------------
+
+class CheckBox(CheckControl, CheckWidget):
+ pass
+
+#---------------------------------------------------------------------------
+
+class RadioControl(Control):
+
+ setting = None
+
+ def get_highlighted(self):
+ return self.value == self.setting
+
+ def mouse_down(self, e):
+ self.value = self.setting
+
+#---------------------------------------------------------------------------
+
+class RadioButton(RadioControl, CheckWidget):
+ pass
140 albow/dialogs.py
@@ -0,0 +1,140 @@
+import textwrap
+from pygame import Rect, event
+from pygame.locals import *
+from widget import Widget
+from controls import Label, Button
+from layout import Row, Column
+from fields import TextField
+
+class Modal(object):
+
+ enter_response = True
+ cancel_response = False
+
+ def ok(self):
+ self.dismiss(True)
+
+ def cancel(self):
+ self.dismiss(False)
+
+
+class Dialog(Modal, Widget):
+
+ click_outside_response = None
+
+ def __init__(self, client = None, responses = None,
+ default = 0, cancel = -1, **kwds):
+ Widget.__init__(self, **kwds)
+ if client or responses:
+ rows = []
+ w1 = 0
+ w2 = 0
+ if client:
+ rows.append(client)
+ w1 = client.width
+ if responses:
+ buttons = Row([
+ Button(text, action = lambda t=text: self.dismiss(t))
+ for text in responses])
+ rows.append(buttons)
+ w2 = buttons.width
+ if w1 < w2:
+ a = 'l'
+ else:
+ a = 'r'
+ contents = Column(rows, align = a)
+ m = self.margin
+ contents.topleft = (m, m)
+ self.add(contents)
+ self.shrink_wrap()
+ if responses and default is not None:
+ self.enter_response = responses[default]
+ if responses and cancel is not None:
+ self.cancel_response = responses[cancel]
+
+ def mouse_down(self, e):
+ if not e in self:
+ response = self.click_outside_response
+ if response is not None:
+ self.dismiss(response)
+
+class QuickDialog(Dialog):
+ """ Dialog that closes as soon as you click outside or press a key"""
+ def mouse_down(self, evt):
+ if evt not in self:
+ self.dismiss(-1)
+ if evt.button != 1:
+ event.post(evt)
+
+ def key_down(self, evt):
+ self.dismiss()
+ event.post(evt)
+
+def wrapped_label(text, wrap_width, **kwds):
+ paras = text.split("\n")
+ text = "\n".join([textwrap.fill(para, wrap_width) for para in paras])
+ return Label(text, **kwds)
+
+#def alert(mess, wrap_width = 60, **kwds):
+# box = Dialog(**kwds)
+# d = box.margin
+# lb = wrapped_label(mess, wrap_width)
+# lb.topleft = (d, d)
+# box.add(lb)
+# box.shrink_wrap()
+# return box.present()
+
+def alert(mess, **kwds):
+ ask(mess, ["OK"], **kwds)
+
+def ask(mess, responses = ["OK", "Cancel"], default = 0, cancel = -1,
+ wrap_width = 60, **kwds):
+ box = Dialog(**kwds)
+ d = box.margin
+ lb = wrapped_label(mess, wrap_width)
+ lb.topleft = (d, d)
+ buts = []
+ for caption in responses:
+ but = Button(caption, action = lambda x = caption: box.dismiss(x))
+ buts.append(but)
+ brow = Row(buts, spacing = d)
+ lb.width = max(lb.width, brow.width)
+ col = Column([lb, brow], spacing = d, align ='r')
+ col.topleft = (d, d)
+ if default is not None:
+ box.enter_response = responses[default]
+ buts[default].is_default = True
+ else:
+ box.enter_response = None
+ if cancel is not None:
+ box.cancel_response = responses[cancel]
+ else:
+ box.cancel_response = None
+ box.add(col)
+ box.shrink_wrap()
+ return box.present()
+
+def input_text(prompt, width, initial = None, **kwds):
+ box = Dialog(**kwds)
+ d = box.margin
+ def ok():
+ box.dismiss(True)
+ def cancel():
+ box.dismiss(False)
+ lb = Label(prompt)
+ lb.topleft = (d, d)
+ tf = TextField(width)
+ if initial:
+ tf.set_text(initial)
+ tf.enter_action = ok
+ tf.escape_action = cancel
+ tf.top = lb.top
+ tf.left = lb.right + 5
+ box.add(lb)
+ box.add(tf)
+ tf.focus()
+ box.shrink_wrap()
+ if box.present():
+ return tf.get_text()
+ else:
+ return None
450 albow/fields.py
@@ -0,0 +1,450 @@
+#
+# Albow - Fields
+#
+
+from pygame import draw
+import pygame
+from pygame.locals import K_LEFT, K_RIGHT, K_TAB, K_c, K_v, SCRAP_TEXT
+from widget import Widget, overridable_property
+from controls import Control
+
+#---------------------------------------------------------------------------
+
+class TextEditor(Widget):
+
+ upper = False
+ tab_stop = True
+
+ _text = u""
+
+ def __init__(self, width, upper = None, **kwds):
+ Widget.__init__(self, **kwds)
+ self.set_size_for_text(width)
+ if upper is not None:
+ self.upper = upper
+ self.insertion_point = None
+
+ def get_text(self):
+ return self._text
+
+ def set_text(self, text):
+ self._text = text
+
+ text = overridable_property('text')
+
+ def draw(self, surface):
+ frame = self.get_margin_rect()
+ fg = self.fg_color
+ font = self.font
+ focused = self.has_focus()
+ text, i = self.get_text_and_insertion_point()
+ if focused and i is None:
+ surface.fill(self.sel_color, frame)
+ image = font.render(text, True, fg)
+ surface.blit(image, frame)
+ if focused and i is not None:
+ x, h = font.size(text[:i])
+ x += frame.left
+ y = frame.top
+ draw.line(surface, fg, (x, y), (x, y + h - 1))
+
+ def key_down(self, event):
+ if not (event.cmd or event.alt):
+ k = event.key
+ if k == K_LEFT:
+ self.move_insertion_point(-1)
+ return
+ if k == K_RIGHT:
+ self.move_insertion_point(1)
+ return
+ if k == K_TAB:
+ self.attention_lost()
+ self.tab_to_next()
+ return
+ try:
+ c = event.unicode
+ except ValueError:
+ c = ""
+ if self.insert_char(c) <> 'pass':
+ return
+ if event.cmd and event.unicode:
+ if event.key == K_c:
+ try:
+ pygame.scrap.put(SCRAP_TEXT, self.text)
+ except:
+ print "scrap not available"
+
+ elif event.key == K_v:
+ try:
+ t = pygame.scrap.get(SCRAP_TEXT).replace('\0', '')
+ self.text = t
+ except:
+ print "scrap not available"
+ #print repr(t)
+ else:
+ self.attention_lost()
+
+ self.call_parent_handler('key_down', event)
+
+ def get_text_and_insertion_point(self):
+ text = self.get_text()
+ i = self.insertion_point
+ if i is not None:
+ i = max(0, min(i, len(text)))
+ return text, i
+
+ def move_insertion_point(self, d):
+ text, i = self.get_text_and_insertion_point()
+ if i is None:
+ if d > 0:
+ i = len(text)
+ else:
+ i = 0
+ else:
+ i = max(0, min(i + d, len(text)))
+ self.insertion_point = i
+
+ def insert_char(self, c):
+ if self.upper:
+ c = c.upper()
+ if c <= "\x7f":
+ if c == "\x08" or c == "\x7f":
+ text, i = self.get_text_and_insertion_point()
+ if i is None:
+ text = ""
+ i = 0
+ else:
+ text = text[:i-1] + text[i:]
+ i -= 1
+ self.change_text(text)
+ self.insertion_point = i
+ return
+ elif c == "\r" or c == "\x03":
+ return self.call_handler('enter_action')
+ elif c == "\x1b":
+ return self.call_handler('escape_action')
+ elif c >= "\x20":
+ if self.allow_char(c):
+ text, i = self.get_text_and_insertion_point()
+ if i is None:
+ text = c
+ i = 1
+ else:
+ text = text[:i] + c + text[i:]
+ i += 1
+ self.change_text(text)
+ self.insertion_point = i
+ return
+ return 'pass'
+
+ def allow_char(self, c):
+ return True
+
+ def mouse_down(self, e):
+ self.focus()
+ if e.num_clicks == 2:
+ self.insertion_point = None
+ return
+
+ x, y = e.local
+ i = self.pos_to_index(x)
+ self.insertion_point = i
+
+ def pos_to_index(self, x):
+ text = self.get_text()
+ font = self.font
+ def width(i):
+ return font.size(text[:i])[0]
+ i1 = 0
+ i2 = len(text)
+ x1 = 0
+ x2 = width(i2)
+ while i2 - i1 > 1:
+ i3 = (i1 + i2) // 2
+ x3 = width(i3)
+ if x > x3:
+ i1, x1 = i3, x3
+ else:
+ i2, x2 = i3, x3
+ if x - x1 > (x2 - x1) // 2:
+ i = i2
+ else:
+ i = i1
+
+ return i
+
+ def change_text(self, text):
+ self.set_text(text)
+ self.call_handler('change_action')
+
+#---------------------------------------------------------------------------
+
+class Field(Control, TextEditor):
+ # type func(string) -> value
+ # editing boolean
+
+ empty = NotImplemented
+ format = u"%s"
+ min = None
+ max = None
+ enter_passes = False
+
+ def __init__(self, width = None, **kwds):
+ min = self.predict_attr(kwds, 'min')
+ max = self.predict_attr(kwds, 'max')
+ if 'format' in kwds:
+ self.format = kwds.pop('format')
+ if 'empty' in kwds:
+ self.empty = kwds.pop('empty')
+ self.editing = False
+ if width is None:
+ w1 = w2 = ""
+ if min is not None:
+ w1 = self.format_value(min)
+ if max is not None:
+ w2 = self.format_value(max)
+ if w2:
+ if len(w1) > len(w2):
+ width = w1
+ else:
+ width = w2
+ if width is None:
+ width = 100
+ TextEditor.__init__(self, width, **kwds)
+
+ def format_value(self, x):
+ if x == self.empty:
+ return ""
+ else:
+ return self.format % x
+
+ def get_text(self):
+ if self.editing:
+ return self._text
+ else:
+ return self.format_value(self.value)
+
+ def set_text(self, text):
+ self.editing = True
+ self._text = text
+ if self.should_commit_immediately(text):
+ self.commit()
+
+ def should_commit_immediately(self, text):
+ return False
+
+ def enter_action(self):
+ if self.editing:
+ self.commit()
+ elif self.enter_passes:
+ return 'pass'
+
+ def escape_action(self):
+ if self.editing:
+ self.editing = False
+ self.insertion_point = None
+ else:
+ return 'pass'
+
+ def attention_lost(self):
+ self.commit(notify=True)
+
+ def clamp_value(self, value):
+ if self.max is not None: value = min(value, self.max)
+ if self.min is not None: value = max(value, self.min)
+ return value
+
+ def commit(self, notify = False):
+ if self.editing:
+ text = self._text
+ if text:
+ try:
+ value = self.type(text)
+ except ValueError:
+ return
+ value = self.clamp_value(value)
+ else:
+ value = self.empty
+ if value is NotImplemented:
+ return
+ self.value = value
+ self.insertion_point = None
+ if notify:
+ self.change_text(unicode(value))
+ else:
+ self._text = unicode(value)
+ self.editing = False
+
+ else:
+ self.insertion_point = None
+
+# def get_value(self):
+# self.commit()
+# return Control.get_value(self)
+#
+# def set_value(self, x):
+# Control.set_value(self, x)
+# self.editing = False
+
+#---------------------------------------------------------------------------
+
+class TextField(Field):
+ type = unicode
+ _value = u""
+
+class IntField(Field):
+ tooltipText = "Point here and use mousewheel to adjust"
+ def type(self, i):
+ try:
+ return eval(i)
+ except:
+ try:
+ return int(i)
+ except:
+ return 0
+
+ _shift_increment = 16
+ _increment = 1
+ @property
+ def increment(self):
+ if key.get_mods() & KMOD_SHIFT:
+ return self._shift_increment
+ else:
+ return self._increment
+ return self._increment
+
+ @increment.setter
+ def increment(self, val):
+ self._increment = val
+
+ def decrease_value(self):
+ self.value = self.clamp_value(self.value - self.increment)
+
+ def increase_value(self):
+ self.value = self.clamp_value(self.value + self.increment)
+
+ def mouse_down(self, evt):
+ if evt.button == 5:
+ self.decrease_value()
+
+ self.change_text(str(self.value))
+
+ elif evt.button == 4:
+ self.increase_value()
+ self.change_text(str(self.value))
+
+ else: Field.mouse_down(self, evt)
+
+ allowed_chars = '-+*/<>()0123456789'
+
+ def allow_char(self, c):
+ return c in self.allowed_chars
+
+ def should_commit_immediately(self, text):
+ try:
+ return str(eval(text)) == text
+ except:
+ return False
+
+
+class TimeField(Field):
+ allowed_chars = ':0123456789 APMapm'
+
+ def format_value(self, hm):
+ format = "%d:%02d"
+ h,m = hm
+ if h >= 12:
+ h -= 12
+ return format % (h or 12,m) + " PM"
+ else:
+ return format % (h or 12,m) + " AM"
+
+ def allow_char(self, c):
+ return c in self.allowed_chars
+
+ def type(self, i):
+ h,m = 0,0
+ i = i.upper()
+
+ pm = "PM" in i
+ for a in "APM":
+ i = i.replace(a, "")
+
+ parts = i.split(":")
+
+ if len(parts):
+ h = int(parts[0])
+ if len(parts)>1:
+ m = int(parts[1])
+
+ if pm and h<12: h += 12
+ h %= 24
+ m %= 60
+ return h,m
+
+ def mouse_down(self, evt):
+ if evt.button == 5:
+ delta = -1
+ elif evt.button == 4:
+ delta = 1
+ else:
+ return Field.mouse_down(self, evt)
+
+ (h,m) = self.value
+ pos = self.pos_to_index(evt.local[0])
+ if pos < 2:
+ h += delta
+ elif pos < 5:
+ m += delta
+ else:
+ h = (h + 12) % 24
+
+ self.value = (h,m)
+
+ def set_value(self, v):
+ h,m = v
+ super(TimeField, self).set_value((h%24, m%60))
+
+from pygame import key
+from pygame.locals import KMOD_SHIFT
+
+class FloatField(Field):
+ type = float
+ _increment = 1.0
+ _shift_increment = 16.0
+ tooltipText = "Point here and use mousewheel to adjust"
+
+ allowed_chars = '-+.0123456789f'
+
+ def allow_char(self, c):
+ return c in self.allowed_chars
+
+ @property
+ def increment(self):
+ if key.get_mods() & KMOD_SHIFT:
+ return self._shift_increment
+ return self._increment
+
+ @increment.setter
+ def increment(self, val):
+ self._increment = self.clamp_value(val)
+
+ def decrease_value(self):
+ self.value = self.clamp_value(self.value - self.increment)
+
+ def increase_value(self):
+ self.value = self.clamp_value(self.value + self.increment)
+
+ def mouse_down(self, evt):
+ if evt.button == 5:
+ self.decrease_value()
+
+ self.change_text(str(self.value))
+
+ elif evt.button == 4:
+ self.increase_value()
+ self.change_text(str(self.value))
+
+ else: Field.mouse_down(self, evt)
+
+#---------------------------------------------------------------------------
330 albow/file_dialogs.py
@@ -0,0 +1,330 @@
+# -*- coding: utf-8 -*-
+#
+# Albow - File Dialogs
+#
+
+import os
+from pygame import draw, Rect
+from pygame.locals import *
+from albow.widget import Widget
+from albow.dialogs import Dialog, ask, alert
+from albow.controls import Label, Button
+from albow.fields import TextField
+from albow.layout import Row, Column
+from albow.palette_view import PaletteView
+from albow.theme import ThemeProperty
+
+class DirPathView(Widget):
+
+ def __init__(self, width, client, **kwds):
+ Widget.__init__(self, **kwds)
+ self.set_size_for_text(width)
+ self.client = client
+
+ def draw(self, surf):
+ frame = self.get_margin_rect()
+ image = self.font.render(self.client.directory, True, self.fg_color)
+ tw = image.get_width()
+ mw = frame.width
+ if tw <= mw:
+ x = 0
+ else:
+ x = mw - tw
+ surf.blit(image, (frame.left + x, frame.top))
+
+
+class FileListView(PaletteView):
+
+ #scroll_button_color = (255, 255, 0)
+
+ def __init__(self, width, client, **kwds):
+ font = self.predict_font(kwds)
+ h = font.get_linesize()
+ d = 2 * self.predict(kwds, 'margin')
+ PaletteView.__init__(self, (width - d, h), 10, 1, scrolling = True, **kwds)
+ self.client = client
+ self.selection = None
+ self.names = []
+
+ def update(self):
+ client = self.client
+ dir = client.directory
+ suffixes = client.suffixes
+ def filter(name):
+ path = os.path.join(dir, name)
+ return os.path.isdir(path) or self.client.filter(path)
+ try:
+ names = [name for name in os.listdir(dir) if filter(name)]
+ #if not name.startswith(".") and filter(name)]
+ except EnvironmentError, e:
+ alert(u"%s: %s" % (dir, e))
+ names = []
+ self.names = sorted(names)
+ self.selection = None
+
+ def num_items(self):
+ return len(self.names)
+
+ #def draw_prehighlight(self, surf, item_no, rect):
+ # draw.rect(surf, self.sel_color, rect)
+
+ def draw_item(self, surf, item_no, rect):
+ font = self.font
+ color = self.fg_color
+ buf = self.font.render(self.names[item_no], True, color)
+ surf.blit(buf, rect)
+
+ def click_item(self, item_no, e):
+ self.selection = item_no
+ self.client.dir_box_click(e.num_clicks == 2)
+
+ def item_is_selected(self, item_no):
+ return item_no == self.selection
+
+ def get_selected_name(self):
+ sel = self.selection
+ if sel is not None:
+ return self.names[sel]
+ else:
+ return ""
+
+
+class FileDialog(Dialog):
+
+ box_width = 250
+ default_prompt = None
+ up_button_text = ThemeProperty("up_button_text")
+
+ def __init__(self, prompt = None, suffixes = None, **kwds):
+ Dialog.__init__(self, **kwds)
+ label = None
+ d = self.margin
+ self.suffixes = suffixes or ("",)
+ up_button = Button(self.up_button_text, action = self.go_up)
+ dir_box = DirPathView(self.box_width - up_button.width - 10, self)
+ self.dir_box = dir_box
+ top_row = Row([dir_box, up_button])
+ list_box = FileListView(self.box_width - 16, self)
+ self.list_box = list_box
+ ctrls = [top_row, list_box]
+ prompt = prompt or self.default_prompt
+ if prompt:
+ label = Label(prompt)
+ if self.saving:
+ filename_box = TextField(self.box_width)
+ filename_box.change_action = self.update
+ filename_box._enter_action = filename_box.enter_action
+ filename_box.enter_action = self.enter_action
+ self.filename_box = filename_box
+ ctrls.append(Column([label, filename_box], align = 'l', spacing = 0))
+ else:
+ if label:
+ ctrls.insert(0, label)
+ ok_button = Button(self.ok_label, action = self.ok, enable = self.ok_enable)
+ self.ok_button = ok_button
+ cancel_button = Button("Cancel", action = self.cancel)
+ vbox = Column(ctrls, align = 'l', spacing = d)
+ vbox.topleft = (d, d)
+ y = vbox.bottom + d
+ ok_button.topleft = (vbox.left, y)
+ cancel_button.topright = (vbox.right, y)
+ self.add(vbox)
+ self.add(ok_button)
+ self.add(cancel_button)
+ self.shrink_wrap()
+ self._directory = None
+ self.directory = os.getcwdu()
+ #print "FileDialog: cwd =", repr(self.directory) ###
+ if self.saving:
+ filename_box.focus()
+
+ def get_directory(self):
+ return self._directory
+
+ def set_directory(self, x):
+ x = os.path.abspath(x)
+ while not os.path.exists(x):
+ y = os.path.dirname(x)
+ if y == x:
+ x = os.getcwdu()
+ break
+ x = y
+ if self._directory <> x:
+ self._directory = x
+ self.list_box.update()
+ self.update()
+
+ directory = property(get_directory, set_directory)
+
+ def filter(self, path):
+ suffixes = self.suffixes
+ if not suffixes or os.path.isdir(path):
+ #return os.path.isfile(path)
+ return True
+ for suffix in suffixes:
+ if path.endswith(suffix.lower()):
+ return True
+
+ def update(self):
+ pass
+
+ def go_up(self):
+ self.directory = os.path.dirname(self.directory)
+ self.list_box.scroll_to_item(0)
+
+ def dir_box_click(self, double):
+ if double:
+ name = self.list_box.get_selected_name()
+ path = os.path.join(self.directory, name)
+ suffix = os.path.splitext(name)[1]
+ if suffix not in self.suffixes and os.path.isdir(path):
+ self.directory = path
+ else:
+ self.double_click_file(name)
+ self.update()
+
+ def enter_action(self):
+ self.filename_box._enter_action()
+ self.ok()
+
+ def ok(self):
+ self.dir_box_click(True)
+ #self.dismiss(True)
+
+ def cancel(self):
+ self.dismiss(False)
+
+ def key_down(self, evt):
+ k = evt.key
+ if k == K_RETURN or k == K_KP_ENTER:
+ self.dir_box_click(True)
+ if k == K_ESCAPE:
+ self.cancel()
+
+
+class FileSaveDialog(FileDialog):
+
+ saving = True
+ default_prompt = "Save as:"
+ ok_label = "Save"
+
+ def get_filename(self):
+ return self.filename_box.value
+
+ def set_filename(self, x):
+ dsuf = self.suffixes[0]
+ if x.endswith(dsuf):
+ x = x[:-len(dsuf)]
+ self.filename_box.value = x
+
+ filename = property(get_filename, set_filename)
+
+ def get_pathname(self):
+ path = os.path.join(self.directory, self.filename_box.value)
+ suffixes = self.suffixes
+ if suffixes and not path.endswith(suffixes[0]):
+ path = path + suffixes[0]
+ return path
+
+ pathname = property(get_pathname)
+
+ def double_click_file(self, name):
+ self.filename_box.value = name
+
+ def ok(self):
+ path = self.pathname
+ if os.path.exists(path):
+ answer = ask("Replace existing '%s'?" % os.path.basename(path))
+ if answer <> "OK":
+ return
+ #FileDialog.ok(self)
+ self.dismiss(True)
+
+ def update(self):
+ FileDialog.update(self)
+
+ def ok_enable(self):
+ return self.filename_box.text <> ""
+
+
+class FileOpenDialog(FileDialog):
+
+ saving = False
+ ok_label = "Open"
+
+ def get_pathname(self):
+ name = self.list_box.get_selected_name()
+ if name:
+ return os.path.join(self.directory, name)
+ else:
+ return None
+
+ pathname = property(get_pathname)
+
+ #def update(self):
+ # FileDialog.update(self)
+
+ def ok_enable(self):
+ path = self.pathname
+ enabled = self.item_is_choosable(path)
+ return enabled
+
+ def item_is_choosable(self, path):
+ return bool(path) and self.filter(path)
+
+ def double_click_file(self, name):
+ self.dismiss(True)
+
+
+class LookForFileDialog(FileOpenDialog):
+
+ target = None
+
+ def __init__(self, target, **kwds):
+ FileOpenDialog.__init__(self, **kwds)
+ self.target = target
+
+ def item_is_choosable(self, path):
+ return path and os.path.basename(path) == self.target
+
+ def filter(self, name):
+ return name and os.path.basename(name) == self.target
+
+
+def request_new_filename(prompt = None, suffix = None, extra_suffixes = None,
+ directory = None, filename = None, pathname = None):
+ if pathname:
+ directory, filename = os.path.split(pathname)
+ if extra_suffixes:
+ suffixes = extra_suffixes
+ else:
+ suffixes = []
+ if suffix:
+ suffixes = [suffix] + suffixes
+ dlog = FileSaveDialog(prompt = prompt, suffixes = suffixes)
+ if directory:
+ dlog.directory = directory
+ if filename:
+ dlog.filename = filename
+ if dlog.present():
+ return dlog.pathname
+ else:
+ return None
+
+def request_old_filename(suffixes = None, directory = None):
+ dlog = FileOpenDialog(suffixes = suffixes)
+ if directory:
+ dlog.directory = directory
+ if dlog.present():
+ return dlog.pathname
+ else:
+ return None
+
+def look_for_file_or_directory(target, prompt = None, directory = None):
+ dlog = LookForFileDialog(target = target, prompt = prompt)
+ if directory:
+ dlog.directory = directory
+ if dlog.present():
+ return dlog.pathname
+ else:
+ return None
51 albow/grid_view.py
@@ -0,0 +1,51 @@
+from pygame import Rect
+from widget import Widget
+
+class GridView(Widget):
+ # cell_size (width, height) size of each cell
+ #
+ # Abstract methods:
+ #
+ # num_rows() --> no. of rows
+ # num_cols() --> no. of columns
+ # draw_cell(surface, row, col, rect)
+ # click_cell(row, col, event)
+
+ def __init__(self, cell_size, nrows, ncols, **kwds):
+ """nrows, ncols are for calculating initial size of widget"""
+ Widget.__init__(self, **kwds)
+ self.cell_size = cell_size
+ w, h = cell_size
+ d = 2 * self.margin
+ self.size = (w * ncols + d, h * nrows + d)
+ self.cell_size = cell_size
+
+ def draw(self, surface):
+ for row in xrange(self.num_rows()):
+ for col in xrange(self.num_cols()):
+ r = self.cell_rect(row, col)
+ self.draw_cell(surface, row, col, r)
+
+ def cell_rect(self, row, col):
+ w, h = self.cell_size
+ d = self.margin
+ x = col * w + d
+ y = row * h + d
+ return Rect(x, y, w, h)
+
+ def draw_cell(self, surface, row, col, rect):
+ pass
+
+ def mouse_down(self, event):
+ if event.button == 1:
+ x, y = event.local
+ w, h = self.cell_size
+ W, H = self.size
+ d = self.margin
+ if d <= x < W - d and d <= y < H - d:
+ row = (y - d) // h
+ col = (x - d) // w
+ self.click_cell(row, col, event)
+
+ def click_cell(self, row, col, event):
+ pass
49 albow/image_array.py
@@ -0,0 +1,49 @@
+from pygame import Rect
+from albow.resource import get_image
+
+class ImageArray(object):
+
+ def __init__(self, image, shape):
+ self.image = image
+ self.shape = shape
+ if isinstance(shape, tuple):
+ self.nrows, self.ncols = shape
+ else:
+ self.nrows = 1
+ self.ncols = shape
+ iwidth, iheight = image.get_size()
+ self.size = iwidth // self.ncols, iheight // self.nrows
+
+ def __len__(self):
+ return self.shape
+
+ def __getitem__(self, index):
+ image = self.image
+ nrows = self.nrows
+ ncols = self.ncols
+ if nrows == 1:
+ row = 0
+ col = index
+ else:
+ row, col = index
+ #left = iwidth * col // ncols
+ #top = iheight * row // nrows
+ #width = iwidth // ncols
+ #height = iheight // nrows
+ width, height = self.size
+ left = width * col
+ top = height * row
+ return image.subsurface(left, top, width, height)
+
+ def get_rect(self):
+ return Rect((0, 0), self.size)
+
+
+image_array_cache = {}
+
+def get_image_array(name, shape, **kwds):
+ result = image_array_cache.get(name)
+ if not result:
+ result = ImageArray(get_image(name, **kwds), shape)
+ image_array_cache[name] = result
+ return result
159 albow/layout.py
@@ -0,0 +1,159 @@
+#
+# Albow - Layout widgets
+#
+
+from pygame import Rect
+from widget import Widget
+
+class RowOrColumn(Widget):
+
+ _is_gl_container = True
+
+ def __init__(self, size, items, kwds):
+ align = kwds.pop('align', 'c')
+ spacing = kwds.pop('spacing', 10)
+ expand = kwds.pop('expand', None)
+ if isinstance(expand, int):
+ expand = items[expand]
+ #if kwds:
+ # raise TypeError("Unexpected keyword arguments to Row or Column: %s"
+ # % kwds.keys())
+ Widget.__init__(self, **kwds)
+ #print "albow.controls: RowOrColumn: size =", size, "expand =", expand ###
+ d = self.d
+ longways = self.longways
+ crossways = self.crossways
+ axis = self.axis
+ k, attr2, attr3 = self.align_map[align]
+ w = 0
+ length = 0
+ if isinstance(expand, int):
+ expand = items[expand]
+ elif not expand:
+ expand = items[-1]
+ move = ''
+ for item in items:
+ r = item.rect
+ w = max(w, getattr(r, crossways))
+ if item is expand:
+ item.set_resizing(axis, 's')
+ move = 'm'
+ else:
+ item.set_resizing(axis, move)
+ length += getattr(r, longways)
+ if size is not None:
+ n = len(items)
+ if n > 1:
+ length += spacing * (n - 1)
+ #print "albow.controls: expanding size from", length, "to", size ###
+ setattr(expand.rect, longways, max(1, size - length))
+ h = w * k // 2
+ m = self.margin
+ px = h * d[1] + m
+ py = h * d[0] + m
+ sx = spacing * d[0]
+ sy = spacing * d[1]
+ for item in items:
+ setattr(item.rect, attr2, (px, py))
+ self.add(item)
+ p = getattr(item.rect, attr3)
+ px = p[0] + sx
+ py = p[1] + sy
+ self.shrink_wrap()
+
+#---------------------------------------------------------------------------
+
+class Row(RowOrColumn):
+
+ d = (1, 0)
+ axis = 'h'
+ longways = 'width'
+ crossways = 'height'
+ align_map = {
+ 't': (0, 'topleft', 'topright'),
+ 'c': (1, 'midleft', 'midright'),
+ 'b': (2, 'bottomleft', 'bottomright'),
+ }
+
+ def __init__(self, items, width = None, **kwds):
+ """
+ Row(items, align = alignment, spacing = 10, width = None, expand = None)
+ align = 't', 'c' or 'b'
+ """
+ RowOrColumn.__init__(self, width, items, kwds)
+
+#---------------------------------------------------------------------------
+
+class Column(RowOrColumn):
+
+ d = (0, 1)
+ axis = 'v'
+ longways = 'height'
+ crossways = 'width'
+ align_map = {
+ 'l': (0, 'topleft', 'bottomleft'),
+ 'c': (1, 'midtop', 'midbottom'),
+ 'r': (2, 'topright', 'bottomright'),
+ }
+
+ def __init__(self, items, height = None, **kwds):
+ """
+ Column(items, align = alignment, spacing = 10, height = None, expand = None)
+ align = 'l', 'c' or 'r'
+ """
+ RowOrColumn.__init__(self, height, items, kwds)
+
+#---------------------------------------------------------------------------
+
+class Grid(Widget):
+
+ _is_gl_container = True
+
+ def __init__(self, rows, row_spacing = 10, column_spacing = 10, **kwds):
+ col_widths = [0] * len(rows[0])
+ row_heights = [0] * len(rows)
+ for j, row in enumerate(rows):
+ for i, widget in enumerate(row):
+ if widget:
+ col_widths[i] = max(col_widths[i], widget.width)
+ row_heights[j] = max(row_heights[j], widget.height)
+ row_top = 0
+ for j, row in enumerate(rows):
+ h = row_heights[j]
+ y = row_top + h // 2
+ col_left = 0
+ for i, widget in enumerate(row):
+ if widget:
+ w = col_widths[i]
+ x = col_left
+ widget.midleft = (x, y)
+ col_left += w + column_spacing
+ row_top += h + row_spacing
+ width = max(1, col_left - column_spacing)
+ height = max(1, row_top - row_spacing)
+ r = Rect(0, 0, width, height)
+ #print "albow.controls.Grid: r =", r ###
+ #print "...col_widths =", col_widths ###
+ #print "...row_heights =", row_heights ###
+ Widget.__init__(self, r, **kwds)
+ self.add(rows)
+
+#---------------------------------------------------------------------------
+
+class Frame(Widget):
+ # margin int spacing between border and widget
+
+ border_width = 1
+ margin = 2
+
+ def __init__(self, client, border_spacing = None, **kwds):
+ Widget.__init__(self, **kwds)
+ self.client = client
+ if border_spacing is not None:
+ self.margin = self.border_width + border_spacing
+ d = self.margin
+ w, h = client.size
+ self.size = (w + 2 * d, h + 2 * d)
+ client.topleft = (d, d)
+ self.add(client)
+
180 albow/menu.py
@@ -0,0 +1,180 @@
+#---------------------------------------------------------------------------
+#
+# Albow - Pull-down or pop-up menu
+#
+#---------------------------------------------------------------------------
+
+import sys
+from root import get_root, get_focus
+from dialogs import Dialog
+from theme import ThemeProperty
+
+#---------------------------------------------------------------------------
+
+class MenuItem(object):
+
+ keyname = ""
+ keycode = None
+ shift = False
+ alt = False
+ enabled = False
+
+ if sys.platform.startswith('darwin') or sys.platform.startswith('mac'):
+ cmd_name = "Cmd "
+ option_name = "Opt "
+ else:
+ cmd_name = "Ctrl "
+ option_name = "Alt "
+
+ def __init__(self, text = "", command = None):
+ self.command = command
+ if "/" in text:
+ text, key = text.split("/", 1)
+ else:
+ key = ""
+ self.text = text
+ if key:
+ keyname = key[-1]
+ mods = key[:-1]
+ self.keycode = ord(keyname.lower())
+ if "^" in mods:
+ self.shift = True
+ keyname = "Shift " + keyname
+ if "@" in mods:
+ self.alt = True
+ keyname = self.option_name + keyname
+ self.keyname = self.cmd_name + keyname
+
+#---------------------------------------------------------------------------
+
+class Menu(Dialog):
+
+ disabled_color = ThemeProperty('disabled_color')
+ click_outside_response = -1
+
+ def __init__(self, title, items, **kwds):
+ self.title = title
+ self.items = items
+ self._items = [MenuItem(*item) for item in items]
+ Dialog.__init__(self, **kwds)
+ h = self.font.get_linesize()
+ self.height = h * len(self._items) + h
+
+ def present(self, client, pos):
+ client = client or get_root()
+ self.topleft = client.local_to_global(pos)
+ focus = get_focus()
+ font = self.font
+ h = font.get_linesize()
+ items = self._items
+ margin = self.margin
+ height = h * len(items) + h
+ w1 = w2 = 0
+ for item in items:
+ item.enabled = self.command_is_enabled(item, focus)
+ w1 = max(w1, font.size(item.text)[0])
+ w2 = max(w2, font.size(item.keyname)[0])
+ width = w1 + 2 * margin
+ self._key_margin = width
+ if w2 > 0:
+ width += w2 + margin
+ self.size = (width, height)
+ self._hilited = None
+
+ root = get_root()
+ self.rect.clamp_ip(root.rect)
+
+ return Dialog.present(self, centered = False)
+
+ def command_is_enabled(self, item, focus):
+ cmd = item.command
+ if cmd:
+ enabler_name = cmd + '_enabled'
+ handler = focus
+ while handler:
+ enabler = getattr(handler, enabler_name, None)
+ if enabler:
+ return enabler()
+ handler = handler.next_handler()
+ return True
+
+ def draw(self, surf):
+ font = self.font
+ h = font.get_linesize()
+ sep = surf.get_rect()
+ sep.height = 1
+ colors = [self.disabled_color, self.fg_color]
+ bg = self.bg_color
+ xt = self.margin
+ xk = self._key_margin
+ y = h // 2
+ hilited = self._hilited
+ for item in self._items:
+ text = item.text
+ if not text:
+ sep.top = y + h // 2
+ surf.fill(colors[0], sep)
+ else:
+ if item is hilited:
+ rect = surf.get_rect()
+ rect.top = y
+ rect.height = h
+ surf.fill(colors[1], rect)
+ color = bg
+ else:
+ color = colors[item.enabled]
+ buf = font.render(item.text, True, color)
+ surf.blit(buf, (xt, y))
+ keyname = item.keyname
+ if keyname:
+ buf = font.render(keyname, True, color)
+ surf.blit(buf, (xk, y))
+ y += h
+
+ def mouse_move(self, e):
+ self.mouse_drag(e)
+
+ def mouse_drag(self, e):
+ item = self.find_enabled_item(e)
+ if item is not self._hilited:
+ self._hilited = item
+ self.invalidate()
+
+ def mouse_up(self, e):
+ item = self.find_enabled_item(e)
+ if item:
+ self.dismiss(self._items.index(item))
+
+ def find_enabled_item(self, e):
+ x, y = e.local
+ if 0 <= x < self.width:
+ h = self.font.get_linesize()
+ i = (y - h // 2) // h
+ items = self._items
+ if 0 <= i < len(items):
+ item = items[i]
+ if item.enabled:
+ return item
+
+ def find_item_for_key(self, e):
+ for item in self._items:
+ if item.keycode == e.key \
+ and item.shift == e.shift and item.alt == e.alt:
+ focus = get_focus()
+ if self.command_is_enabled(item, focus):
+ return self._items.index(item)
+ else:
+ return -1
+ return -1
+
+ def get_command(self, i):
+ if i >= 0:
+ item = self._items[i]
+ cmd = item.command
+ if cmd:
+ return cmd + '_cmd'
+
+ def invoke_item(self, i):
+ cmd = self.get_command(i)
+ if cmd:
+ get_focus().handle_command(cmd)
66 albow/menu_bar.py
@@ -0,0 +1,66 @@
+#
+# Albow - Menu bar
+#
+
+from pygame import Rect
+from widget import Widget, overridable_property
+
+class MenuBar(Widget):
+
+ menus = overridable_property('menus', "List of Menu instances")
+
+ def __init__(self, menus = None, width = 0, **kwds):
+ font = self.predict_font(kwds)
+ height = font.get_linesize()
+ Widget.__init__(self, Rect(0, 0, width, height), **kwds)
+ self.menus = menus or []
+ self._hilited_menu = None
+
+ def get_menus(self):
+ return self._menus
+
+ def set_menus(self, x):
+ self._menus = x
+
+ def draw(self, surf):
+ fg = self.fg_color
+ bg = self.bg_color
+ font = self.font
+ hilited = self._hilited_menu
+ x = 0
+ for menu in self._menus:
+ text = " %s " % menu.title
+ if menu is hilited:
+ buf = font.render(text, True, bg, fg)
+ else:
+ buf = font.render(text, True, fg, bg)
+ surf.blit(buf, (x, 0))
+ x += surf.get_width()
+
+ def mouse_down(self, e):
+ mx = e.local[0]
+ font = self.font
+ x = 0
+ for menu in self._menus:
+ text = " %s " % menu.title
+ w = font.size(text)[0]
+ if x <= mx < x + w:
+ self.show_menu(menu, x)
+
+ def show_menu(self, menu, x):
+ self._hilited_menu = menu
+ try:
+ i = menu.present(self, (x, self.height))
+ finally:
+ self._hilited_menu = None
+ menu.invoke_item(i)
+
+ def handle_command_key(self, e):
+ menus = self.menus
+ for m in xrange(len(menus)-1, -1, -1):
+ menu = menus[m]
+ i = menu.find_item_for_key(e)
+ if i >= 0:
+ menu.invoke_item(i)
+ return True
+ return False
206 albow/music.py
@@ -0,0 +1,206 @@
+#---------------------------------------------------------------------------
+#
+# Albow - Music
+#
+#---------------------------------------------------------------------------
+
+from __future__ import division
+import os
+from random import randrange
+
+try:
+ from pygame.mixer import music
+except ImportError:
+ music = None
+ print "Music not available"
+
+if music:
+ import root
+ music.set_endevent(root.MUSIC_END_EVENT)
+
+from resource import resource_path
+from root import schedule
+
+#---------------------------------------------------------------------------
+
+fadeout_time = 1 # Time over which to fade out music (sec)
+change_delay = 2 # Delay between end of one item and starting the next (sec)
+
+#---------------------------------------------------------------------------
+
+music_enabled = True
+current_music = None
+current_playlist = None
+next_change_delay = 0
+
+#---------------------------------------------------------------------------
+
+class PlayList(object):
+ """A collection of music filenames to be played sequentially or
+ randomly. If random is true, items will be played in a random order.
+ If repeat is true, the list will be repeated indefinitely, otherwise
+ each item will only be played once."""
+
+ def __init__(self, items, random = False, repeat = False):
+ self.items = list(items)
+ self.random = random
+ self.repeat = repeat
+
+ def next(self):
+ """Returns the next item to be played."""
+ items = self.items
+ if items:
+ if self.random:
+ n = len(items)
+ if self.repeat:
+ n = (n + 1) // 2
+ i = randrange(n)
+ else:
+ i = 0
+ item = items.pop(i)
+ if self.repeat:
+ items.append(item)
+ return item
+
+#---------------------------------------------------------------------------
+
+def get_music(*names, **kwds):
+ """Return the full pathname of a music file from the "music" resource
+ subdirectory."""
+ prefix = kwds.pop('prefix', "music")
+ return resource_path(prefix, *names)
+
+def get_playlist(*names, **kwds):
+ prefix = kwds.pop('prefix', "music")
+ dirpath = get_music(*names, **{'prefix': prefix})
+ items = [os.path.join(dirpath, filename)
+ for filename in os.listdir(dirpath)
+ if not filename.startswith(".")]
+ items.sort()
+ return PlayList(items, **kwds)
+
+def change_playlist(new_playlist):
+ """Fade out any currently playing music and start playing from the given
+ playlist."""
+ #print "albow.music: change_playlist" ###
+ global current_music, current_playlist, next_change_delay
+ if music and new_playlist is not current_playlist:
+ current_playlist = new_playlist
+ if music_enabled:
+ music.fadeout(fadeout_time * 1000)
+ next_change_delay = max(0, change_delay - fadeout_time)
+ jog_music()
+ else:
+ current_music = None
+
+def change_music(new_music, repeat = False):
+ """Fade out any currently playing music and start playing the given
+ music file."""
+ #print "albow.music: change_music" ###
+