Sunday, December 7, 2014

Python dialog boxes and system call for launching Windows Photo Viewer

Here is the Python 2.7 code for my program that displays user prompts to create an XML file of text to accompany the files in a directory as part of my process for creating photo albums. I originally wrote this to run in a different environment that had an older version of Python and a different tool kit for user prompts, so to make this version I read a bunch of examples of how to use the Tkinter module; the links to the different examples follow the code. The system call for displaying the Photo viewer was all figured out a long time ago so I don't have the links for those examples, but when I migrated to MS-DOS python it took me forever to get the right command syntax to spawn that system call as a background task with no wait, so the links from that research are also attached.

import os
import sys
import array
import string
from Tkinter import *
import tkFileDialog
from tkFileDialog import askopenfilename
import tkSimpleDialog
from tkSimpleDialog import askstring
import tkMessageBox
from tkMessageBox import askquestion
import subprocess
import glob

default_gof_path = "C:/Users/Charles2/Documents/GOF2014"

# MS-DOS Python 2.7 compatible version
#
# Program structure:
#
# Prompt for any file in the directory to be analyzed
# Get directory name as the prefix to the .xls files to be produced
# Prompt for page title dates
# Prompt for page title text
# Open a .xls file and write the page header
# Read list of files in the directory
# For each file in list:
# Get the file name
# Pop up preview
# Pop up Y/N question for portrait
# Ask for a title
# Ask for text
# Write out the .xls info
# Close page tags and close file

def MakeGOF():
global default_gof_path

print('Starting MakeGOF')

samplefile = askopenfilename(initialdir=default_gof_path, title="Select any file in the directory to make into an album")
default_gof_path = os.path.dirname(samplefile)
gofdir = os.path.dirname(samplefile)

# Need a function to trim end of gofdir to make foldername.
foldername = gofdir.split("/")[-1]
print('MakeGOF: Source directory is %s in %s' %(foldername, gofdir))

outfilename = gofdir + "/" + foldername + ".xls"
outfile = open(outfilename, "a")

GOFnumber = askstring("GOF","For " + foldername + ", enter GOF number to use in title")
title = askstring("GOF","For " + foldername + ", enter album title")
subtitle = askstring("GOF","For " + foldername + ", enter album subtitle")
datestring = askstring("GOF","For " + foldername + ", enter album date string")
header_title = "GOFx: TITLE\n"
header_title = header_title.replace('GOFx',GOFnumber)
header_title = header_title.replace('TITLE',title)
header_subtitle = "meaning\n"
header_subtitle = header_subtitle.replace('meaning',subtitle)
header_datestring = "DATE\n"
header_datestring = header_datestring.replace('DATE',datestring)
outfile.write(header_title)
outfile.write(header_subtitle)
outfile.write(header_datestring)
outfile.close()

# Note: Need to use glob here to get only JPG files
globlist = gofdir + "/*.[J,j][P,p][G,g]"
files = glob.glob(globlist)
for f in files:
file = os.path.split(f)[1]
filenamestring = f.replace('/','\\')
systemstring= '%SystemRoot%\\System32\\rundll32.exe \"%ProgramFiles%\\Windows Photo Viewer\\PhotoViewer.dll\", ImageView_Fullscreen ' + filenamestring
subprocess.Popen(systemstring, shell=True)
is_port = askquestion("GOF","Is picture portrait orientation Y/N?")
name = askstring("GOF","Enter name for this picture")
text = askstring("GOF","Enter text for this picture")
outfile = open(outfilename, "a")
outfile.write("\n")
if (is_port == "yes"):
outfile.write("\t"+file+"\n")
else:
outfile.write("\t"+file+"\n")
outfile.write("\t"+name+"\n")
outfile.write("\t"+text+"\n")
outfile.write("<\Friend>\n")
outfile.close()
systemstring2 = 'TASKKILL /FI "WINDOWTITLE eq ' + file + '*"'
print systemstring2
os.system(systemstring2)
print('MakeGOF: Processing complete')
MakeGOF()



Links:

The first key migration to tKinter was the file selection dialog. The tookit that I had originally just had only one file selection tool that returned a file path. So in tKinter, I had to use askopenfilename(). askopenfile actually opens the file and returns the file handle, so although that's nice I didn't use that because I wanted to do a 1-to-1 migration.

http://stackoverflow.com/questions/10993089/tkinter-opening-and-reading-a-file

http://tkinter.unpythonic.net/wiki/tkFileDialog

http://stackoverflow.com/questions/20725056/get-a-files-directory-in-a-string-selected-by-askopenfilename

The next task was to migrate the text entry widget to tKinter. No problem. The first link that I tried was mostly a hilarious story of bad loop construction, but one of the later replies had the syntax for tkSimpleDialog.askstring()

http://stackoverflow.com/questions/15522336/text-input-in-tkinter

The next thing was an ask box with Yes/No buttons. No problem, tkMessageBox.askyesno(). It's a little funny though because it returns the strings "yes" or "no" rather than the more obvious True/False.

http://stackoverflow.com/questions/1052420/tkinter-message-box

Now the real challenge, forming the system call for Windows Photo Viewer in such a way that it's a background task with NOWAIT. spawn() is apparently discontinued in python 2.7, and I had a devil of a time getting subprocess to work the way I wanted. A plain old os.system() would always work but of course halted the program waiting for the viewer to be dismissed, and subprocess.call() and subprocess.Popen() kept giving me a "couldn't find executable" error. Eventually I figured it out: use subprocess.Popen(), and set shell=True so that it use can the DOS path and all of the windows environment shortcuts that I had in my command string. It really didn't help that a lot of the below examples didn't have shell=True or didn't emphasise the reason for having it set.

http://stackoverflow.com/questions/24974761/running-command-line-programs-in-background-using-python-os-or-subprocess-module

https://docs.python.org/2/library/subprocess.html

http://stackoverflow.com/questions/20069080/process-spawning-in-python

http://stackoverflow.com/questions/1196074/starting-a-background-process-in-python

This link supposed a problem with extra quotes in the command string, but that didn't turn out to be my problem. I took out the quotes in my command string, but got other errors and put them back in.
http://stackoverflow.com/questions/14655629/subprocess-call-vs-os-system-python

Likewise, this hint for the COMSPEC environment variable didn't have anything to do with my issue, but did get me thinking about the environment and trying setting the shell option:
http://stackoverflow.com/questions/20330385/cannot-find-the-file-specified-when-using-subprocess-calldir-shell-true-in

The final challenge was to get it to run. Clicking on the .py file didn't produce any results, but that's because the code was still full of bugs. I ran it from the command line in a DOS window to get the error messages until it actually ran the way it was supposed to.

I tried import at one point from the command line to try to run the file, and it totally didn't work the way I had hoped, but interestingly it created a .pyd file (which I also don't understand how to use yet, but it's probably worth remembering).
http://stackoverflow.com/questions/13621540/import-a-file-from-different-directory