Revision 82b72072 examples/miniedit.py

View differences:

examples/miniedit.py
24 24
class MiniEdit( Frame ):
25 25

  
26 26
    "A simple network editor for Mininet."
27
    
27

  
28 28
    def __init__( self, parent=None, cheight=200, cwidth=500 ):
29
        
29

  
30 30
        Frame.__init__( self, parent )
31 31
        self.action = None
32 32
        self.appName = 'MiniEdit'
......
39 39
        # Title
40 40
        self.top = self.winfo_toplevel()
41 41
        self.top.title( self.appName )
42
        
42

  
43 43
        # Menu bar
44 44
        self.createMenubar()
45
        
45

  
46 46
        # Editing canvas
47 47
        self.cheight, self.cwidth = cheight, cwidth
48 48
        self.cframe, self.canvas = self.createCanvas()
49
        
49

  
50 50
        # Toolbar
51
        self.images = miniEditImages()
51 52
        self.buttons = {}
52 53
        self.active = None
53 54
        self.tools = ( 'Select', 'Host', 'Switch', 'Link' )
54
        self.images = self.createImages()
55 55
        self.customColors = { 'Switch': 'darkGreen', 'Host': 'blue' }
56 56
        self.toolbar = self.createToolbar()
57 57

  
......
61 61
        self.columnconfigure( 1, weight=1 )
62 62
        self.rowconfigure( 0, weight=1 )
63 63
        self.pack( expand=True, fill='both' )
64
    
64

  
65 65
        # About box
66 66
        self.aboutBox = None
67
        
67

  
68 68
        # Initialize node data
69 69
        self.nodeBindings = self.createNodeBindings()
70 70
        self.nodePrefixes =  { 'Switch': 's', 'Host': 'h' }
71 71
        self.widgetToItem = {}
72 72
        self.itemToWidget = {}
73
        
73

  
74 74
        # Initialize link tool
75 75
        self.link = self.linkWidget = None
76 76

  
77 77
        # Selection support
78 78
        self.selection = None
79
        
79

  
80 80
        # Keyboard bindings
81 81
        self.bind( '<Control-q>', lambda event: self.quit() )
82 82
        self.bind( '<KeyPress-Delete>', self.deleteSelection )
83 83
        self.bind( '<KeyPress-BackSpace>', self.deleteSelection )
84 84
        self.focus()
85
        
85

  
86
        # Event handling initalization
87
        self.linkx = self.linky = self.linkItem = None
88
        self.lastSelection = None
89

  
86 90
        # Model initialization
87 91
        self.links = {}
88 92
        self.nodeCount = 0
89 93
        self.net = None
90
        
94

  
91 95
        # Close window gracefully
92 96
        Wm.wm_protocol( self.top, name='WM_DELETE_WINDOW', func=self.quit )
93
    
97

  
94 98
    def quit( self ):
95 99
        "Stop our network, if any, then quit."
96 100
        self.stop()
97 101
        Frame.quit( self )
98
        
102

  
99 103
    def createMenubar( self ):
100 104
        "Create our menu bar."
101 105

  
102 106
        font = self.font
103
        
107

  
104 108
        mbar = Menu( self.top, font=font )
105 109
        self.top.configure( menu=mbar )
106
        
110

  
107 111
        # Application menu
108 112
        appMenu = Menu( mbar, tearoff=False )
109 113
        mbar.add_cascade( label=self.appName, font=font, menu=appMenu )
......
111 115
            font=font)
112 116
        appMenu.add_separator()
113 117
        appMenu.add_command( label='Quit', command=self.quit, font=font )
114
        
115
        """
116
        fileMenu = Menu( mbar, tearoff=False )
117
        mbar.add_cascade( label="File", font=font, menu=fileMenu )
118
        fileMenu.add_command( label="Load...", font=font )
119
        fileMenu.add_separator()
120
        fileMenu.add_command( label="Save", font=font )
121
        fileMenu.add_separator()
122
        fileMenu.add_command( label="Print", font=font )
123
        """
124
        
118

  
119
        #fileMenu = Menu( mbar, tearoff=False )
120
        #mbar.add_cascade( label="File", font=font, menu=fileMenu )
121
        #fileMenu.add_command( label="Load...", font=font )
122
        #fileMenu.add_separator()
123
        #fileMenu.add_command( label="Save", font=font )
124
        #fileMenu.add_separator()
125
        #fileMenu.add_command( label="Print", font=font )
126

  
125 127
        editMenu = Menu( mbar, tearoff=False )
126 128
        mbar.add_cascade( label="Edit", font=font, menu=editMenu )
