Python GUI 030 – Transpo Groundwork 1

Turns out, combining nine different transposition systems into one program is a lot more work than I’d anticipated. I had a rough idea of what I wanted and how I wanted the pieces to fit together, I just didn’t know how to write specific parts of the code for it. I finally sat down to start writing everything and now there’s nothing ready to upload to Patreon or the blog to show my progress. This is because each piece is interrelated, and I won’t have finished code to show for any one piece until almost all of the others are also done.

I’ve come up with all kinds of things to say here as a preparatory introduction, but that’s boring. Here are the basics.


(Transpo start-up screen)

Requirements:
1) Combine all nine transposition systems into one Python program called Transpo Solver.
2) Use as much of the existing Python code as possible, and have as few individual .py support files as possible.
3) For each cipher system, have the options for manually encrypting and decrypting individual ciphers (for making and checking my own CONs (cryptograms)), as well as for automatically solving the CONs from the American Cryptogram Association’s (ACA) bi-monthly Cryptogram (Cm) newsletter.
4) For auto-solving CONs, have options for bruteforce attacks on the key (either counting numbers or reading words from a wordlist), and hillclimbing.
5) For manual encryption, have the option to show the intermediate step of building up the transposition template, which then leads to the option of exporting that template along with the plaintext and finished CON to a textfile for inclusion in a future issue of the Cm.
6) Develop the menu bar to allow for individual selection of the cipher types, and be able to go from one cipher type to the next without having to exit the program to reset the user interface each time.
7) Conversely to #6, be able to open a Cm digital CONs file, display only the transposition CONs in the file, and change the screen interface for each cipher type as I step through them, solving them or not.
8) When starting the program, automatically read the files in a Resource folder, collect the names of the wordlists in that folder, and allow the user (or program) to select the desired language for the CON.
9) With respect to (WRT) #8, for manual encryption, use the legal letters for the language selected (i.e. – Portuguese) to convert from a native letter set to the closest English equivalents prior to doing the transposition. For auto-solving, open the n-grams files for the language as specified by the CON in the digital file.
10) File handling, and data reading and saving should be the same as for the cryptarithm solver.


(Cipher type pulldown menu)

1) Select cipher type
This is hardcoded into the transpo_gui.py file in two parts. First is just the list of cipher types Transpo recognizes.

CON_TYPE = ['Columnar', 'Railfence', 'AMSCO',
............. 'Grille', 'Swagman', 'Redefence']]

Second is in the menubar setup.

... def make_menu(root):
....... menubar = Menu(root)
....... root.config(menu=menubar)

....... # Create the main menu bar
....... file_menu = Menu(menubar, tearoff=0)
....... file_menu.add_command(label='Open', command = lambda:

........... root.select_and_display(CONS_FILE_PATH, CON_TYPE))
....... file_menu.add_command(label='Save', command = lambda:

........... save_file(root, CONS_FILE_PATH, entries, 1, window_title))
....... file_menu.add_command(label='Save As', command = lambda:

........... save_file(root, CONS_FILE_PATH, entries, 0, window_title))
....... file_menu.add_separator()
....... file_menu.add_command(label='Exit', command =

........... root.confirm_exit) # or, self.client_exit

....... options_menu = Menu(menubar, tearoff=0)
....... options_menu.add_command(label='Go To', command = lambda:

........... select_con_no(root, entries, display_settings))
....... options_menu.add_command(label='Display Settings', command =

........... lambda: set_display_settings(root, CON_TYPE))
....... options_menu.add_command(label='Hillclimbing Settings',

........... command = lambda: set_hillclimbing_settings(root,
........... entries))

....... ciphers_menu = Menu(menubar, tearoff=0)
....... ciphers_menu.add_command(label='Columnar', command = lambda:

........... select_cipher_type(root, 'Columnar'))
....... ciphers_menu.add_command(label='Railfence', command = lambda:

........... select_cipher_type(root, 'Railfence'))
....... ciphers_menu.add_command(label='AMSCO', command = lambda:

........... select_cipher_type(root, 'AMSCO'))
....... ciphers_menu.add_command(label='Grille', command = lambda:

........... select_cipher_type(root, 'Grille'))
....... ciphers_menu.add_command(label='Swagman', command = lambda:

........... select_cipher_type(root, 'Swagman'))
....... ciphers_menu.add_command(label='Redefence', command = lambda:

........... select_cipher_type(root, 'Redefence'))

....... lang_menu = Menu(menubar, tearoff=0)
....... lang_menu.add_command(label='English', command = lambda:

........... select_language(lang_menu))

