Welcome, guest | Sign In | My Account | Store | Cart

An improved buttonbar program. Allows user to execute command by clicking button.
Designed to be executed from a file association on a Win32 platform. Replaces recipe 577038. Useful for commands requiring multiple related files.

Python, 539 lines
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
'''
;#template` {-path} {-menu} {-s1} {-s2} {-s3}
;#option`-path`Path to controlling file`F`c:\source\python\projects\menu\buttonbar.py`
;#option`-menu`Path to menu file`F`c:\source\python\projects\menu\test.ini`
;#option`-s1`First section`X`info`
;#option`-s2`Second section`X`help`
;#option`-s3`Third section`X`data`
;#end

'''
mHelpText = '''
# ----------------------------------------------
# Name: ButtonBarV1Ex
# Description:
## D20G-76 Popup button bar using Qeditor style menus.ini file, selects sections to use (ExPopen)
#
# Author: Philip S. Rist
# Date: 10/27/2010
# Copyright 2010 by St. Thomas Software
# ----------------------------------------------
# This program is freeware.  It may be used
# for any moral purpose.  You use it at your
# own risk.  St. Thomas Software makes no
# guarantees to its fitness to do anything.
#
# If you feel you must pay for it. Please
# send one million dollars to
#
#     The International Rescue Committee
#     122 East 42nd Street
#     New York, N.Y.   10168-1289
#
# Ok, we are in a recession.  So, make it a half
# million.

#
# Example of .reg file entry
#     [HKEY_CLASSES_ROOT\textfile\Shell\TextTools]
#     @="T&ext Tools
#     "EditFlags"=hex:01,00,00,00
#     [HKEY_CLASSES_ROOT\textfile\Shell\TextTools\Command]
#     @="c:\\sys\\python25\\python.exe   c:\\bin\\ButtonBarV1Ex.py   \"%1\"  {p}\\menus.ini;{o}\\menus.ini;c:\\bin\\menus.ini   ide     "
#

#
#     Syntax:
#         ButtonBarV1Ex.py  [options] <file path> <menu path> <first section> [ <another section> ... ]
#
#     Options:
#
#         -b <color>        - default button background color
#         -d <directory>    - set working directory
#         -e <name>=<value> - set environment variable
#         -h                - print help text
#         -l <color>        - default label background color
#         -o                - set orientation to horizontal
#
#     The command line template extracted from the menu file may contain macros of the form '{x}' which
#     are replaced by parts of the file path or other string.  The Do.py and Do2.py modules are used.
#     %1 is replaced by the selected file path.  The file path string is also used for display 
#     at the top of the button bar.  If no expansion is performed any string can be used and displayed.
#
#     The second parameter is the name of an initialization file similar to the one shown below.  
#     Each line contains a prompt displayed on a button and a command separated by a comma.  
#     Macros are replaced before execution.
#
#     The remaining parameters name sections to be used to make the button bar.  The order in the 
#     initialization file is maintained.  Selection is case insensitive.
#
#     Do.py and Do2.py are used to expand the command line, the menu selection and each section
#     name.  The command line can also contain '{pr}' which will be replaced by the prompt
#     displayed on the button.  Also, any string enclosed in braces not a macro will be taken
#     as a command name in the registry entry for the primary files file type.
#
#     The Menu File:
#
#     The menu file follows the syntax used by the QEditor menus.ini file.  The same syntax as an .ini 
#     file except a ',' is used to separate key from value.  In this case the values are command
#     line templates usually containing one or more macro strings.
#
#     Lines beginning with ';' are comments unless followed by '#' in which case the line may contain 
#     a special command.  Command line options are also available for the same functions
#
#     ;#cd`c:\bin               - Sets the working directory to c:\bin   or '-d c:\bin'
#     ;#set`abc=def             - Sets the environment variable abc to 'def' or '-s abc=def'
#     ;#button`#00CCCC          - Sets the background of succeeding buttons to #00CCCC. or '-b #00CCCC'
#     ;#label`light blue        - Sets the background of labels created by succeeding '-' lines
#                                 to light blue.  If included before the first section the
#                                 color will also be used for section header labels   or '-l "light blue"'
#     Within sections not included in the button bar special commands are ignored.  The '`' usually
#     appears under the '~' on the keyboard.
#
#     -o option will produce a horizontal button bar
#
#     Section names and command prompts can contan a file pattern enclosed within two '/' or two '!'
#     to include the section or line if the file path matches or does not match the pattern.  Examples
#     are shown below.
#     

# Example initialization file (Text Tools.ini)

;#set`test=A Short String
;#set`use=c:\sys\python27\pythonw.exe
;#set`gain=none
;#cd`\source\tcl
;#cd`c:\source\python\Projects\Program Execution
;#label`yellow

[Test1]
;#button`#00EEEE
;#label`#00CCCC
test 1,c:\bin\messagebox.exe  {a} {b} {p} "{f}" {n} {e} {o} %test%
-Directory List
Dir,c:\windows\system32\cmd.exe /C dir "{p}\*.{e}"
DirX,c:\windows\system32\cmd.exe /C dir *.*
Dir2,c:\windows\system32\cmd.exe /C dir c:\source\Python\*.*
;#label`light gray
-Program execution
/*.exe/Run,"{a}" I am running "{a}"
/*.py/Run,c:\bin\messagebox.exe I am running a Python program "{a}"
/*.py/Run 2,%use% "{a}"
/*.exe/Run 2,"{a}" I am running "{a}"

[/*.py/Test2]
Edit T,{o}\qeditor.exe "{a}"
Dir *,%comspec% /C dir {.}\*.*
'''

