a387952493
fixes #189
705 lines
25 KiB
Python
Executable File
705 lines
25 KiB
Python
Executable File
#!/usr/bin/python
|
|
|
|
"""
|
|
MiniEdit: a simple network editor for Mininet
|
|
|
|
This is a simple demonstration of how one might build a
|
|
GUI application using Mininet as the network model.
|
|
|
|
Development version - not entirely functional!
|
|
|
|
Bob Lantz, April 2010
|
|
"""
|
|
|
|
from Tkinter import Frame, Button, Label, Scrollbar, Canvas
|
|
from Tkinter import Menu, BitmapImage, PhotoImage, Wm, Toplevel
|
|
|
|
# someday: from ttk import *
|
|
|
|
from mininet.log import setLogLevel
|
|
from mininet.net import Mininet
|
|
from mininet.util import ipStr
|
|
from mininet.term import makeTerm, cleanUpScreens
|
|
|
|
class MiniEdit( Frame ):
|
|
|
|
"A simple network editor for Mininet."
|
|
|
|
def __init__( self, parent=None, cheight=200, cwidth=500 ):
|
|
|
|
Frame.__init__( self, parent )
|
|
self.action = None
|
|
self.appName = 'MiniEdit'
|
|
|
|
# Style
|
|
self.font = ( 'Geneva', 9 )
|
|
self.smallFont = ( 'Geneva', 7 )
|
|
self.bg = 'white'
|
|
|
|
# Title
|
|
self.top = self.winfo_toplevel()
|
|
self.top.title( self.appName )
|
|
|
|
# Menu bar
|
|
self.createMenubar()
|
|
|
|
# Editing canvas
|
|
self.cheight, self.cwidth = cheight, cwidth
|
|
self.cframe, self.canvas = self.createCanvas()
|
|
|
|
# Toolbar
|
|
self.images = miniEditImages()
|
|
self.buttons = {}
|
|
self.active = None
|
|
self.tools = ( 'Select', 'Host', 'Switch', 'Link' )
|
|
self.customColors = { 'Switch': 'darkGreen', 'Host': 'blue' }
|
|
self.toolbar = self.createToolbar()
|
|
|
|
# Layout
|
|
self.toolbar.grid( column=0, row=0, sticky='nsew')
|
|
self.cframe.grid( column=1, row=0 )
|
|
self.columnconfigure( 1, weight=1 )
|
|
self.rowconfigure( 0, weight=1 )
|
|
self.pack( expand=True, fill='both' )
|
|
|
|
# About box
|
|
self.aboutBox = None
|
|
|
|
# Initialize node data
|
|
self.nodeBindings = self.createNodeBindings()
|
|
self.nodePrefixes = { 'Switch': 's', 'Host': 'h' }
|
|
self.widgetToItem = {}
|
|
self.itemToWidget = {}
|
|
|
|
# Initialize link tool
|
|
self.link = self.linkWidget = None
|
|
|
|
# Selection support
|
|
self.selection = None
|
|
|
|
# Keyboard bindings
|
|
self.bind( '<Control-q>', lambda event: self.quit() )
|
|
self.bind( '<KeyPress-Delete>', self.deleteSelection )
|
|
self.bind( '<KeyPress-BackSpace>', self.deleteSelection )
|
|
self.focus()
|
|
|
|
# Event handling initalization
|
|
self.linkx = self.linky = self.linkItem = None
|
|
self.lastSelection = None
|
|
|
|
# Model initialization
|
|
self.links = {}
|
|
self.nodeCount = 0
|
|
self.net = None
|
|
|
|
# Close window gracefully
|
|
Wm.wm_protocol( self.top, name='WM_DELETE_WINDOW', func=self.quit )
|
|
|
|
def quit( self ):
|
|
"Stop our network, if any, then quit."
|
|
self.stop()
|
|
Frame.quit( self )
|
|
|
|
def createMenubar( self ):
|
|
"Create our menu bar."
|
|
|
|
font = self.font
|
|
|
|
mbar = Menu( self.top, font=font )
|
|
self.top.configure( menu=mbar )
|
|
|
|
# Application menu
|
|
appMenu = Menu( mbar, tearoff=False )
|
|
mbar.add_cascade( label=self.appName, font=font, menu=appMenu )
|
|
appMenu.add_command( label='About MiniEdit', command=self.about,
|
|
font=font)
|
|
appMenu.add_separator()
|
|
appMenu.add_command( label='Quit', command=self.quit, font=font )
|
|
|
|
#fileMenu = Menu( mbar, tearoff=False )
|
|
#mbar.add_cascade( label="File", font=font, menu=fileMenu )
|
|
#fileMenu.add_command( label="Load...", font=font )
|
|
#fileMenu.add_separator()
|
|
#fileMenu.add_command( label="Save", font=font )
|
|
#fileMenu.add_separator()
|
|
#fileMenu.add_command( label="Print", font=font )
|
|
|
|
editMenu = Menu( mbar, tearoff=False )
|
|
mbar.add_cascade( label="Edit", font=font, menu=editMenu )
|
|
editMenu.add_command( label="Cut", font=font,
|
|
command=lambda: self.deleteSelection( None ) )
|
|
|
|
runMenu = Menu( mbar, tearoff=False )
|
|
mbar.add_cascade( label="Run", font=font, menu=runMenu )
|
|
runMenu.add_command( label="Run", font=font, command=self.doRun )
|
|
runMenu.add_command( label="Stop", font=font, command=self.doStop )
|
|
runMenu.add_separator()
|
|
runMenu.add_command( label='Xterm', font=font, command=self.xterm )
|
|
|
|
# Canvas
|
|
|
|
def createCanvas( self ):
|
|
"Create and return our scrolling canvas frame."
|
|
f = Frame( self )
|
|
|
|
canvas = Canvas( f, width=self.cwidth, height=self.cheight,
|
|
bg=self.bg )
|
|
|
|
# Scroll bars
|
|
xbar = Scrollbar( f, orient='horizontal', command=canvas.xview )
|
|
ybar = Scrollbar( f, orient='vertical', command=canvas.yview )
|
|
canvas.configure( xscrollcommand=xbar.set, yscrollcommand=ybar.set )
|
|
|
|
# Resize box
|
|
resize = Label( f, bg='white' )
|
|
|
|
# Layout
|
|
canvas.grid( row=0, column=1, sticky='nsew')
|
|
ybar.grid( row=0, column=2, sticky='ns')
|
|
xbar.grid( row=1, column=1, sticky='ew' )
|
|
resize.grid( row=1, column=2, sticky='nsew' )
|
|
|
|
# Resize behavior
|
|
f.rowconfigure( 0, weight=1 )
|
|
f.columnconfigure( 1, weight=1 )
|
|
f.grid( row=0, column=0, sticky='nsew' )
|
|
f.bind( '<Configure>', lambda event: self.updateScrollRegion() )
|
|
|
|
# Mouse bindings
|
|
canvas.bind( '<ButtonPress-1>', self.clickCanvas )
|
|
canvas.bind( '<B1-Motion>', self.dragCanvas )
|
|
canvas.bind( '<ButtonRelease-1>', self.releaseCanvas )
|
|
|
|
return f, canvas
|
|
|
|
def updateScrollRegion( self ):
|
|
"Update canvas scroll region to hold everything."
|
|
bbox = self.canvas.bbox( 'all' )
|
|
if bbox is not None:
|
|
self.canvas.configure( scrollregion=( 0, 0, bbox[ 2 ],
|
|
bbox[ 3 ] ) )
|
|
|
|
def canvasx( self, x_root ):
|
|
"Convert root x coordinate to canvas coordinate."
|
|
c = self.canvas
|
|
return c.canvasx( x_root ) - c.winfo_rootx()
|
|
|
|
def canvasy( self, y_root ):
|
|
"Convert root y coordinate to canvas coordinate."
|
|
c = self.canvas
|
|
return c.canvasy( y_root ) - c.winfo_rooty()
|
|
|
|
# Toolbar
|
|
|
|
def activate( self, toolName ):
|
|
"Activate a tool and press its button."
|
|
# Adjust button appearance
|
|
if self.active:
|
|
self.buttons[ self.active ].configure( relief='raised' )
|
|
self.buttons[ toolName ].configure( relief='sunken' )
|
|
# Activate dynamic bindings
|
|
self.active = toolName
|
|
|
|
def createToolbar( self ):
|
|
"Create and return our toolbar frame."
|
|
|
|
toolbar = Frame( self )
|
|
|
|
# Tools
|
|
for tool in self.tools:
|
|
cmd = ( lambda t=tool: self.activate( t ) )
|
|
b = Button( toolbar, text=tool, font=self.smallFont, command=cmd)
|
|
if tool in self.images:
|
|
b.config( height=35, image=self.images[ tool ] )
|
|
# b.config( compound='top' )
|
|
b.pack( fill='x' )
|
|
self.buttons[ tool ] = b
|
|
self.activate( self.tools[ 0 ] )
|
|
|
|
# Spacer
|
|
Label( toolbar, text='' ).pack()
|
|
|
|
# Commands
|
|
for cmd, color in [ ( 'Stop', 'darkRed' ), ( 'Run', 'darkGreen' ) ]:
|
|
doCmd = getattr( self, 'do' + cmd )
|
|
b = Button( toolbar, text=cmd, font=self.smallFont,
|
|
fg=color, command=doCmd )
|
|
b.pack( fill='x', side='bottom' )
|
|
|
|
return toolbar
|
|
|
|
def doRun( self ):
|
|
"Run command."
|
|
self.activate( 'Select' )
|
|
for tool in self.tools:
|
|
self.buttons[ tool ].config( state='disabled' )
|
|
self.start()
|
|
|
|
def doStop( self ):
|
|
"Stop command."
|
|
self.stop()
|
|
for tool in self.tools:
|
|
self.buttons[ tool ].config( state='normal' )
|
|
|
|
# Generic canvas handler
|
|
#
|
|
# We could have used bindtags, as in nodeIcon, but
|
|
# the dynamic approach used here
|
|
# may actually require less code. In any case, it's an
|
|
# interesting introspection-based alternative to bindtags.
|
|
|
|
def canvasHandle( self, eventName, event ):
|
|
"Generic canvas event handler"
|
|
if self.active is None:
|
|
return
|
|
toolName = self.active
|
|
handler = getattr( self, eventName + toolName, None )
|
|
if handler is not None:
|
|
handler( event )
|
|
|
|
def clickCanvas( self, event ):
|
|
"Canvas click handler."
|
|
self.canvasHandle( 'click', event )
|
|
|
|
def dragCanvas( self, event ):
|
|
"Canvas drag handler."
|
|
self.canvasHandle( 'drag', event )
|
|
|
|
def releaseCanvas( self, event ):
|
|
"Canvas mouse up handler."
|
|
self.canvasHandle( 'release', event )
|
|
|
|
# Currently the only items we can select directly are
|
|
# links. Nodes are handled by bindings in the node icon.
|
|
|
|
def findItem( self, x, y ):
|
|
"Find items at a location in our canvas."
|
|
items = self.canvas.find_overlapping( x, y, x, y )
|
|
if len( items ) == 0:
|
|
return None
|
|
else:
|
|
return items[ 0 ]
|
|
|
|
# Canvas bindings for Select, Host, Switch and Link tools
|
|
|
|
def clickSelect( self, event ):
|
|
"Select an item."
|
|
self.selectItem( self.findItem( event.x, event.y ) )
|
|
|
|
def deleteItem( self, item ):
|
|
"Delete an item."
|
|
# Don't delete while network is running
|
|
if self.buttons[ 'Select' ][ 'state' ] == 'disabled':
|
|
return
|
|
# Delete from model
|
|
if item in self.links:
|
|
self.deleteLink( item )
|
|
if item in self.itemToWidget:
|
|
self.deleteNode( item )
|
|
# Delete from view
|
|
self.canvas.delete( item )
|
|
|
|
def deleteSelection( self, _event ):
|
|
"Delete the selected item."
|
|
if self.selection is not None:
|
|
self.deleteItem( self.selection )
|
|
self.selectItem( None )
|
|
|
|
def nodeIcon( self, node, name ):
|
|
"Create a new node icon."
|
|
icon = Button( self.canvas, image=self.images[ node ],
|
|
text=name, compound='top' )
|
|
# Unfortunately bindtags wants a tuple
|
|
bindtags = [ str( self.nodeBindings ) ]
|
|
bindtags += list( icon.bindtags() )
|
|
icon.bindtags( tuple( bindtags ) )
|
|
return icon
|
|
|
|
def newNode( self, node, event ):
|
|
"Add a new node to our canvas."
|
|
c = self.canvas
|
|
x, y = c.canvasx( event.x ), c.canvasy( event.y )
|
|
self.nodeCount += 1
|
|
name = self.nodePrefixes[ node ] + str( self.nodeCount )
|
|
icon = self.nodeIcon( node, name )
|
|
item = self.canvas.create_window( x, y, anchor='c', window=icon,
|
|
tags=node )
|
|
self.widgetToItem[ icon ] = item
|
|
self.itemToWidget[ item ] = icon
|
|
self.selectItem( item )
|
|
icon.links = {}
|
|
|
|
def clickHost( self, event ):
|
|
"Add a new host to our canvas."
|
|
self.newNode( 'Host', event )
|
|
|
|
def clickSwitch( self, event ):
|
|
"Add a new switch to our canvas."
|
|
self.newNode( 'Switch', event )
|
|
|
|
def dragLink( self, event ):
|
|
"Drag a link's endpoint to another node."
|
|
if self.link is None:
|
|
return
|
|
# Since drag starts in widget, we use root coords
|
|
x = self.canvasx( event.x_root )
|
|
y = self.canvasy( event.y_root )
|
|
c = self.canvas
|
|
c.coords( self.link, self.linkx, self.linky, x, y )
|
|
|
|
def releaseLink( self, _event ):
|
|
"Give up on the current link."
|
|
if self.link is not None:
|
|
self.canvas.delete( self.link )
|
|
self.linkWidget = self.linkItem = self.link = None
|
|
|
|
# Generic node handlers
|
|
|
|
def createNodeBindings( self ):
|
|
"Create a set of bindings for nodes."
|
|
bindings = {
|
|
'<ButtonPress-1>': self.clickNode,
|
|
'<B1-Motion>': self.dragNode,
|
|
'<ButtonRelease-1>': self.releaseNode,
|
|
'<Enter>': self.enterNode,
|
|
'<Leave>': self.leaveNode,
|
|
'<Double-ButtonPress-1>': self.xterm
|
|
}
|
|
l = Label() # lightweight-ish owner for bindings
|
|
for event, binding in bindings.items():
|
|
l.bind( event, binding )
|
|
return l
|
|
|
|
def selectItem( self, item ):
|
|
"Select an item and remember old selection."
|
|
self.lastSelection = self.selection
|
|
self.selection = item
|
|
|
|
def enterNode( self, event ):
|
|
"Select node on entry."
|
|
self.selectNode( event )
|
|
|
|
def leaveNode( self, _event ):
|
|
"Restore old selection on exit."
|
|
self.selectItem( self.lastSelection )
|
|
|
|
def clickNode( self, event ):
|
|
"Node click handler."
|
|
if self.active is 'Link':
|
|
self.startLink( event )
|
|
else:
|
|
self.selectNode( event )
|
|
return 'break'
|
|
|
|
def dragNode( self, event ):
|
|
"Node drag handler."
|
|
if self.active is 'Link':
|
|
self.dragLink( event )
|
|
else:
|
|
self.dragNodeAround( event )
|
|
|
|
def releaseNode( self, event ):
|
|
"Node release handler."
|
|
if self.active is 'Link':
|
|
self.finishLink( event )
|
|
|
|
# Specific node handlers
|
|
|
|
def selectNode( self, event ):
|
|
"Select the node that was clicked on."
|
|
item = self.widgetToItem.get( event.widget, None )
|
|
self.selectItem( item )
|
|
|
|
def dragNodeAround( self, event ):
|
|
"Drag a node around on the canvas."
|
|
c = self.canvas
|
|
# Convert global to local coordinates;
|
|
# Necessary since x, y are widget-relative
|
|
x = self.canvasx( event.x_root )
|
|
y = self.canvasy( event.y_root )
|
|
w = event.widget
|
|
# Adjust node position
|
|
item = self.widgetToItem[ w ]
|
|
c.coords( item, x, y )
|
|
# Adjust link positions
|
|
for dest in w.links:
|
|
link = w.links[ dest ]
|
|
item = self.widgetToItem[ dest ]
|
|
x1, y1 = c.coords( item )
|
|
c.coords( link, x, y, x1, y1 )
|
|
|
|
def startLink( self, event ):
|
|
"Start a new link."
|
|
if event.widget not in self.widgetToItem:
|
|
# Didn't click on a node
|
|
return
|
|
w = event.widget
|
|
item = self.widgetToItem[ w ]
|
|
x, y = self.canvas.coords( item )
|
|
self.link = self.canvas.create_line( x, y, x, y, width=4,
|
|
fill='blue', tag='link' )
|
|
self.linkx, self.linky = x, y
|
|
self.linkWidget = w
|
|
self.linkItem = item
|
|
|
|
# Link bindings
|
|
# Selection still needs a bit of work overall
|
|
# Callbacks ignore event
|
|
|
|
def select( _event, link=self.link ):
|
|
"Select item on mouse entry."
|
|
self.selectItem( link )
|
|
|
|
def highlight( _event, link=self.link ):
|
|
"Highlight item on mouse entry."
|
|
# self.selectItem( link )
|
|
self.canvas.itemconfig( link, fill='green' )
|
|
|
|
def unhighlight( _event, link=self.link ):
|
|
"Unhighlight item on mouse exit."
|
|
self.canvas.itemconfig( link, fill='blue' )
|
|
# self.selectItem( None )
|
|
|
|
self.canvas.tag_bind( self.link, '<Enter>', highlight )
|
|
self.canvas.tag_bind( self.link, '<Leave>', unhighlight )
|
|
self.canvas.tag_bind( self.link, '<ButtonPress-1>', select )
|
|
|
|
def finishLink( self, event ):
|
|
"Finish creating a link"
|
|
if self.link is None:
|
|
return
|
|
source = self.linkWidget
|
|
c = self.canvas
|
|
# Since we dragged from the widget, use root coords
|
|
x, y = self.canvasx( event.x_root ), self.canvasy( event.y_root )
|
|
target = self.findItem( x, y )
|
|
dest = self.itemToWidget.get( target, None )
|
|
if ( source is None or dest is None or source == dest
|
|
or dest in source.links or source in dest.links ):
|
|
self.releaseLink( event )
|
|
return
|
|
# For now, don't allow hosts to be directly linked
|
|
stags = self.canvas.gettags( self.widgetToItem[ source ] )
|
|
dtags = self.canvas.gettags( target )
|
|
if 'Host' in stags and 'Host' in dtags:
|
|
self.releaseLink( event )
|
|
return
|
|
x, y = c.coords( target )
|
|
c.coords( self.link, self.linkx, self.linky, x, y )
|
|
self.addLink( source, dest )
|
|
# We're done
|
|
self.link = self.linkWidget = None
|
|
|
|
# Menu handlers
|
|
|
|
def about( self ):
|
|
"Display about box."
|
|
about = self.aboutBox
|
|
if about is None:
|
|
bg = 'white'
|
|
about = Toplevel( bg='white' )
|
|
about.title( 'About' )
|
|
info = self.appName + ': a simple network editor for MiniNet'
|
|
warning = 'Development version - not entirely functional!'
|
|
author = 'Bob Lantz <rlantz@cs>, April 2010'
|
|
line1 = Label( about, text=info, font='Helvetica 10 bold', bg=bg )
|
|
line2 = Label( about, text=warning, font='Helvetica 9', bg=bg )
|
|
line3 = Label( about, text=author, font='Helvetica 9', bg=bg )
|
|
line1.pack( padx=20, pady=10 )
|
|
line2.pack(pady=10 )
|
|
line3.pack(pady=10 )
|
|
hide = ( lambda about=about: about.withdraw() )
|
|
self.aboutBox = about
|
|
# Hide on close rather than destroying window
|
|
Wm.wm_protocol( about, name='WM_DELETE_WINDOW', func=hide )
|
|
# Show (existing) window
|
|
about.deiconify()
|
|
|
|
def createToolImages( self ):
|
|
"Create toolbar (and icon) images."
|
|
|
|
# Model interface
|
|
#
|
|
# Ultimately we will either want to use a topo or
|
|
# mininet object here, probably.
|
|
|
|
def addLink( self, source, dest ):
|
|
"Add link to model."
|
|
source.links[ dest ] = self.link
|
|
dest.links[ source ] = self.link
|
|
self.links[ self.link ] = ( source, dest )
|
|
|
|
def deleteLink( self, link ):
|
|
"Delete link from model."
|
|
pair = self.links.get( link, None )
|
|
if pair is not None:
|
|
source, dest = pair
|
|
del source.links[ dest ]
|
|
del dest.links[ source ]
|
|
if link is not None:
|
|
del self.links[ link ]
|
|
|
|
def deleteNode( self, item ):
|
|
"Delete node (and its links) from model."
|
|
widget = self.itemToWidget[ item ]
|
|
for link in widget.links.values():
|
|
# Delete from view and model
|
|
self.deleteItem( link )
|
|
del self.itemToWidget[ item ]
|
|
del self.widgetToItem[ widget ]
|
|
|
|
def build( self ):
|
|
"Build network based on our topology."
|
|
|
|
net = Mininet( topo=None )
|
|
|
|
# Make controller
|
|
net.addController( 'c0' )
|
|
# Make nodes
|
|
for widget in self.widgetToItem:
|
|
name = widget[ 'text' ]
|
|
tags = self.canvas.gettags( self.widgetToItem[ widget ] )
|
|
nodeNum = int( name[ 1: ] )
|
|
if 'Switch' in tags:
|
|
net.addSwitch( name )
|
|
elif 'Host' in tags:
|
|
#Generate IP adddress in the 10.0/8 block
|
|
ipAddr = ( 10 << 24 ) + nodeNum
|
|
net.addHost( name, ip=ipStr( ipAddr ) )
|
|
else:
|
|
raise Exception( "Cannot create mystery node: " + name )
|
|
# Make links
|
|
for link in self.links.values():
|
|
( src, dst ) = link
|
|
srcName, dstName = src[ 'text' ], dst[ 'text' ]
|
|
src, dst = net.nameToNode[ srcName ], net.nameToNode[ dstName ]
|
|
src.linkTo( dst )
|
|
|
|
# Build network (we have to do this separately at the moment )
|
|
net.build()
|
|
|
|
return net
|
|
|
|
def start( self ):
|
|
"Start network."
|
|
if self.net is None:
|
|
self.net = self.build()
|
|
self.net.start()
|
|
|
|
def stop( self ):
|
|
"Stop network."
|
|
if self.net is not None:
|
|
self.net.stop()
|
|
cleanUpScreens()
|
|
self.net = None
|
|
|
|
def xterm( self, _ignore=None ):
|
|
"Make an xterm when a button is pressed."
|
|
if ( self.selection is None or
|
|
self.net is None or
|
|
self.selection not in self.itemToWidget ):
|
|
return
|
|
name = self.itemToWidget[ self.selection ][ 'text' ]
|
|
if name not in self.net.nameToNode:
|
|
return
|
|
term = makeTerm( self.net.nameToNode[ name ], 'Host' )
|
|
self.net.terms += term
|
|
|
|
def miniEditImages():
|
|
"Create and return images for MiniEdit."
|
|
|
|
# Image data. Git will be unhappy. However, the alternative
|
|
# is to keep track of separate binary files, which is also
|
|
# unappealing.
|
|
|
|
return {
|
|
'Select': BitmapImage(
|
|
file='/usr/include/X11/bitmaps/left_ptr' ),
|
|
|
|
'Host': PhotoImage( data=r"""
|
|
R0lGODlhIAAYAPcAMf//////zP//mf//Zv//M///AP/M///MzP/M
|
|
mf/MZv/MM//MAP+Z//+ZzP+Zmf+ZZv+ZM/+ZAP9m//9mzP9mmf9m
|
|
Zv9mM/9mAP8z//8zzP8zmf8zZv8zM/8zAP8A//8AzP8Amf8AZv8A
|
|
M/8AAMz//8z/zMz/mcz/Zsz/M8z/AMzM/8zMzMzMmczMZszMM8zM
|
|
AMyZ/8yZzMyZmcyZZsyZM8yZAMxm/8xmzMxmmcxmZsxmM8xmAMwz
|
|
/8wzzMwzmcwzZswzM8wzAMwA/8wAzMwAmcwAZswAM8wAAJn//5n/
|
|
zJn/mZn/Zpn/M5n/AJnM/5nMzJnMmZnMZpnMM5nMAJmZ/5mZzJmZ
|
|
mZmZZpmZM5mZAJlm/5lmzJlmmZlmZplmM5lmAJkz/5kzzJkzmZkz
|
|
ZpkzM5kzAJkA/5kAzJkAmZkAZpkAM5kAAGb//2b/zGb/mWb/Zmb/
|
|
M2b/AGbM/2bMzGbMmWbMZmbMM2bMAGaZ/2aZzGaZmWaZZmaZM2aZ
|
|
AGZm/2ZmzGZmmWZmZmZmM2ZmAGYz/2YzzGYzmWYzZmYzM2YzAGYA
|
|
/2YAzGYAmWYAZmYAM2YAADP//zP/zDP/mTP/ZjP/MzP/ADPM/zPM
|
|
zDPMmTPMZjPMMzPMADOZ/zOZzDOZmTOZZjOZMzOZADNm/zNmzDNm
|
|
mTNmZjNmMzNmADMz/zMzzDMzmTMzZjMzMzMzADMA/zMAzDMAmTMA
|
|
ZjMAMzMAAAD//wD/zAD/mQD/ZgD/MwD/AADM/wDMzADMmQDMZgDM
|
|
MwDMAACZ/wCZzACZmQCZZgCZMwCZAABm/wBmzABmmQBmZgBmMwBm
|
|
AAAz/wAzzAAzmQAzZgAzMwAzAAAA/wAAzAAAmQAAZgAAM+4AAN0A
|
|
ALsAAKoAAIgAAHcAAFUAAEQAACIAABEAAADuAADdAAC7AACqAACI
|
|
AAB3AABVAABEAAAiAAARAAAA7gAA3QAAuwAAqgAAiAAAdwAAVQAA
|
|
RAAAIgAAEe7u7t3d3bu7u6qqqoiIiHd3d1VVVURERCIiIhEREQAA
|
|
ACH5BAEAAAAALAAAAAAgABgAAAiNAAH8G0iwoMGDCAcKTMiw4UBw
|
|
BPXVm0ixosWLFvVBHFjPoUeC9Tb+6/jRY0iQ/8iVbHiS40CVKxG2
|
|
HEkQZsyCM0mmvGkw50uePUV2tEnOZkyfQA8iTYpTKNOgKJ+C3AhO
|
|
p9SWVaVOfWj1KdauTL9q5UgVbFKsEjGqXVtP40NwcBnCjXtw7tx/
|
|
C8cSBBAQADs=
|
|
""" ),
|
|
|
|
'Switch': PhotoImage( data=r"""
|
|
R0lGODlhIAAYAPcAMf//////zP//mf//Zv//M///AP/M///MzP/M
|
|
mf/MZv/MM//MAP+Z//+ZzP+Zmf+ZZv+ZM/+ZAP9m//9mzP9mmf9m
|
|
Zv9mM/9mAP8z//8zzP8zmf8zZv8zM/8zAP8A//8AzP8Amf8AZv8A
|
|
M/8AAMz//8z/zMz/mcz/Zsz/M8z/AMzM/8zMzMzMmczMZszMM8zM
|
|
AMyZ/8yZzMyZmcyZZsyZM8yZAMxm/8xmzMxmmcxmZsxmM8xmAMwz
|
|
/8wzzMwzmcwzZswzM8wzAMwA/8wAzMwAmcwAZswAM8wAAJn//5n/
|
|
zJn/mZn/Zpn/M5n/AJnM/5nMzJnMmZnMZpnMM5nMAJmZ/5mZzJmZ
|
|
mZmZZpmZM5mZAJlm/5lmzJlmmZlmZplmM5lmAJkz/5kzzJkzmZkz
|
|
ZpkzM5kzAJkA/5kAzJkAmZkAZpkAM5kAAGb//2b/zGb/mWb/Zmb/
|
|
M2b/AGbM/2bMzGbMmWbMZmbMM2bMAGaZ/2aZzGaZmWaZZmaZM2aZ
|
|
AGZm/2ZmzGZmmWZmZmZmM2ZmAGYz/2YzzGYzmWYzZmYzM2YzAGYA
|
|
/2YAzGYAmWYAZmYAM2YAADP//zP/zDP/mTP/ZjP/MzP/ADPM/zPM
|
|
zDPMmTPMZjPMMzPMADOZ/zOZzDOZmTOZZjOZMzOZADNm/zNmzDNm
|
|
mTNmZjNmMzNmADMz/zMzzDMzmTMzZjMzMzMzADMA/zMAzDMAmTMA
|
|
ZjMAMzMAAAD//wD/zAD/mQD/ZgD/MwD/AADM/wDMzADMmQDMZgDM
|
|
MwDMAACZ/wCZzACZmQCZZgCZMwCZAABm/wBmzABmmQBmZgBmMwBm
|
|
AAAz/wAzzAAzmQAzZgAzMwAzAAAA/wAAzAAAmQAAZgAAM+4AAN0A
|
|
ALsAAKoAAIgAAHcAAFUAAEQAACIAABEAAADuAADdAAC7AACqAACI
|
|
AAB3AABVAABEAAAiAAARAAAA7gAA3QAAuwAAqgAAiAAAdwAAVQAA
|
|
RAAAIgAAEe7u7t3d3bu7u6qqqoiIiHd3d1VVVURERCIiIhEREQAA
|
|
ACH5BAEAAAAALAAAAAAgABgAAAhwAAEIHEiwoMGDCBMqXMiwocOH
|
|
ECNKnEixosWB3zJq3Mixo0eNAL7xG0mypMmTKPl9Cznyn8uWL/m5
|
|
/AeTpsyYI1eKlBnO5r+eLYHy9Ck0J8ubPmPOrMmUpM6UUKMa/Ui1
|
|
6saLWLNq3cq1q9evYB0GBAA7
|
|
""" ),
|
|
|
|
'Link': PhotoImage( data=r"""
|
|
R0lGODlhFgAWAPcAMf//////zP//mf//Zv//M///AP/M///MzP/M
|
|
mf/MZv/MM//MAP+Z//+ZzP+Zmf+ZZv+ZM/+ZAP9m//9mzP9mmf9m
|
|
Zv9mM/9mAP8z//8zzP8zmf8zZv8zM/8zAP8A//8AzP8Amf8AZv8A
|
|
M/8AAMz//8z/zMz/mcz/Zsz/M8z/AMzM/8zMzMzMmczMZszMM8zM
|
|
AMyZ/8yZzMyZmcyZZsyZM8yZAMxm/8xmzMxmmcxmZsxmM8xmAMwz
|
|
/8wzzMwzmcwzZswzM8wzAMwA/8wAzMwAmcwAZswAM8wAAJn//5n/
|
|
zJn/mZn/Zpn/M5n/AJnM/5nMzJnMmZnMZpnMM5nMAJmZ/5mZzJmZ
|
|
mZmZZpmZM5mZAJlm/5lmzJlmmZlmZplmM5lmAJkz/5kzzJkzmZkz
|
|
ZpkzM5kzAJkA/5kAzJkAmZkAZpkAM5kAAGb//2b/zGb/mWb/Zmb/
|
|
M2b/AGbM/2bMzGbMmWbMZmbMM2bMAGaZ/2aZzGaZmWaZZmaZM2aZ
|
|
AGZm/2ZmzGZmmWZmZmZmM2ZmAGYz/2YzzGYzmWYzZmYzM2YzAGYA
|
|
/2YAzGYAmWYAZmYAM2YAADP//zP/zDP/mTP/ZjP/MzP/ADPM/zPM
|
|
zDPMmTPMZjPMMzPMADOZ/zOZzDOZmTOZZjOZMzOZADNm/zNmzDNm
|
|
mTNmZjNmMzNmADMz/zMzzDMzmTMzZjMzMzMzADMA/zMAzDMAmTMA
|
|
ZjMAMzMAAAD//wD/zAD/mQD/ZgD/MwD/AADM/wDMzADMmQDMZgDM
|
|
MwDMAACZ/wCZzACZmQCZZgCZMwCZAABm/wBmzABmmQBmZgBmMwBm
|
|
AAAz/wAzzAAzmQAzZgAzMwAzAAAA/wAAzAAAmQAAZgAAM+4AAN0A
|
|
ALsAAKoAAIgAAHcAAFUAAEQAACIAABEAAADuAADdAAC7AACqAACI
|
|
AAB3AABVAABEAAAiAAARAAAA7gAA3QAAuwAAqgAAiAAAdwAAVQAA
|
|
RAAAIgAAEe7u7t3d3bu7u6qqqoiIiHd3d1VVVURERCIiIhEREQAA
|
|
ACH5BAEAAAAALAAAAAAWABYAAAhIAAEIHEiwoEGBrhIeXEgwoUKG
|
|
Cx0+hGhQoiuKBy1irChxY0GNHgeCDAlgZEiTHlFuVImRJUWXEGEy
|
|
lBmxI8mSNknm1Dnx5sCAADs=
|
|
""" )
|
|
}
|
|
|
|
if __name__ == '__main__':
|
|
setLogLevel( 'info' )
|
|
app = MiniEdit()
|
|
app.mainloop()
|