....... about_menu = Menu(menubar, tearoff=0)
....... about_menu.add_command(label='About the parser', command =

........... lambda: show_about_box(root, window_title))

....... menubar.add_cascade(label='File', menu=file_menu, underline=0)
....... menubar.add_cascade(label='Options', menu=options_menu,

........... underline=0)
....... menubar.add_cascade(label='Cipher Types', menu=ciphers_menu,

........... underline=0)
....... menubar.add_cascade(label='Languages', menu=lang_menu,

........... underline=0)
....... menubar.add_cascade(label='About', menu=about_menu,

........... underline=0)

Note that we also have the code for the language menu option, which starts out with ‘English’ as the default. More about that next time.

Clicking on one of the cipher types in the menu bar calls select_cipher_type(), passing the pointer to root, and the name of the selected cipher type as a string.


(Columnar Encryption/Decryption screen)

Right now, I only have screens implemented for Columnar, Railfence and Redefence (Redefence is just Railfence but with a key for transposing each of the rails in the cipher. More on this later.)

def select_cipher_type(root, cipher_name):
... global entries, frames_list

... if entries['type'] != None:
....... destroy_frame(root)

... if cipher_name == 'Columnar':
....... entries, frames_list, frame = columnar_make_frames(root,

........... entries, frames_list, NORM_FONT, FIXED_NORM_FONT)
....... frame.grid(column=0, row=1)

... elif cipher_name == 'Railfence' or cipher_name == 'Redefence':
....... entries, frames_list, frame = railfence_make_frames(root,

........... entries, frames_list, NORM_FONT, FIXED_NORM_FONT,
........... 'Invert', cipher_name)
....... frame.grid(column=0, row=1)
....... if cipher_name == 'Redefence':
........... entries['message'].set('Key format: KKKKK-O')

... else:
....... entries['type'] = None
....... showinfo(root, '%s not implemented yet.' % cipher_name)

... if entries['button_frame'] == None:
....... button_frame = create_ed_button_frame(root).grid(column=1,

........... row=1, rowspan = 2)

... print('Selected %s' % (cipher_name))

I had to create two different button frames, one for the manual encryption and decryption screens, and one for auto-solving the Cm CONs. And, I’ve got different screens for the cipher portions of the program. I’ll write about the cipher frames later.

Right now, let’s look at the above code. entries[] and frames_list[] are global dict objects declared at the top of the transpo_gui.py file.

entries = {'type': None, 'button_frame': None}
frames_list = {}

entries[] is used to hold the pointers to the tkinter widgets, such as the textboxes and labels, which primarily make up the cipher frame contents.

frames_list[] holds the pointers to the cipher frames.

If the user clicks on a cipher type from the menu bar, the program calls select_cipher_type(). First, we check whether we already have frames open.

... if entries['type'] != None:
....... destroy_frame(root)

Call destroy_frame(), which steps through each of the frames registered in frames_list[], destroys them, then empties the dict object.

def destroy_frame(root):
... for f in frames_list:
....... if frames_list[f] != None:
........... frames_list[f].destroy()
... frames_list.clear()

So, we should be starting with a clean window (ignoring the message bar frame). Then we have:

... if cipher_name == 'Columnar':
....... entries, frames_list, frame = columnar_make_frames(root,

........... entries, frames_list, NORM_FONT, FIXED_NORM_FONT)
....... frame.grid(column=0, row=1)

If the user clicked on ‘Columnar’ from the pulldown menu, we’ll call columnar_make_frames(), which is located in transpo_columnar.py. I’ll describe that later. For now, I’ll just state that each of the make_frames() functions load the frame pointers to frames_list[], and each of the widget pointers to entries[], and then returns the updated dicts.

Because I’m returning a tuple, I can’t just do something like:

tuple = columnar_make_frames().grid(column=0, row=1)

In order to make the call to .grid(), I have to return the pointer to the new frame I created as part of the tuple (the var frame), and then use the var frame separately to set .grid(). That is:

entries, frames_list, frame = columnar_make_frames()
frame.grid(column=0, row=1)


(Columnar cipher frame)

Ok, that creates the encryption/decryption part of the Columnar screen. Again, if I click on Railfence, the Columnar frame is destroyed and the Railfence cipher screen is displayed.


(Railfence cipher frame)

If one of the other cipher types is selected, which haven’t been implemented yet, I just null out the ‘type’ item in entries and display a status message:

... else:
....... entries['type'] = None
....... showinfo(root, '%s not implemented yet.' % cipher_name)

That brings us to the end of select_cipher_type(). If the buttons frame hasn’t been created yet, the ‘button_frame‘ item in the entries[] dict will be ‘None’. In this case, make the frame. Otherwise, exit the function.