import sys
import os
import getopt
import fnmatch
import Tkinter
from Tkconstants import *
import tkMessageBox
from _winreg import *
import Do                              # Recipe: 577439
from Do2 import ExpandArg              # Recipe: 577440
from DoCommand import BuildCommand     # Recipe: 577441
import subprocess

mCmds = {}

def Expand(pCommand, pFilePath, pPrompt, pSep=','):
    '''
    Expand 
    '''

#   ---- Standard macro expansion with added {pr} macro
#        Can separate arguments with ',' fo force argument by argument 
#        expansion
    if pCommand.find(pSep) >= 0:
      try:
        lArgs = pCommand.split(pSep)     
        lCommand = ''
        lDefaultPath = pFilePath
        for lArg in lArgs:
            lVal = ExpandArg(lArg, pFilePath, lDefaultPath)
            if lVal.find('{pr}') >= 0:        # insert button prompt
                lVal = lVal.replace('{pr}', pPrompt)      
            lCommand = lCommand + lVal + " "
      except Exception, e:
        print 'BBV1Ex',e
    else:
        lCommand = Do.Expand(pCommand, pFilePath)
        if lCommand.find('{pr}') >= 0:
            lCommand = lCommand.replace('{pr}', pPrompt)

#   ---- Remove {} for compatibility with DoM.py
    lCommand = lCommand.replace('{}', ' ')

#   ---- Remaining macros will be replaced by command of same name
#        in registry
    lPos = lCommand.find("{")                      
    if lPos >= 0:                                 
        lEndPos = lCommand.find("}")
        if lEndPos > lPos:
            try:
                lKey = lCommand[lPos+1:lEndPos]
                lDotPos = lKey.rfind('.')
                if lDotPos > 0:
                    lExt = lKey[lDotPos]
                    lKey = lKey[0:lDotPos]
                else:
                    lDotPos = pFilePath.rfind('.')
                    lExt = pFilePath[lDotPos:]
                lReplace = BuildCommand(pFilePath, lKey, lExt, [])
                lCommand = lCommand[0:lPos] + lReplace + lCommand[lEndPos+1:]
            except:
                pass

#   ---- replace %1 and %*
    else:
        lPos = lCommand.rfind("%1")
        if lPos >= 0:
            lCommand = lCommand.replace("%1", pFilePath)
        lPos = lCommand.rfind("%*")
        if lPos >= 0:
            lCommand = lCommand.replace("%*", "")

#   ---- Remove !! used by DoM.py
    lPos = lCommand.rfind('!!')
    if lPos > 0:
        lCommand = lCommand[0:lPos]

    return lCommand

