- Stack windows on top of each other—Thursday, March 18th, 2010
-
I check in on the Nisus scripting forum every once in a while to see if there are any new tricks I can pick up for managing my online documents (such as my Gods & Monsters gamebooks).
On that forum, member bjast asked “How would I go about writing a macro to automatically stack all of the open windows exactly on top of each other?” Well, it turns out that Nisus macros are designed mainly for working within documents, and its window manipulation is not very extensive. But being a well-designed Mac OS X application, it also has AppleScript support. And there are standard tools for manipulating windows in AppleScript that most applications get automatically by supporting AppleScript.
[toggle code]
-
tell application "Nisus Writer Pro"
- copy windows to windowList
- copy the bounds of the first item of windowList to windowBounds
-
repeat with aLowerWindow in the rest of windowList
- if aLowerWindow is visible then set the bounds of aLowerWindow to windowBounds
- end repeat
- end tell
This will grab the “bounds” of the focus window and set all other windows in Nisus Writer Pro to have the same bounds. They’ll all go behind the focus window (which is almost always the top-most window).
This is useful for comparing similar documents. Besides Nisus Writer Pro, it also works in AppleScript Editor, AppleWorks 6, Mail, Microsoft Word X, Safari, Smultron, and TaskPaper1.
There appears to be some sort of oddity or bug in Terminal; it sometimes works in Terminal, and sometimes it positions lower windows horizontally but not vertically.
By only setting the bounds of windows that are “visible” the script ignores hidden windows, obviously, but also docked windows. It does not ignore windows in other spaces if you use Spaces; I couldn’t find a way to distinguish them. However, it will not pull them into the current space, it will just position them in their own space to be where your document is in the current space.
To use this, you need to enable the script menu in your Mac’s menu bar. When you go to AppleScript Editor to paste the script in, check your preferences to make sure that “Show Script menu in menu bar” is checked. Then, whenever you go into an application (such as Nisus) you can choose “Open Scripts Folder” from the script menu in your menu bar. Any script you place in the application’s folder will show in that menu when you use that application.
-
tell application "Nisus Writer Pro"
- All roads lead up—Tuesday, March 9th, 2010
-
When I was in college, I had a lot of fun studying programming and psychology. Neither topic helped me pass my Physics courses. When I realized that Physics was not my future, I chose not to switch to Computer Science because I thought the future of programming was going to be that of mechanics running through the same rote procedures for every car that came in. That it would be reduced from a creative endeavor to gluing things together, though I wouldn’t have worded it that way then.
I was wrong. I was wrong because I didn’t foresee the Internet and web sites. And I ended up working in computer programming anyway, due to a combination of skill and just being in the right place at the right time. A lot of what Mike Taylor is talking about in Whatever happened to programming? is him being in the wrong place. If he were to take a pay cut and work for a small organization, he’d be able to, and probably be required to, do his own programming again, using Python, PHP, or Ruby possibly in combination with HTML and CSS.
One of my first tweets was “The main benefit of JavaScript toolkits is they condense to fifty or so kilobytes what would otherwise take several hundred bytes to do.” It’s true, as Mike writes, that much of what passes for programming today is trying to find the right parameters for some black box toolkit. But it’s also true that what some people seem to want to do using, say, JQuery, are things that don’t need more than a few lines of code if you know how to program. They don’t need to include a 50k file on every page view just to flip between a series of divs.
But I also stand by what I said in Learning to program without BASIC: our expectations were lower then. Programming isn’t fun unless we’re programming something useful, even if it’s only useful for a moment. This means that some things we had fun programming then aren’t going to be fun now, because computers—and frameworks—already do that for us. But, so far at least, there’s always something else to program. There’s always something else to optimize and make more beautiful.
To paraphrase Stanislaw Lem, we’ve climbed to reach the summit, and we’ve discovered that some roads still lead up.
- A present for Palm—Wednesday, January 20th, 2010
-
I was browsing the web, minding my own business, when a twelve-point Macalope strode past. Being from flyover country, my first thought was “dinner on the table”. But since this is the holiday season and it’s possible he’s a distant relation to Rudolph, I offered him a beer and we started talking.
Well, not so much talking. When a creature has a computer screen for a head, their side of the conversation is all blog. It’s not like the Macalope’s got high-res graphics in that MacPlus.
Apparently Computerworld is taking Apple to task because Palm’s software doesn’t work. Some idiocy is so bad it requires fictional creatures to respond; the Macalope, in a springtime editorial, does the job.
- Palm is hardly a “little guy”.
- It’s hardly Apple’s job to do Palm’s work for them.
But the mythical man/Mac/beast forgot point 3: Apple has already done Palm’s work for them. The Macalope writes:
Just move the files. All you’re really complaining about is losing the playlists. Even in the realm of first-world problems, that rates pretty low.
In fact, even that isn’t a problem no matter what world you’re in (or on). Apple has made all of this data easily available in a standard, easy-to-read (for computers) format. Every iTunes user’s Music folder contains an “iTunes Music Library.xml” file. As you might guess from the extension, this file is simple XML. It continuously updates with not just your music, but also all of your playlists: both the static ones and the dynamic ones.
Why do they do this? So that ungrateful third-parties like Palm can use it to make life easier for iTunes-using music lovers.
So if you want to blame someone for Palm not syncing your iTunes music, blame Palm; Apple’s already done the work, clocked out, and gone home to the little tablet and six tiny iPods.
But why talk about blame? It’s a new year and a new era of bipartisan peace, love, and understanding. In the spirit of the post-holiday season, here’s a present for Palm.
- Nisus HTML script now handles tables—Monday, January 18th, 2010
-
I finally got around to copying the table code from my old publish script to the new one. Note that it currently is written for me, and I never have tables with more than one line in them. So, I transfer paragraph-level styles from the paragraph to the table cell. You may find this less than useful.
- Reusing Django’s filter_horizontal—Thursday, January 7th, 2010
-
Django’s admin site documentation describes ModelAdmin’s filter_horizontal option as a “nifty unobtrusive JavaScript” to use in place of “the usability-challenged <select multiple>”. HTML’s multiple select does indeed suck for any number of options that require scrolling. Inevitably, when editing an existing entry, you or your users will eventually erase an existing option without knowing it.
Django’s horizontal and vertical filter solutions change these select boxes into one box of unselected options and one box of selected options, making the selected options much more obvious, and making it pretty much impossible to accidentally remove an existing selection.
You can use this JavaScript in your own forms. It consists of several JavaScript files, one CSS file, and a snippet of HTML right next to the pop-up button.
JavaScript and CSS
Assuming that you’ve made use of Django’s popup add form, you already have RelatedObjectLookups.js on your template somewhere. Add several more JavaScript files as well as one CSS file from Django’s built-in library:
- <script type="text/javascript" src="/media/js/admin/RelatedObjectLookups.js"></script>
- <script type="text/javascript" src="/admin/jsi18n/"></script>
- <script type="text/javascript" src="/media/js/core.js"></script>
- <script type="text/javascript" src="/media/js/SelectBox.js"></script>
- <script type="text/javascript" src="/media/js/SelectFilter2.js"></script>
- <link rel="stylesheet" type="text/css" href="/media/css/widgets.css" />
Call the JavaScript
If you’re using the admin-form pop-ups as I described earlier in Replicating Django’s admin form pop-ups, you have a template snippet called “form/popupplus.html”. This template is called by both SelectWithPop and MultipleSelectWithPop. Only MultipleSelectWithPop needs filter_horizontal, so add a flag to that class’s render method’s context:
[toggle code]
-
class MultipleSelectWithPop(forms.SelectMultiple):
-
def render(self, name, *args, **kwargs):
- html = super(MultipleSelectWithPop, self).render(name, *args, **kwargs)
- popupplus = render_to_string("form/popupplus.html", {'field': name, 'multiple': True})
- return html+popupplus
-
def render(self, name, *args, **kwargs):
And then, inside of popupplus.html, call the SelectFilter JavaScript:
- Write an Inkscape extension: create multiple duplicates—Sunday, January 3rd, 2010
-
While making maps for Gods & Monsters last night, I needed to duplicate the same item thirty times at exact pixel intervals apart. As far as I can tell, there is no way to do this directly in Inkscape; the recommended way is to make tiled clones, and then disconnect the clones from each other. Even that doesn’t tile in terms of pixels, however, but in relation to the bounding box. Normally what I do when I need actual copies spaced specifically is make one copy, move it where I want it; duplicate both of those copies, move the two new ones where I want them; duplicate all four, etc., and then when I’m done remove the extras. That’s a little tedious, though, and, because I’m easily distracted, prone to error.1 I decided to look into making an Inkscape extension to do what I wanted.
Inkscape extensions consist of two parts: a configuration file that defines how and where the extension appears in the GUI, and a script file that does whatever the extension is supposed to do.
Configuration
The configuration file is a simple XML file. It needs to end in .inx and it needs to go in your personal extensions directory. The example says “~/.inkscape/extensions” but it looks like this has changed, in Inkscape .47, to “~/.config/inkscape/extensions/”.
For example, the following .inx file, saved as “duplicates.inx”, will create a new submenu in Inkscape’s Extensions menu called “Mine”.
[toggle code]
-
<inkscape-extension>
- <_name>Multiple Copies</_name>
- <id>com.hoboes.filter.duplicates</id>
- <dependency type="executable" location="extensions">inkex.py</dependency>
- <dependency type="executable" location="extensions">simpletransform.py</dependency>
- <param name="number" type="int" min="1" max="1000" _gui-text="How many copies would you like?">2</param>
- <param name="horizontal" type="float" min="-10000.0" max="10000.0" _gui-text="X Offset">0.0</param>
- <param name="vertical" type="float" min="-10000.0" max="10000.0" _gui-text="Y Offset">0.0</param>
-
<effect>
- <object-type>all</object-type>
-
<effects-menu>
- <submenu _name="Mine" />
- </effects-menu>
- </effect>
-
<inkscape-extension>
- HTML 5’s expensive video—Monday, December 21st, 2009
-
I agree with Gruber’s take on autoloading making HTML5 video unusable, although I do now embed Youtube, and for the same reason he doesn’t: ClickToFlash. I assume that everyone with any intelligence has installed ClickToFlash, and anyone else isn’t my audience.
Because I’m using templates to build the Youtube links (thank you, Django), I can quickly switch to a better format if one ever works. This assumes basic intelligence on Youtube’s part—they need to maintain the video’s ID string, which, given today’s YouTu.be announcement, they probably will.
John Gruber: Why the HTML5 ‘Video’ Element Is Effectively Unusable, Even in the Browsers Which Support It at Daring Fireball (#)
- Django formsets and date/time fields—Thursday, December 17th, 2009
-
Just a quick note on something I’ve run into a couple of times now. The forms created from modelformset_factory (and probably other inline forms) will detect spurious changes if you have DateFields, DateTimeFields, or TimeFields and your default value is a Python datetime object.
[toggle code]
-
class Note(models.Model):
- title = models.CharField('Note Title', max_length=80)
- content = models.TextField('Note', max_length=2000)
- live = models.BooleanField(default=True)
- livedate = models.DateTimeField('Go Live', default=datetime.datetime.now)
- deaddate = models.DateTimeField('Go Dead', default=datetime.datetime.now)
What appears to be happening if I create an automatic formset from this model is that the form will check for changes based on whether any of the data in the submitted form differs from the data in the defaults above. But form submissions are always text, and the default for the two DateTimeFields are Python datetime objects1.
The blank line for adding new items thus always looks like it has changed—I’m guessing, because text never equals a datetime object. The is_valid method will always return invalid (unless they actually did add a new item) because it sees those two fields as having changed. They are “no longer” datetime objects. So it assumes the visitor has attempted to add a new item, but the other required items (title, content) are blank. So it generates errors of “This field is required.” for the title and content fields.
The way I fix this is to make the default be a text rendering of the date/time using strftime.
[toggle code]
- #without setting a string format, formsets will always think the value has changed on form submission
-
def defaultLive():
- now = datetime.datetime.now()
- return now.strftime('%Y-%m-%d %H:%M')
-
def defaultDead():
- then = datetime.datetime.now()+datetime.timedelta(days=3)
- return then.strftime('%Y-%m-%d 09:00')
-
class Note(models.Model):
- title = models.CharField('Note Title', max_length=80)
- content = models.TextField('Note', max_length=2000)
- live = models.BooleanField(default=True)
- livedate = models.DateTimeField('Go Live', default=defaultLive)
- deaddate = models.DateTimeField('Go Dead', default=defaultDead)
-
class Note(models.Model):