127
        editMenu.add_command( label="Cut", font=font, 
129
        editMenu.add_command( label="Cut", font=font,
128 130
            command=lambda: self.deleteSelection( None ) )
129 131

  
130 132
        runMenu = Menu( mbar, tearoff=False )
......
135 137
        runMenu.add_command( label='Xterm', font=font, command=self.xterm )
136 138

  
137 139
    # Canvas
138
        
140

  
139 141
    def createCanvas( self ):
140 142
        "Create and return our scrolling canvas frame."
141 143
        f = Frame( self )
142 144

  
143
        canvas = Canvas( f, width=self.cwidth, height=self.cheight, 
145
        canvas = Canvas( f, width=self.cwidth, height=self.cheight,
144 146
            bg=self.bg )
145
        
147

  
146 148
        # Scroll bars
147 149
        xbar = Scrollbar( f, orient='horizontal', command=canvas.xview )
148 150
        ybar = Scrollbar( f, orient='vertical', command=canvas.yview )
149 151
        canvas.configure( xscrollcommand=xbar.set, yscrollcommand=ybar.set )
150
        
152

  
151 153
        # Resize box
152 154
        resize = Label( f, bg='white' )
153
        
155

  
154 156
        # Layout
155 157
        canvas.grid( row=0, column=1, sticky='nsew')
156 158
        ybar.grid( row=0, column=2, sticky='ns')
157 159
        xbar.grid( row=1, column=1, sticky='ew' )
158 160
        resize.grid( row=1, column=2, sticky='nsew' )
159
        
161

  
160 162
        # Resize behavior
161 163
        f.rowconfigure( 0, weight=1 )
162 164
        f.columnconfigure( 1, weight=1 )
......
167 169
        canvas.bind( '<ButtonPress-1>', self.clickCanvas )
168 170
        canvas.bind( '<B1-Motion>', self.dragCanvas )
169 171
        canvas.bind( '<ButtonRelease-1>', self.releaseCanvas )
170
                      
172

  
171 173
        return f, canvas
172 174

  
173 175
    def updateScrollRegion( self ):
......
175 177
        bbox = self.canvas.bbox( 'all' )
176 178
        if bbox is not None:
177 179
            self.canvas.configure( scrollregion=( 0, 0, bbox[ 2 ], bbox[ 3 ] ) )
178
        
180

  
179 181
    def canvasx( self, x_root ):
180 182
        "Convert root x coordinate to canvas coordinate."
181 183
        c = self.canvas
182 184
        return c.canvasx( x_root ) - c.winfo_rootx()
183
        
185

  
184 186
    def canvasy( self, y_root ):
185 187
        "Convert root y coordinate to canvas coordinate."
186 188
        c = self.canvas
187 189
        return c.canvasy( y_root ) - c.winfo_rooty()
188 190

  
189 191
    # Toolbar
190
    
192

  
191 193
    def activate( self, toolName ):
194
        "Activate a tool and press its button."
192 195
        # Adjust button appearance
193 196
        if self.active:
194 197
            self.buttons[ self.active ].configure( relief='raised' )
195 198
        self.buttons[ toolName ].configure( relief='sunken' )
196 199
        # Activate dynamic bindings
197 200
        self.active = toolName
198
        
201

  
199 202
    def createToolbar( self ):
200 203
        "Create and return our toolbar frame."
201
        
204

  
202 205
        toolbar = Frame( self )
203
        
206

  
204 207
        # Tools
205 208
        for tool in self.tools:
206
            cmd = lambda t=tool: self.activate( t )
209
            cmd = ( lambda t=tool: self.activate( t ) )
207 210
            b = Button( toolbar, text=tool, font=self.smallFont, command=cmd)
208 211
            if tool in self.images:
209 212
                b.config( height=35, image=self.images[ tool ] )
210 213
                # b.config( compound='top' )
211 214
            b.pack( fill='x' )
212
            self.buttons[ tool ] = b 
215
            self.buttons[ tool ] = b
213 216
        self.activate( self.tools[ 0 ] )
214
        
217

  
215 218
        # Spacer
216 219
        Label( toolbar, text='' ).pack()
217
        
220

  
218 221
        # Commands
219 222
        for cmd, color in [ ( 'Stop', 'darkRed' ), ( 'Run', 'darkGreen' ) ]:
220
            def doCmd( f=getattr( self, 'do' + cmd ) ):
221
                f()
222
            b = Button( toolbar, text=cmd, font=self.smallFont, fg=color, command=doCmd ) 
223
            doCmd = getattr( self, 'do' + cmd )
224
            b = Button( toolbar, text=cmd, font=self.smallFont, 
225
                fg=color, command=doCmd )
223 226
            b.pack( fill='x', side='bottom' )
224
        
227

  
225 228
        return toolbar
226
    
229

  
227 230
    def doRun( self ):
228 231
        "Run command."
229 232
        self.activate( 'Select' )
230 233
        for tool in self.tools:
231 234
            self.buttons[ tool ].config( state='disabled' )
232 235
        self.start()
233
        
236

  
234 237
    def doStop( self ):
235 238
        "Stop command."
236 239
        self.stop()
237 240
        for tool in self.tools:
238 241
            self.buttons[ tool ].config( state='normal' )
239
        
242

  
240 243
    # Generic canvas handler
241 244
    #
242
    # We could have used bindtags, as in nodeIcon, but 
245
    # We could have used bindtags, as in nodeIcon, but
243 246
    # the dynamic approach used here
244 247
    # may actually require less code. In any case, it's an
245 248
    # interesting introspection-based alternative to bindtags.
246
    
249

  
247 250
    def canvasHandle( self, eventName, event ):
248 251
        "Generic canvas event handler"
249 252
        if self.active is None:
......
252 255
        handler = getattr( self, eventName + toolName, None )
253 256
        if handler is not None:
254 257
            handler( event )
255
            
258

  
256 259
    def clickCanvas( self, event ):
257 260
        "Canvas click handler."
258 261
        self.canvasHandle( 'click', event )
259
    
262

  
260 263
    def dragCanvas( self, event ):
261 264
        "Canvas drag handler."
262 265
        self.canvasHandle( 'drag', event )
263
        
266

  
264 267
    def releaseCanvas( self, event ):
265 268
        "Canvas mouse up handler."
266 269
        self.canvasHandle( 'release', event )
267
    
270

  
268 271
    # Currently the only items we can select directly are
269 272
    # links. Nodes are handled by bindings in the node icon.
270
    # If we want to allow node deletion, we will 
271
    
273

  
272 274
    def findItem( self, x, y ):
275
        "Find items at a location in our canvas."
273 276
        items = self.canvas.find_overlapping( x, y, x, y )
274 277
        if len( items ) == 0:
275 278
            return None
276 279
        else:
277 280
            return items[ 0 ]
278
 
281

  
279 282
    # Canvas bindings for Select, Host, Switch and Link tools
280 283

  
281 284
    def clickSelect( self, event ):
282 285
        "Select an item."
283 286
        self.selectItem( self.findItem( event.x, event.y ) )
284
        
287

  
285 288
    def deleteItem( self, item ):
286 289
        "Delete an item."
287 290
        # Don't delete while network is running
......
293 296
        if item in self.itemToWidget:
294 297
            self.deleteNode( item )
295 298
        # Delete from view
296
        self.canvas.delete( item )                
297
        
299
        self.canvas.delete( item )
300

  
301
    # Callback ignores event
302
    # pylint: disable-msg=W0613
298 303
    def deleteSelection( self, event ):
304
        "Delete the selected item."
299 305
        if self.selection is not None:
300 306
            self.deleteItem( self.selection )
301 307
        self.selectItem( None )
302
    
308
    # pylint: enable-msg=W0613
309

  
303 310
    def nodeIcon( self, node, name ):
304 311
        "Create a new node icon."
305
        icon = Button( self.canvas, image=self.images[ node ], 
312
        icon = Button( self.canvas, image=self.images[ node ],
306 313
            text=name, compound='top' )
307 314
        # Unfortunately bindtags wants a tuple
308 315
        bindtags = [ str( self.nodeBindings ) ]
309 316
        bindtags += list( icon.bindtags() )
310 317
        icon.bindtags( tuple( bindtags ) )
311 318
        return icon
312
        
319

  
313 320
    def newNode( self, node, event ):
314 321
        "Add a new node to our canvas."
315 322
        c = self.canvas
......
317 324
        self.nodeCount += 1
318 325
        name = self.nodePrefixes[ node ] + str( self.nodeCount )
319 326
        icon = self.nodeIcon( node, name )
320
        item = self.canvas.create_window( x, y, anchor='c', window=icon, tags=node )
327
        item = self.canvas.create_window( x, y, anchor='c', 
328
            window=icon, tags=node )
321 329
        self.widgetToItem[ icon ] = item
322 330
        self.itemToWidget[ item ] = icon
323 331
        self.selectItem( item )
324 332
        icon.links = {}
325
            
333

  
326 334
    def clickHost( self, event ):
327 335
        "Add a new host to our canvas."
328 336
        self.newNode( 'Host', event )
......
330 338
    def clickSwitch( self, event ):
331 339
        "Add a new switch to our canvas."
332 340
        self.newNode( 'Switch', event )
333
    
341

  
334 342
    def dragLink( self, event ):
335 343
        "Drag a link's endpoint to another node."
336 344
        if self.link is None:
......
341 349
        c = self.canvas
342 350
        c.coords( self.link, self.linkx, self.linky, x, y )
343 351

  
352
    # Callback ignores event
353
    # pylint: disable-msg=W0613
344 354
    def releaseLink( self, event ):
345 355
        "Give up on the current link."
346 356
        if self.link is not None:
347 357
            self.canvas.delete( self.link )
348 358
        self.linkWidget = self.linkItem = self.link = None
349
        
350
    # Generic node handlers 
351
        
352
    def createBindings( self, bindings ):
353
        l = Label()
354
        for event, binding in bindings.items():
355
            l.bind( event, binding )
356
        return l
357
            
359
    # pylint: enable-msg=W0613
360

  
361
    # Generic node handlers
362

  
358 363
    def createNodeBindings( self ):
359 364
        "Create a set of bindings for nodes."
360
        return self.createBindings( {
365
        bindings = {
361 366
            '<ButtonPress-1>': self.clickNode,
362 367
            '<B1-Motion>': self.dragNode,
363 368
            '<ButtonRelease-1>': self.releaseNode,
364 369
            '<Enter>': self.enterNode,
365 370
            '<Leave>': self.leaveNode,
366 371
            '<Double-ButtonPress-1>': self.xterm
367
        } )
368
    
372
        }
373
        l = Label()  # lightweight-ish owner for bindings
374
        for event, binding in bindings.items():
375
            l.bind( event, binding )
376
        return l
377

  
369 378
    def selectItem( self, item ):
379
        "Select an item and remember old selection."
370 380
        self.lastSelection = self.selection
371 381
        self.selection = item
372 382

  
373 383
    def enterNode( self, event ):
384
        "Select node on entry."
374 385
        self.selectNode( event )
375
        
386

  
387
    # Callback ignores event
388
    # pylint: disable-msg=W0613
376 389
    def leaveNode( self, event ):
390
        "Restore old selection on exit."
377 391
        self.selectItem( self.lastSelection )
392
    # pylint: enable-msg=W0613
378 393

  
379 394
    def clickNode( self, event ):
380 395
        "Node click handler."
......
383 398
        else:
384 399
            self.selectNode( event )
385 400
        return 'break'
386
                
401

  
387 402
    def dragNode( self, event ):
388 403
        "Node drag handler."
389 404
        if self.active is 'Link':
390 405
            self.dragLink( event )
391 406
        else:
392 407
            self.dragNodeAround( event )
393
    
408

  
394 409
    def releaseNode( self, event ):
395 410
        "Node release handler."
396 411
        if self.active is 'Link':
397 412
            self.finishLink( event )
398
    
413

  
399 414
    # Specific node handlers
400 415

  
401 416
    def selectNode( self, event ):
402 417
        "Select the node that was clicked on."
403 418
        item = self.widgetToItem.get( event.widget, None )
404 419
        self.selectItem( item )
405
        
420

  
406 421
    def dragNodeAround( self, event ):
407 422
        "Drag a node around on the canvas."
408 423
        c = self.canvas
......
429 444
        w = event.widget
430 445
        item = self.widgetToItem[ w ]
431 446
        x, y = self.canvas.coords( item )
432
        self.link = self.canvas.create_line( x, y, x, y, width=4, 
447
        self.link = self.canvas.create_line( x, y, x, y, width=4,
433 448
            fill='blue', tag='link' )
434 449
        self.linkx, self.linky = x, y
435 450
        self.linkWidget = w
436 451
        self.linkItem = item
437 452
        # Link bindings
438 453
        # Selection still needs a bit of work overall
454
        # Callbacks ignore event
455
        # pylint: disable-msg=W0613
439 456
        def select( event, link=self.link ):
457
            "Select item on mouse entry."
440 458
            self.selectItem( link )
441 459
        def highlight( event, link=self.link ):
460
            "Highlight item on mouse entry."
442 461
            # self.selectItem( link )
443 462
            self.canvas.itemconfig( link, fill='green' )
444 463
        def unhighlight( event, link=self.link ):
464
            "Unhighlight item on mouse exit."
445 465
            self.canvas.itemconfig( link, fill='blue' )
446 466
            # self.selectItem( None )
467
        # pylint: disable-msg=W0613
447 468
        self.canvas.tag_bind( self.link, '<Enter>', highlight )
448 469
        self.canvas.tag_bind( self.link, '<Leave>', unhighlight )
449 470
        self.canvas.tag_bind( self.link, '<ButtonPress-1>', select )
450
        
471

  
451 472
    def finishLink( self, event ):
452 473
        "Finish creating a link"
453 474
        if self.link is None:
......
471 492
        x, y = c.coords( target )
472 493
        c.coords( self.link, self.linkx, self.linky, x, y )
473 494
        self.addLink( source, dest )
474

  
475

  
476 495
        # We're done
477 496
        self.link = self.linkWidget = None
478
    
497

  
479 498
    # Menu handlers
480
    
499

  
481 500
    def about( self ):
482 501
        "Display about box."
483 502
        about = self.aboutBox
......
494 513
            line1.pack( padx=20, pady=10 )
495 514
            line2.pack(pady=10 )
496 515
            line3.pack(pady=10 )
497
            hide = lambda about=about: about.withdraw()
516
            hide = ( lambda about=about: about.withdraw() )
498 517
            self.aboutBox = about
499 518
            # Hide on close rather than destroying window
500 519
            Wm.wm_protocol( about, name='WM_DELETE_WINDOW', func=hide )
501 520
        # Show (existing) window
502 521
        about.deiconify()
503 522

  
504
    
523

  
505 524
    def createToolImages( self ):
506 525
        "Create toolbar (and icon) images."
507
        
526

  
508 527
    # Model interface
509 528
    #
510 529
    # Ultimately we will either want to use a topo or
511 530
    # mininet object here, probably.
512
    
531

  
513 532
    def addLink( self, source, dest ):
514 533
        "Add link to model."
515 534
        source.links[ dest ] = self.link
516 535
        dest.links[ source ] = self.link
517 536
        self.links[ self.link ] = ( source, dest )
518
        
537

  
519 538
    def deleteLink( self, link ):
520 539
        "Delete link from model."
521 540
        pair = self.links.get( link, None )
......
539 558
        "Build network based on our topology."
540 559

  
541 560
        net = Mininet( topo=None )
542
        
561

  
543 562
        # Make controller
544 563
        net.addController( 'c0' )
545 564
        # Make nodes
......
559 578
            srcName, dstName  = src[ 'text' ], dst[ 'text' ]
560 579
            src, dst = net.nameToNode[ srcName ], net.nameToNode[ dstName ]
561 580
            src.linkTo( dst )
562
        
581

  
563 582
        # Build network (we have to do this separately at the moment )
564 583
        net.build()
565
        
584

  
566 585
        return net
567
        
586

  
568 587
    def start( self ):
588
        "Start network."
569 589
        if self.net is None:
570
            self.net = self.build()
571 590
            self.net.start()
572
    
591

  
573 592
    def stop( self ):
593
        "Stop network."
574 594
        if self.net is not None:
575 595
            self.net.stop()
576 596
        cleanUpScreens()
577 597
        self.net = None
578
        
598

  
579 599
    def xterm( self, ignore=None ):
600
        "Make an xterm when a button is pressed."
580 601
        if ( self.selection is None or
581 602
             self.net is None or
582 603
             self.selection not in self.itemToWidget ):
......
584 605
        name = self.itemToWidget[ self.selection ][ 'text' ]
585 606
        if name not in self.net.nameToNode:
586 607
            return
587
        self.net.terms.append( makeTerm( self.net.nameToNode[ name ],  'Host' ) )
588
    
589
    # Image data. Git will be unhappy.
608
        term = makeTerm( self.net.nameToNode[ name ], 'Host' )
609
        self.net.terms.append( term )
610

  
611

  
612
def miniEditImages():
613
    "Create and return images for MiniEdit."
614

  
615
    # Image data. Git will be unhappy. However, the alternative
616
    # is to keep track of separate binary files, which is also
617
    # unappealing.
618
        
619
    return {
620
        'Select': BitmapImage(
621
            file='/usr/include/X11/bitmaps/left_ptr' ),
622

  
623
        'Host': PhotoImage( data=r"""
624
            R0lGODlhIAAYAPcAMf//////zP//mf//Zv//M///AP/M///MzP/M
625
            mf/MZv/MM//MAP+Z//+ZzP+Zmf+ZZv+ZM/+ZAP9m//9mzP9mmf9m
626
            Zv9mM/9mAP8z//8zzP8zmf8zZv8zM/8zAP8A//8AzP8Amf8AZv8A
627
            M/8AAMz//8z/zMz/mcz/Zsz/M8z/AMzM/8zMzMzMmczMZszMM8zM
628
            AMyZ/8yZzMyZmcyZZsyZM8yZAMxm/8xmzMxmmcxmZsxmM8xmAMwz
629
            /8wzzMwzmcwzZswzM8wzAMwA/8wAzMwAmcwAZswAM8wAAJn//5n/
630
            zJn/mZn/Zpn/M5n/AJnM/5nMzJnMmZnMZpnMM5nMAJmZ/5mZzJmZ
631
            mZmZZpmZM5mZAJlm/5lmzJlmmZlmZplmM5lmAJkz/5kzzJkzmZkz
632
            ZpkzM5kzAJkA/5kAzJkAmZkAZpkAM5kAAGb//2b/zGb/mWb/Zmb/
633
            M2b/AGbM/2bMzGbMmWbMZmbMM2bMAGaZ/2aZzGaZmWaZZmaZM2aZ
634
            AGZm/2ZmzGZmmWZmZmZmM2ZmAGYz/2YzzGYzmWYzZmYzM2YzAGYA
635
            /2YAzGYAmWYAZmYAM2YAADP//zP/zDP/mTP/ZjP/MzP/ADPM/zPM
636
            zDPMmTPMZjPMMzPMADOZ/zOZzDOZmTOZZjOZMzOZADNm/zNmzDNm
637
            mTNmZjNmMzNmADMz/zMzzDMzmTMzZjMzMzMzADMA/zMAzDMAmTMA
638
            ZjMAMzMAAAD//wD/zAD/mQD/ZgD/MwD/AADM/wDMzADMmQDMZgDM
639
            MwDMAACZ/wCZzACZmQCZZgCZMwCZAABm/wBmzABmmQBmZgBmMwBm
640
            AAAz/wAzzAAzmQAzZgAzMwAzAAAA/wAAzAAAmQAAZgAAM+4AAN0A
641
            ALsAAKoAAIgAAHcAAFUAAEQAACIAABEAAADuAADdAAC7AACqAACI
642
            AAB3AABVAABEAAAiAAARAAAA7gAA3QAAuwAAqgAAiAAAdwAAVQAA
643
            RAAAIgAAEe7u7t3d3bu7u6qqqoiIiHd3d1VVVURERCIiIhEREQAA
644
            ACH5BAEAAAAALAAAAAAgABgAAAiNAAH8G0iwoMGDCAcKTMiw4UBw
645
            BPXVm0ixosWLFvVBHFjPoUeC9Tb+6/jRY0iQ/8iVbHiS40CVKxG2
646
            HEkQZsyCM0mmvGkw50uePUV2tEnOZkyfQA8iTYpTKNOgKJ+C3AhO
647
            p9SWVaVOfWj1KdauTL9q5UgVbFKsEjGqXVtP40NwcBnCjXtw7tx/
648
            C8cSBBAQADs=
649
        """ ),
650

  
651
        'Switch': PhotoImage( data=r"""
652
            R0lGODlhIAAYAPcAMf//////zP//mf//Zv//M///AP/M///MzP/M
653
            mf/MZv/MM//MAP+Z//+ZzP+Zmf+ZZv+ZM/+ZAP9m//9mzP9mmf9m
654
            Zv9mM/9mAP8z//8zzP8zmf8zZv8zM/8zAP8A//8AzP8Amf8AZv8A
655
            M/8AAMz//8z/zMz/mcz/Zsz/M8z/AMzM/8zMzMzMmczMZszMM8zM
656
            AMyZ/8yZzMyZmcyZZsyZM8yZAMxm/8xmzMxmmcxmZsxmM8xmAMwz
657
            /8wzzMwzmcwzZswzM8wzAMwA/8wAzMwAmcwAZswAM8wAAJn//5n/
658
            zJn/mZn/Zpn/M5n/AJnM/5nMzJnMmZnMZpnMM5nMAJmZ/5mZzJmZ
659
            mZmZZpmZM5mZAJlm/5lmzJlmmZlmZplmM5lmAJkz/5kzzJkzmZkz
660
            ZpkzM5kzAJkA/5kAzJkAmZkAZpkAM5kAAGb//2b/zGb/mWb/Zmb/
661
            M2b/AGbM/2bMzGbMmWbMZmbMM2bMAGaZ/2aZzGaZmWaZZmaZM2aZ
662
            AGZm/2ZmzGZmmWZmZmZmM2ZmAGYz/2YzzGYzmWYzZmYzM2YzAGYA
663
            /2YAzGYAmWYAZmYAM2YAADP//zP/zDP/mTP/ZjP/MzP/ADPM/zPM
664
            zDPMmTPMZjPMMzPMADOZ/zOZzDOZmTOZZjOZMzOZADNm/zNmzDNm
665
            mTNmZjNmMzNmADMz/zMzzDMzmTMzZjMzMzMzADMA/zMAzDMAmTMA
666
            ZjMAMzMAAAD//wD/zAD/mQD/ZgD/MwD/AADM/wDMzADMmQDMZgDM
667
            MwDMAACZ/wCZzACZmQCZZgCZMwCZAABm/wBmzABmmQBmZgBmMwBm
668
            AAAz/wAzzAAzmQAzZgAzMwAzAAAA/wAAzAAAmQAAZgAAM+4AAN0A
669
            ALsAAKoAAIgAAHcAAFUAAEQAACIAABEAAADuAADdAAC7AACqAACI
670
            AAB3AABVAABEAAAiAAARAAAA7gAA3QAAuwAAqgAAiAAAdwAAVQAA
671
            RAAAIgAAEe7u7t3d3bu7u6qqqoiIiHd3d1VVVURERCIiIhEREQAA
672
            ACH5BAEAAAAALAAAAAAgABgAAAhwAAEIHEiwoMGDCBMqXMiwocOH
673
            ECNKnEixosWB3zJq3Mixo0eNAL7xG0mypMmTKPl9Cznyn8uWL/m5
674
            /AeTpsyYI1eKlBnO5r+eLYHy9Ck0J8ubPmPOrMmUpM6UUKMa/Ui1
675
            6saLWLNq3cq1q9evYB0GBAA7
676
        """ ),
677

  
678
        'Link': PhotoImage( data=r"""
679
            R0lGODlhFgAWAPcAMf//////zP//mf//Zv//M///AP/M///MzP/M
680
            mf/MZv/MM//MAP+Z//+ZzP+Zmf+ZZv+ZM/+ZAP9m//9mzP9mmf9m
681
            Zv9mM/9mAP8z//8zzP8zmf8zZv8zM/8zAP8A//8AzP8Amf8AZv8A
682
            M/8AAMz//8z/zMz/mcz/Zsz/M8z/AMzM/8zMzMzMmczMZszMM8zM
683
            AMyZ/8yZzMyZmcyZZsyZM8yZAMxm/8xmzMxmmcxmZsxmM8xmAMwz
684
            /8wzzMwzmcwzZswzM8wzAMwA/8wAzMwAmcwAZswAM8wAAJn//5n/
685
            zJn/mZn/Zpn/M5n/AJnM/5nMzJnMmZnMZpnMM5nMAJmZ/5mZzJmZ
686
            mZmZZpmZM5mZAJlm/5lmzJlmmZlmZplmM5lmAJkz/5kzzJkzmZkz
687
            ZpkzM5kzAJkA/5kAzJkAmZkAZpkAM5kAAGb//2b/zGb/mWb/Zmb/
688
            M2b/AGbM/2bMzGbMmWbMZmbMM2bMAGaZ/2aZzGaZmWaZZmaZM2aZ
689
            AGZm/2ZmzGZmmWZmZmZmM2ZmAGYz/2YzzGYzmWYzZmYzM2YzAGYA
690
            /2YAzGYAmWYAZmYAM2YAADP//zP/zDP/mTP/ZjP/MzP/ADPM/zPM
691
            zDPMmTPMZjPMMzPMADOZ/zOZzDOZmTOZZjOZMzOZADNm/zNmzDNm
692
            mTNmZjNmMzNmADMz/zMzzDMzmTMzZjMzMzMzADMA/zMAzDMAmTMA
693
            ZjMAMzMAAAD//wD/zAD/mQD/ZgD/MwD/AADM/wDMzADMmQDMZgDM
694
            MwDMAACZ/wCZzACZmQCZZgCZMwCZAABm/wBmzABmmQBmZgBmMwBm
695
            AAAz/wAzzAAzmQAzZgAzMwAzAAAA/wAAzAAAmQAAZgAAM+4AAN0A
696
            ALsAAKoAAIgAAHcAAFUAAEQAACIAABEAAADuAADdAAC7AACqAACI
697
            AAB3AABVAABEAAAiAAARAAAA7gAA3QAAuwAAqgAAiAAAdwAAVQAA
698
            RAAAIgAAEe7u7t3d3bu7u6qqqoiIiHd3d1VVVURERCIiIhEREQAA
699
            ACH5BAEAAAAALAAAAAAWABYAAAhIAAEIHEiwoEGBrhIeXEgwoUKG
700
            Cx0+hGhQoiuKBy1irChxY0GNHgeCDAlgZEiTHlFuVImRJUWXEGEy
701
            lBmxI8mSNknm1Dnx5sCAADs=
702
        """ )
703
    }
590 704

  
591
    def createImages( self ):
592
        "Initialize button/icon images."
593
        images = {}
594
        
595
        images[ 'Select' ] = BitmapImage( file='/usr/include/X11/bitmaps/left_ptr' )
596
        
597
        images[ 'Host' ] = PhotoImage( data=r"""
598
            R0lGODlhIAAYAPcAMf//////zP//mf//Zv//M///AP/M///MzP/Mmf/MZv/MM//MAP+Z//+Z
599
            zP+Zmf+ZZv+ZM/+ZAP9m//9mzP9mmf9mZv9mM/9mAP8z//8zzP8zmf8zZv8zM/8zAP8A//8A
600
            zP8Amf8AZv8AM/8AAMz//8z/zMz/mcz/Zsz/M8z/AMzM/8zMzMzMmczMZszMM8zMAMyZ/8yZ
601
            zMyZmcyZZsyZM8yZAMxm/8xmzMxmmcxmZsxmM8xmAMwz/8wzzMwzmcwzZswzM8wzAMwA/8wA
602
            zMwAmcwAZswAM8wAAJn//5n/zJn/mZn/Zpn/M5n/AJnM/5nMzJnMmZnMZpnMM5nMAJmZ/5mZ
603
            zJmZmZmZZpmZM5mZAJlm/5lmzJlmmZlmZplmM5lmAJkz/5kzzJkzmZkzZpkzM5kzAJkA/5kA
604
            zJkAmZkAZpkAM5kAAGb//2b/zGb/mWb/Zmb/M2b/AGbM/2bMzGbMmWbMZmbMM2bMAGaZ/2aZ
605
            zGaZmWaZZmaZM2aZAGZm/2ZmzGZmmWZmZmZmM2ZmAGYz/2YzzGYzmWYzZmYzM2YzAGYA/2YA
606
            zGYAmWYAZmYAM2YAADP//zP/zDP/mTP/ZjP/MzP/ADPM/zPMzDPMmTPMZjPMMzPMADOZ/zOZ
607
            zDOZmTOZZjOZMzOZADNm/zNmzDNmmTNmZjNmMzNmADMz/zMzzDMzmTMzZjMzMzMzADMA/zMA
608
            zDMAmTMAZjMAMzMAAAD//wD/zAD/mQD/ZgD/MwD/AADM/wDMzADMmQDMZgDMMwDMAACZ/wCZ
609
            zACZmQCZZgCZMwCZAABm/wBmzABmmQBmZgBmMwBmAAAz/wAzzAAzmQAzZgAzMwAzAAAA/wAA
610
            zAAAmQAAZgAAM+4AAN0AALsAAKoAAIgAAHcAAFUAAEQAACIAABEAAADuAADdAAC7AACqAACI
611
            AAB3AABVAABEAAAiAAARAAAA7gAA3QAAuwAAqgAAiAAAdwAAVQAARAAAIgAAEe7u7t3d3bu7
612
            u6qqqoiIiHd3d1VVVURERCIiIhEREQAAACH5BAEAAAAALAAAAAAgABgAAAiNAAH8G0iwoMGD
613
            CAcKTMiw4UBwBPXVm0ixosWLFvVBHFjPoUeC9Tb+6/jRY0iQ/8iVbHiS40CVKxG2HEkQZsyC
614
            M0mmvGkw50uePUV2tEnOZkyfQA8iTYpTKNOgKJ+C3AhOp9SWVaVOfWj1KdauTL9q5UgVbFKs
615
            EjGqXVtP40NwcBnCjXtw7tx/C8cSBBAQADs=""" )
616
            
617
        images[ 'Switch' ] = PhotoImage( data=r"""
618
            R0lGODlhIAAYAPcAMf//////zP//mf//Zv//M///AP/M///MzP/Mmf/MZv/MM//MAP+Z//+Z
619
            zP+Zmf+ZZv+ZM/+ZAP9m//9mzP9mmf9mZv9mM/9mAP8z//8zzP8zmf8zZv8zM/8zAP8A//8A
620
            zP8Amf8AZv8AM/8AAMz//8z/zMz/mcz/Zsz/M8z/AMzM/8zMzMzMmczMZszMM8zMAMyZ/8yZ
621
            zMyZmcyZZsyZM8yZAMxm/8xmzMxmmcxmZsxmM8xmAMwz/8wzzMwzmcwzZswzM8wzAMwA/8wA
622
            zMwAmcwAZswAM8wAAJn//5n/zJn/mZn/Zpn/M5n/AJnM/5nMzJnMmZnMZpnMM5nMAJmZ/5mZ
623
            zJmZmZmZZpmZM5mZAJlm/5lmzJlmmZlmZplmM5lmAJkz/5kzzJkzmZkzZpkzM5kzAJkA/5kA
624
            zJkAmZkAZpkAM5kAAGb//2b/zGb/mWb/Zmb/M2b/AGbM/2bMzGbMmWbMZmbMM2bMAGaZ/2aZ
625
            zGaZmWaZZmaZM2aZAGZm/2ZmzGZmmWZmZmZmM2ZmAGYz/2YzzGYzmWYzZmYzM2YzAGYA/2YA
626
            zGYAmWYAZmYAM2YAADP//zP/zDP/mTP/ZjP/MzP/ADPM/zPMzDPMmTPMZjPMMzPMADOZ/zOZ
627
            zDOZmTOZZjOZMzOZADNm/zNmzDNmmTNmZjNmMzNmADMz/zMzzDMzmTMzZjMzMzMzADMA/zMA
628
            zDMAmTMAZjMAMzMAAAD//wD/zAD/mQD/ZgD/MwD/AADM/wDMzADMmQDMZgDMMwDMAACZ/wCZ
629
            zACZmQCZZgCZMwCZAABm/wBmzABmmQBmZgBmMwBmAAAz/wAzzAAzmQAzZgAzMwAzAAAA/wAA
630
            zAAAmQAAZgAAM+4AAN0AALsAAKoAAIgAAHcAAFUAAEQAACIAABEAAADuAADdAAC7AACqAACI
631
            AAB3AABVAABEAAAiAAARAAAA7gAA3QAAuwAAqgAAiAAAdwAAVQAARAAAIgAAEe7u7t3d3bu7
632
            u6qqqoiIiHd3d1VVVURERCIiIhEREQAAACH5BAEAAAAALAAAAAAgABgAAAhwAAEIHEiwoMGD
633
            CBMqXMiwocOHECNKnEixosWB3zJq3Mixo0eNAL7xG0mypMmTKPl9Cznyn8uWL/m5/AeTpsyY
634
            I1eKlBnO5r+eLYHy9Ck0J8ubPmPOrMmUpM6UUKMa/Ui16saLWLNq3cq1q9evYB0GBAA7
635
            """ )
636
        
637
        images[ 'Link' ] = PhotoImage( data=r"""
638
            R0lGODlhFgAWAPcAMf//////zP//mf//Zv//M///AP/M///MzP/Mmf/MZv/MM//MAP+Z//+Z
639
            zP+Zmf+ZZv+ZM/+ZAP9m//9mzP9mmf9mZv9mM/9mAP8z//8zzP8zmf8zZv8zM/8zAP8A//8A
640
            zP8Amf8AZv8AM/8AAMz//8z/zMz/mcz/Zsz/M8z/AMzM/8zMzMzMmczMZszMM8zMAMyZ/8yZ
641
            zMyZmcyZZsyZM8yZAMxm/8xmzMxmmcxmZsxmM8xmAMwz/8wzzMwzmcwzZswzM8wzAMwA/8wA
642
            zMwAmcwAZswAM8wAAJn//5n/zJn/mZn/Zpn/M5n/AJnM/5nMzJnMmZnMZpnMM5nMAJmZ/5mZ
643
            zJmZmZmZZpmZM5mZAJlm/5lmzJlmmZlmZplmM5lmAJkz/5kzzJkzmZkzZpkzM5kzAJkA/5kA
644
            zJkAmZkAZpkAM5kAAGb//2b/zGb/mWb/Zmb/M2b/AGbM/2bMzGbMmWbMZmbMM2bMAGaZ/2aZ
645
            zGaZmWaZZmaZM2aZAGZm/2ZmzGZmmWZmZmZmM2ZmAGYz/2YzzGYzmWYzZmYzM2YzAGYA/2YA
646
            zGYAmWYAZmYAM2YAADP//zP/zDP/mTP/ZjP/MzP/ADPM/zPMzDPMmTPMZjPMMzPMADOZ/zOZ
647
            zDOZmTOZZjOZMzOZADNm/zNmzDNmmTNmZjNmMzNmADMz/zMzzDMzmTMzZjMzMzMzADMA/zMA
648
            zDMAmTMAZjMAMzMAAAD//wD/zAD/mQD/ZgD/MwD/AADM/wDMzADMmQDMZgDMMwDMAACZ/wCZ
649
            zACZmQCZZgCZMwCZAABm/wBmzABmmQBmZgBmMwBmAAAz/wAzzAAzmQAzZgAzMwAzAAAA/wAA
650
            zAAAmQAAZgAAM+4AAN0AALsAAKoAAIgAAHcAAFUAAEQAACIAABEAAADuAADdAAC7AACqAACI
651
            AAB3AABVAABEAAAiAAARAAAA7gAA3QAAuwAAqgAAiAAAdwAAVQAARAAAIgAAEe7u7t3d3bu7
652
            u6qqqoiIiHd3d1VVVURERCIiIhEREQAAACH5BAEAAAAALAAAAAAWABYAAAhIAAEIHEiwoEGB
653
            rhIeXEgwoUKGCx0+hGhQoiuKBy1irChxY0GNHgeCDAlgZEiTHlFuVImRJUWXEGEylBmxI8mS
654
            Nknm1Dnx5sCAADs=
655
            """ )
656
            
657
        return images
658
        
659 705
if __name__ == '__main__':
660 706
    setLogLevel( 'info' )
661 707
    app = MiniEdit()
662 708
    app.mainloop()
663 709

  
664
        
665 710

  
666
    
711

  
712

  

Also available in: Unified diff