def showit(pEvent):
    'View expansion of selected command'
    lPrompt = pEvent.widget.cget("text")
    lCommand = mCmds[lPrompt]
    lCommand = Expand(lCommand, mFileName, lPrompt)
    
    tkMessageBox.showinfo('Expanded Command:', lPrompt + " = " + lCommand)

def submitnow(pEvent):
    'Execute selected command'
    lPrompt = pEvent.widget.cget("text")
    lCommand = mCmds[lPrompt]
    lCommand = Expand(lCommand, mFileName, lPrompt)
    lCommand = '"' + lCommand + '"'
    subprocess.Popen(lCommand, shell=True)

def Commands():
    return mCmds

def Build(pFrame, pMenuName, pFileName, pLabelList, pSubmit=submitnow, pCallBack=None,
              pDefaultLabel='yellow', pDefaultButton='light blue', pSide=TOP, pFill=X):
    '''
    Build menu from menu file
    The menu file may contain commands to set the current directory and to
    set specified environment variables.  This is done while the file is
    being read.  They are not dependent on the selected button.  The last 
    command encountered will be active.

    set environment variable name to value
        ;#set`name=value
    set current directory to path
        ;#cd`path
    set label background color
        ;#label`color
    set button background color
        ;#button`color
    '''
#
    lPos = pFileName.rfind('\\')
    if lPos >= 0:
        lFileName = pFileName[lPos+1:].lower()
    else:
        lFileName = pFileName.lower()

    lExist = os.path.exists(pFileName)
        
    lFile = open(pMenuName,'r')
    lFileText = lFile.readlines()
    lFile.close

    lDefaultButton = lButtonColor = pDefaultButton
    lDefaultLabel = lLabelColor = pDefaultLabel
    label = Tkinter.Label(pFrame, text=pFileName, bg=lLabelColor)
    #label.pack(side=TOP, fill=X)
    label.pack(side=pSide, fill=pFill)
    lKeep = False
    lSectionCount = 0
   
    for lText in lFileText:
        if len(lText) > 1:
            if lText[0] != "[":

#               ---- Menu splitter line
                if lText[0] == "-" and lKeep:
                    if len(lText) > 1:
                        lPrompt = lText[1:-1]
                        if lPrompt.find('{') >= 0:
                            lPrompt = ExpandCmd(lPrompt, pFileName)
                                
#                   ---- Display label for - line                                
#                            can be removed without affecting remainder of program
                    if len(lPrompt) > 0 and lKeep == True:
                        label= Tkinter.Label(pFrame, text=lPrompt, bg=lLabelColor)
                        #label.pack(side=TOP, fill=X)
                        label.pack(side=pSide, fill=pFill)

#               ---- process special command
                elif lText.startswith(";#") and (lSectionCount == 0 or lKeep == True): 
                    lPos = lText.find("`")          # <---- Fields are separated by ` (below ~)
                    if lPos > 0:
                        lCh = lText[2]

#                       ---- Set environment variable
                        if lCh == 's':              # set environment variable
                            lEqPos = lText.find("=")
                            if lEqPos > lPos:
                                lKey = lText[lPos+1:lEqPos].strip()
                                lValue = lText[lEqPos+1:].strip()
                                if lValue.find('{') >= 0:
                                    lValue = ExpandCmd(lValue, pFileName)
                                os.environ[lKey] = lValue

#                       ---- Change working directory
                        elif lCh == 'c':            # set working directory
                            if lPos > 0 and lPos < len(lText):
                                lText = lText[lPos+1:].strip()
                                if lText.find('{') >= 0:
                                    lText = ExpandCmd(lText, pFileName)
                                os.chdir(lText)
                                
                        elif lCh == 'b':
                            lButtonColor = lText[lPos+1:].strip().lower()
                            if lSectionCount == 0:
                                lDefaultButton = lButtonColor

                        elif lCh == 'l':
                            lLabelColor = lText[lPos+1:].strip().lower()
                            if lSectionCount == 0:
                                lDefaultLabel = lLabelColor

                        elif pCallBack != None:
                            pCallBack(lText)
                            
                        else:                       # ignore
                            pass 

#               ---- Comment                            
                elif lText[0] == ";":
                    pass