... if entries['button_frame'] == None:
....... button_frame = create_ed_button_frame(root).grid(column=1,

........... row=1, rowspan = 2)

Right now, there are two button_frame functions. create_button_frame() makes the same frame as before, for stepping through the Cm CONs and clicking the Solve button.


(create_button_frame() frame)

The new function is create_ed_button_frame(), for “create the encryption-decryption button frame.”


(create_ed_button_frame() frame)

def create_ed_button_frame(root):
... global entries, frames_list

... frame.columnconfigure(0, weight=1)

... button_encrypt = ttk.Button(frame, text='Encrypt', command=lambda:
....... encrypt_decrypt(0, entries))
... button_decrypt = ttk.Button(frame, text='Decrypt', command=lambda:

....... encrypt_decrypt(1, entries))
... button_dummy = tk.Button(frame, state='disabled', bg='gray94',

....... relief='flat')
... button_show = ttk.Button(frame, text='Show', command=lambda:

....... show_template(root, entries))
... button_format = ttk.Button(frame, text='Format', command=lambda:

....... showinfo(root, 'Not implemented yet.'))

... for widget in frame.winfo_children():
....... widget.grid(padx=5, pady=5)

... entries['encrypt'] = button_encrypt
... entries['decrypt'] = button_decrypt
... entries['show'] = button_show
... entries['format'] = button_format

... entries['button_frame'] = frame

... return frame

As can be seen, we’re creating four buttons – Encrypt, Decrypt, Show and Format – and one dummy to act as a spacer. The pointers to the button widgets are registered in the entries[] dict, although I’m not enabling and disabling them at this time. And, I’m registering this frame in entries as well. In this way, when I go from one cipher type to another, the buttons frame will NOT be destroyed by destroy_frame(). I have to handle destroying the manual encrypt/decrypt buttons frame separately when I open a digital CONs file.

This then brings me to the new buttons. Encrypt and Decrypt call functions based on the selected cipher type, and I’ll write more about them later. Suffice it to say they do what you’d expect them to do.

At the moment, Format is just a placeholder. Ultimately, I want to create new CONs to submit to the Cm for inclusion in the newsletter, and Format will be used to automate the process of formatting the title, plaintext, ciphertext and “worksheet” for me. But, that will come later.

Then we have the Show button. Show calls show_template(), which then calls the appropriate method in the .py file for that cipher type.

def show_template(root, entries):
... ciphertype = entries['type']
... key = entries['key'].get()
... text = entries['in_area'].get(1.0, 'end').strip()
... result = ''

... if ciphertype == 'Columnar':
....... result = columnar_showtemplate(text, key)
....... if len(result) > 0:
........... DisplayInfoBig(root, wtitle='Formatted', msg=result,

............... lheight=20, lwidth=40, lfont=FIXED_NORM_FONT)
....... else:
........... showinfo(window_title, 'Empty message')

... elif ciphertype == 'Railfence' or ciphertype == 'Redefence':
....... invert = entries['invert_var'].get()
....... result = railfence_showtemplate(text, key, invert,

........... ciphertype == 'Railfence')
....... if len(result) > 0:
........... DisplayInfoBig(root, wtitle='Formatted', msg=result,

............... lheight=20, lwidth=40, lfont=FIXED_NORM_FONT)
....... else:
........... showinfo(window_title, 'Empty message')

For initialization, we’re getting the cipher type from entries[], which had been preloaded when we created the cipher frame. We need the key word or number from the ‘keyentry box widget. And, the plaintext used to create the CON is in the ‘in_area‘ textbox widget.

... ciphertype = entries['type']
... key = entries['key'].get()
... text = entries['in_area'].get(1.0, 'end').strip()
... result = ''

Then we call the show_() function, which returns a formatted string to us. Assuming the string is not empty, we’ll call DisplayInfoBig(), which is just a variant of showinfo(), but with a textbox widget with horizontal and vertical scrollbars.

Using the following columnar cipher,


(Columnar screen with plaintext and cipher)

Here’s what the plaintext looks like in the columnar template. Note that “common” is the keyword I used, and the number underneath that is the equivalent key number.


(Show results for the columnar cipher)

For contrast, here’s the railfence screen with cipher,


(Railfence screen with plaintext and cipher)

And the corresponding Show screen.


(Show results for the railfence cipher)

Next up: Some stuff about languages, maybe. I haven’t gotten that far yet.

Published by The Chief

Who wants to know?

Leave a comment

Design a site like this with WordPress.com
Get started