#               ---- Menu section is being skipped
                elif lKeep == False:
                    pass

#               ---- Command template
                else:
                    lPos = lText.find(",")
                    if lPos > 0:
                        lPrompt = lText[:lPos]
                        lCommand = lText[lPos+1:]

#                       ---- Filter commands based on match with file name
#                            /*3.py/Run,c:\source\python31\pyrhonw.exe ...
#                            can be removed without affecting remainder of program
                        if lPrompt[0] == '/':
                            try:
                                (lMask, lPrompt) = lPrompt[1:].split('/')
                                if not fnmatch.fnmatch(lFileName, lMask.lower()):
                                    continue
                            except Exception, e:
                                continue
                                
#                       ---- Filter commands based on mismatch with file name
#                            !*3.py!Run,c:\source\python27\pythonw.exe ...
#                            can be removed without affecting remainder of program
                        elif lPrompt[0] == '!':
                            try:
                                (lMask, lPrompt) = lPrompt[1:].split('!')
                                if fnmatch.fnmatch(lFileName, lMask.lower()):
                                    continue
                            except Exception, e:
                                continue

#                       ---- Create toolbar button                        
                        mCmds[lPrompt] = lCommand
                        try:
                            button = Tkinter.Button(pFrame, text=lPrompt, bg=lButtonColor)
                        except:
                            button = Tkinter.Button(pFrame, text=lPrompt)
                        #button.pack(side=TOP,fill=X)
                        button.pack(side=pSide, fill=pFill)
                        button.bind("<Button-1>", pSubmit) ### (1)
                        button.bind("<Button-3>", showit)

#           ---- Section Header
            else:
                lSectionCount += 1
                lLabelColor = lDefaultLabel
                lButtonColor = lDefaultButton
                if lText.find('{') >= 0:
                    lText = ExpandCmd(label, pFileName)
                label = lText[1:-2].lower()

#               ---- Filter section based on match with file name
#                    [/*3.py/Special]
#                    can be removed without affecting remainder of program
                if label[0] == '/':
                    try:
                        (lMask, label) = label[1:].split('/')
                        if not fnmatch.fnmatch(lFileName, lMask.lower()):
                            lKeep = False
                            continue
                    except Exception, e:
                        lKeep = False
                        continue
                        
#               ---- Filter section based on mismatch with file name
#                    [!*3.py!Special]
#                    can be removed without affecting remainder of program
                elif label[0] == '!':
                    try:
                        (lMask, label) = label[1:].split('!')
                        if fnmatch.fnmatch(lFileName, lMask.lower()):
                            lKeep = False
                            continue
                    except Exception, e:
                        lKeep = False
                        continue

#               ---- label must begin with letter
                elif (label[0] < 'a') or (label[0] > 'z'):
                    label = label[1:]

#               ---- Select/Unselect section                    
                if pLabelList == [] or label in pLabelList:
                    lKeep = True
                    label = Tkinter.Label(pFrame,text=label.capitalize(), bg=lLabelColor)
                    #label.pack(side=TOP, fill=X)
                    label.pack(side=pSide, fill=pFill)
                else:
                    lKeep = False
                    
    label = Tkinter.Label(pFrame,text="", bg=lLabelColor)
    #label.pack(side=TOP, fill=X)
    label.pack(side=pSide, fill=pFill)
    button = Tkinter.Button(pFrame,text='Exit', command=pFrame.quit, bg=lButtonColor)
    #button.pack(side=TOP, fill=X)
    button.pack(side=pSide, fill=pFill)

if __name__ == '__main__':
    (mOptions, mArgs) = getopt.getopt(sys.argv[1:], 'b:d:e:hl:o')

#   ---- Set run options
    mDefaultLabel = 'light green'
    mDefaultButton = 'light blue'
    mOrient = VERTICAL
    for (mKey, mValue) in mOptions:
        if mKey == '-b':
            mDefaultButton = mValue
            
        elif mKey == '-d':                 # Set current directory
            if mValue.find('}') >= 0:
                mValue = Expand(mValue, mFileName)
            os.chdir(mValue)
            
        elif mKey == '-e':               # Set environment variable
            Do.setenviron(mValue, mFileName)

        elif mKey == '-h':
            print mHelpText
            
        elif mKey == '-l':
            mDefaultLabel = mValue
            
        elif mKey == '-o':
            mOrient = HORIZONTAL
    
    tk = Tkinter.Tk()                
    if len(mArgs) > 0:
    
        try:
            mDefaultMenu = os.environ['MENUPATH']
            if mVerbose:
                print 'BB1Ex Default menu path', mDefaultMenu
        except:
            mDefaultMenu = ''

                 
        try:
            mDefaultSection = os.environ['SECTION']
            if mVerbose:
                print 'BB1Ex Default section', mDefaultSection
        except:
            mDefaultSection = ''             # <------- Default section - Change this
        if mDefaultSection == '':
            mDefaultSection = ''
  
#       ---- First argument is absolute path to file to be processed
        mFileName = mArgs[0]                    
        mFileName = mFileName.replace("/","\\")
        
#       ---- Second argument is absolute path to initialization menu 
#                    Selected menu can depend on file path (ie. {o}\menus.ini) See do2.py
        if len(mArgs) > 1:                       
            mMenuArg = mArgs[1]                 # generate button bar
            mMenuArg += ';' + mDefaultMenu
        else:
            mMenuArg = mDefaultMenu        
    
#       ---- Prevent QEditor from performing replacement, may be removed                
        mMenuArg = mMenuArg.replace("(","{")
        mMenuArg = mMenuArg.replace(")","}")

#       ---- Expand menu name and/or select from list
        if mMenuArg.find('{') >= 0:
            mMenuName = ExpandArg(mMenuArg, mFileName, mDefaultMenu)
        elif mMenuArg.find(';') >= 0:
            for mMenu in mMenuArg.split(';'):
                if os.path.exists(mMenu):
                    mMenuName = mMenu
                    break
            else:
                mMenuName = mDefaultMenu 
        else:
            mMenuName = mMenuArg
        
    
#       ---- Remaining arguments list sections to include in menu
        if len(mArgs) > 2:
            mLabelList = []
            for lItem in mArgs[2:]:
                if lItem == '*':
                    break
                if lItem == '-':
                     lItem = mDefaultSection
                lItem = lItem.lower()
#               ---- Prevent QEditor from performing replacement, may be removed                
                lItem = lItem.replace("(","{")
                lItem = lItem.replace(")","}")
#               ---- Selected section can be dependent on file path                
                if lItem.find('{') >= 0:
                    lItem = ExpandArg(lItem, mFileName, '')
                mLabelList.append(lItem)
                
        elif mDefaultSection != '':
            mLabelList = [ mDefaultSection ]
            
        else:
            mLabelList = [ ]  
        
#       ---- Build buttonbar
        mFrame = Tkinter.Frame(tk, relief=RIDGE, borderwidth=2)
        mFrame.pack(fill=BOTH, expand=1)
        if mOrient == VERTICAL:
            Build(mFrame, mMenuName, mFileName, mLabelList, pDefaultLabel=mDefaultLabel, pDefaultButton=mDefaultButton, pSide=TOP, pFill=X)
        else:
            Build(mFrame, mMenuName, mFileName, mLabelList, pDefaultLabel=mDefaultLabel, pDefaultButton=mDefaultButton, pSide=LEFT, pFill=Y)

#       ---- Enter event loop
        tk.mainloop()
    else:
        tkMessageBox.showinfo('Syntax Error','File name required')
        
   
        
        
    
    

This recipe is an improvement to recipe 577038.

  1. Both the menu and section command line arguments can contain macros. The menu and\or section can depend upon the file being processed.

  2. The menu command line argument can contain a list of file paths containing macros. The first valid file path will be selected.

  3. Button and label colors can be set

  4. Both sections and individual command templates can be accepted based on the matching of the selected file path with a specified pattern.

  5. The Do (recipe 577439), Do2 (recipe 577440) and DoCommand (recipe 577441) are used for macro expansion are used ensuring all recipes use the same macro definitions. This includes DoM (recipe 577453 ) and pb (recipe 577454).

1 comment

Phil Rist (author) 13 years, 4 months ago  # | flag

Fixed problem with option list. Added missing d and e options.