 All right, well, let's get going. Today, as you know, we'll be talking about GUIs in Python. So let's get started. Why do we need GUIs? We'll introduce you to some of the GUI programming paradigms that Python exposes us to. In particular, TKInter and then traits, which is something that's come out of, I guess, the n-thought people. It's part of the n-thought distribution. And then we'll talk a little bit about Matplotlib widgets. These are all three very different paradigms for interacting with data. So what are GUIs? I'm sure all of you intuit this already, but just to get it down. Graphical user interface, it's a visual, as opposed to command line or text-based interaction with the underlying architecture of your computing platform. And we most naturally think of this in terms of windows, as it's presented to us in two dimensions. And then our interactions, obviously, are not always typing. The one that we're all used to is, obviously, mouse movements and mouse clicks. And then, of course, if we want to tack onto that gestures, haptic touch, et cetera, that is everything that isn't text-based. And the idea is, obviously, to give us sort of an intuitive connection to the data and allow us to interact, perhaps in real time, with our own sort of simulations, for instance. I'll sort of give you some examples on a second. It's important to recognize that GUI idea has been around actually since the 30s, before there were computers. There was a very well-known article, as I just learned recently, like the last few hours. This great article in Ars Technica, which itself is very old from 2005, has a really nice, very long description of the history of the GUI. There were essentially sci-fi writers talking about how to augment mankind's interactions with large data. And then that inspired this guy, Douglas Engelbart, to essentially spend a good part of the 60s trying to create that kind of interaction. And he did some famous demo on live TV of a mouse and a keyboard, and moving the mouse pointer around on a graphical depiction of the data. This was obviously before Xerox and the Palo Alto Research, et cetera, and certainly before Windows and certainly before Apple. So this idea has been around for a long time, just interacting with the data in a textual way, obviously isn't the most intuitive. And then sort of jumping ahead from where we are now, there's obviously sort of the interaction with data, let's see, is this gonna work if I click, interaction with data on our machine, not even with touch, but just moving our hands around in space. Somebody has hacked the Connect, Microsoft Connect, to be able to basically do what Tom Cruise did in the Minority Report to go through a file system, he's scrolling around just moving his hands in the air, grabbing a file, pulling it down, and then he's gonna be able to make that larger, turn it around, et cetera. So this is what you'll be doing in two or three years from now, and it'll be called I Something and Apple will try to patent it, even though we've been talking about it ahead of time. So this is considered prior art, I guess. You Apple people in the future listening to this lecture. But sort of more to where we are right now, it's very clear, of course, that you can get a tremendous amount of information about what your code is doing in just basically a graphical representation of all the different, in this case, inputs to a simulation, perhaps you wanna change some numbers and you wanna see what the output is, you wanna interact with your code and you don't wanna do it all on the command line, perhaps even you wanna rotate things around in three dimensions. All of this makes sense to us intuitively, so I don't really have to tell you the power of this, but just to show you that it's used fairly widely. Then in systems control and actually visualizing not sort of simulations of the world, but actual feedback from the world and actually seeing how something is working, say in the context of a factory, it's nice to be able to visualize all the different parts of your actual hardware workflow and be able to, in some cases, take action and set triggers and things in a graphical way. It's much easier and obviously much better to intuit it in a graphical sense. Astronomers are quite used to this. Some of the biggest telescopes in the world use GUIs to take data. This is a graphical representation of all the things that we have access to on the Keck Telescope, which is a 10-meter telescope, in Hawaii using an instrument called LRIS, and what the observer gets to do is play around with all these different buttons. We don't actually get to move telescopes around because telescope operators are put in place to stop us from breaking the telescope, but we do get to interact with the instrument, so we can change angles of different gradings, we can change the different split wavelengths in our dichroics, we can put in different masks, we can open and close the door to this instrument, and we can do all this in a scripted way from the command line, but again, as something is changing, you might have stuff blinking and it just gives you sort of that visual clue of what's happening with your real-world hardware. This was written in Tickle-TK, I think about 18 years ago. We're still using it today, so a good lesson that the code that you write today will not be used by minority report type people in the future, it will be used by real people. This is my first Tickle-TK GUI that I wrote for a camera that I built as part of my PhD thesis back in the 90s, and it allowed us to take data simultaneously on two different cameras called J-CAM-0 and J-CAM-1. J did not stand for Josh, by the way, stand for our donor that gave us money. And I could see when something was online, it was green and when it went off, it turned red, and so it allowed me to interact in a very complex way with this data-taking facility and then actually spit out sort of kind of responses and give us sort of real-time updates of what was going on. So this idea of GUIs for scientific programming is obviously very clear when you're trying to program whatever it is that you have in the sort of hardware side of things, and we talked just a little bit about simulations, but then of course, there's also the utility of GUIs in the context of teaching, which hopefully many of you will be doing. It's really great, you'll love it. So here is a JavaScript GUI that runs inside of a browser that allows you to change a couple of different properties of a binary orbit of two different stars, essentially dancing around each other, and you can essentially get a simulation. So I'll show you here of what we would wind up seeing on Earth in terms of different Doppler shifts from lines from those two different stars. So I can change the semi-major axis or the distance between those stars and make and update that change. I can change the eccentricity of the system, and I can update that change. In principle, you should be able to just slide these things back and forth and have those changes updated automatically, but it's pretty clear that interacting, now we know that there's physics happening under the hood, all I have to do is change these parameters instead of doing it on the command line, I can do it within a browser. Okay, so let's get into the anatomy of a GUI. So far, I haven't showed you anything that's Python-specific, although some of that stuff actually was written in Python, TK, Enter. The base construct of a GUI is the window, and we call that the root window, and so here we might want to call that root window for you to build a little calculator. Inside of that root window will be a bunch of different widgets. Some of those widgets may be display, so it's just showing you the value of what you just pressed, or they may be things like buttons, or maybe title bars. Everything within that window when you create it needs to basically get constructed and placed with respect to each other. They are instantiated, and they are associated with their parent window, so when I create a button I say this button has to belong to this window, and then I have to decide where I'm going to put that button with respect to everything else, and that's called a geometry manager. Okay, so let's see what happens and how I would actually lay this thing out. If you want to think about this conceptually, I've got sort of my display at the top, and that's this rectangle in purple, and then I've got all my sort of functions on the right-hand side, and then I've got my numbers on the left-hand side. If you want to break this down in the context of sort of high-level geometry, we really have three things, and inside of those three things, in the case of the one at the top, we just have one thing, and then inside of the one on the right, we've got eight things, and those are going to wind up being buttons, and all those buttons are going to have different meaning. When I click on one of those buttons with a mouse, or I bind perhaps a key press event to those specific buttons, then that's going to do something, and in this case, we intuit what that's going to be, you're basically going to be essentially doing math functions. So there are different notions of how you pack these different pieces of the puzzle into the overall window, and we'll go over how that happens within Python, but this sort of concept of a geometry is important, and there's different ways to do it. There's packing, gritting, and placing. Okay, so what do we have in Python? Well, the thing that's built into Python thankfully is something called TKInter. TKInter is a Python binding that actually wraps around an interpreter of the TK GUI APIs. That interpreter is sometimes called TCL, so you can essentially start a TCL interpreter just like you could a Python interpreter from the command line. I think the one that you probably all have on your laptop is called Wish, and then you can start typing TCL and TK commands. What Python does through TKInter, it exposes to you essentially TCL, but you're not actually programming directly in TCL. You're interacting through TCL to the TK toolkit. If that's a little confusing, hopefully it'll become more clear as time goes on. It's ported to all the platforms that Python runs on, so in particular, Windows, which is sometimes hard to get third-party window manager going, TKInter is the GUI of choice. There's also WX Python, which is an object-oriented wrapper around a set of APIs called WX Widgets. A lot of people use this. There's a lot of code base on this, but it is not built in. I think it comes with the end-thought distribution if I'm not mistaken. But if you're wondering about portability issues, WX Python may be a little bit worrisome. Then this thing called Traits, which is something that came out of the end-thought people themselves, is a different GUI paradigm, although itself isn't actually about GUIs. It's about creating objects and instantiating objects that have connections between each other, where it allows you essentially to strongly type Python. As many of you know, until you have decorators all over the place in your code that says that this variable has to be of type string, you can just keep on declaring variables and changing variables from strings to floats to whatever else you want. Traits allows you to basically declare that this variable is going to be a string, and it allows you to validate that, and then allows you to create connections between those variables, and then allows you to build essentially trivially GUIs around those complex variable objects. We'll see that towards the end of the class after the breakout session. But there's many more. There's the Python hooks to QuickTime, there's Python hooks to the GTK. If you are comfortable already with building GUIs because you've been doing it as part of your own work in one of these other platforms by all means, just use the Python hooks to that, and you'll be just fine. I don't want to overly proselytize TKInter or traits, which is what we'll focus on today, but do recognize and you can go to this FAQ that there are plenty of different paradigms out there. All of those paradigms have sort of four basics surrounding them when we're thinking about how to do good GUI programming. How many people have done GUI programming before? That's more than I thought, that's good. So I'm preaching to the choir. You know that you have to specify how the user interface is going to look, you have to specify what that user interface is going to do, and you have to connect the looking and the doing, and then finally you have to sit around and you have to wait for things to happen. So let's do our first GUI in Python. So from the command line, if you would, you can type from TKInter, it's a capital T import star that just puts everything into our namespace. As you know, that's not great coding style, but we're going to do it anyway. If you want, you can from the command line, type Python ghello.py, we're not going to be doing anything in the notebook today because we're explicitly working outside of the notebook. We're going to create standalone windows that are not part of the notebook. We then want to create a root window, and we do that just by calling TK, and then we're going to add a title to that root, and we're going to say, hello, question mark. Inside of that, we're going to make a label, and then we're going to pack it into that root. So the parlance for this is capital label, and then we have to give it the parent window, and that parent window is root, and we're going to give it some text. Is it me you're looking for? Then we're going to pack that, and we'll see more about the geometry later, but effectively we're packing that inside of the window, the root window, and then we have to actually make it happen and start waiting for something to happen. You do this by starting a main loop within that window, and so that loop is basically just an event loop that's just waiting for something to happen associated with that window. So when you do that and you run that from the command line, you should get something that pops up, and it's got the right title, and it's got the right text in there, and depending upon which platform you're on, you should see this stylized to the platform that you're used to. So if you're on Windows, you should see the normal Windows buttons, Mac, etc. as you can see here, and the way to open things up in Mac as you grab this corner. We haven't told the operating system to do any of that, and in fact, if you took this code as effectively as what's happening right now, you're taking this and putting this on different platforms with different window managers, you're going to get a different look and feel. But effectively what you're doing is you're saying to the window manager, make for me what you think is a window and put inside of it something that has a label. So this label doesn't have anything associated with it. I can't click on it. It's just a widget called label, which is basically for displaying text, and the window manager sort of obliges for you. Now you can tell it where you'd like to have it on the screen. There are lots of different things you can say with the total size of this window, you'd like it to be what the different laws should be for when I minimize it and all this. You can set all those things, but for the basic kind of hello world, this is essentially as basic as it gets. Instead of doing hello world, you see that we're invoking the spirit of Lionel Richie here. Lionel Richie needs to be part of, who codes to Lionel Richie by the way? Yeah, zero hands. That's what I thought. Good. Maybe do more coding to him. All right. So what is this event loop manager actually doing when you're actually saying, as we said before, main loop, you're starting something running. That is in charge of making the windows, resizing them, etc. It looks over the geometry and tries to obey all the rules of how you packed your different widgets inside of your window, and it's the thing that's watching over like this old lady looking outside of her window and yelling at the crazy kids downstairs when they're doing stuff they shouldn't be doing. It watches events and takes actions as appropriate, perhaps calls the police or throws their water bottle at them. So we'll do another one which is similar, and instead of just doing labels, we'll also create some buttons and we'll set actions to those buttons. Yes. So when you do it, well, how are you killing it? You're essentially destroying that route, and I think what happens for you with TK Inter is it winds up cleaning up that route. If you did root question mark, does it even know that that variable exists anymore? That behavior may not be consistent across platforms. If you say root.mainloop parentheses, that doesn't pop it back up. So it must have some understanding of having killed it and then basically not allowing you to put it back up. I'm a little bit surprised about that, and I don't think that's true across all platforms. In general, you're not usually killing windows and then jumping out of the main loop. You could have multiple windows around and then you can essentially destroy, as it's called, one of the windows, but you don't kill the main loop. When you exit out of the main loop, you're essentially saying, I'm done with this whole thing, and there's probably a way to get that window back up if you really needed to. That's not the general workflow though. So if we just do all the same stuff we did before, but now we're actually going to make some buttons. First, I'm going to define two simple functions, which when called good, it will print on the command line good. When called bad, it'll print bad. Now I'm going to create two buttons, one I'll call B1, and that's again associated with the root window, and it's going to have a text that says yes, and it's going to be associated with a command which is basically a callback function called good. So when you click on yes, it's going to print good. Likewise, when you click on no, it will print bad. Hello, is it me you're looking for? Yes or no? Now I have to stick those buttons in, so I can create those buttons and they don't actually show up in my window. I actually have to now put them somewhere, and so I'm going to use the geometry packer, and I'm going to put this onto the left side, and I want to expand it out as much as I possibly can, and I'm going to fill it in the x direction. We'll see all these things later on. The sense in these geometry managers is that x is moving horizontally across the screen and y is moving vertically across the screen. Likewise, I'll do the same thing for the second button. That function, so the way that we call this button thing, the way we create this button, and we can do this after the fact, by the way. We can create a button, and then just like we wind up sort of decorating objects within Matplotlib where we say, oh, I've got, now I want to add a title to this thing, or now I want to change the color of this line, you can do all this stuff after the fact, but we're doing it right as we create it. Basically what you're doing is you're changing different attributes of this object called button. And again, Python is nice because it's making this look object oriented to you, even though TCL is not object oriented. Yeah, so then you pack it in, and then you run your main loop, and then you basically will click on yes or no, and depending upon what you do, you'll wind up printing out good or bad. So why don't you give that a try and see if that works for you. I wanted to note that I have the end thought distribution that came out basically at the beginning, right at the beginning of the semester, and there's some issues sometimes when you're running these event loops within TKinter, that you get some weird statement about an error of not being able to get the right connection. Those are harmless errors, and I think that's been recently fixed. That's something that happened on Macs where it went from using, I think it was COCA to, what's the other window manager they use? Something else. What? Yeah, whatever. Aqua, aqua to coca, I don't know, coca to aqua. So don't worry about it if that pops up for you. Do you want me to run it here or are you guys okay demoing it for yourself? You good, okay? So you're clicking that button. Again, you're only sort of doing the very high level stuff, but TKinter is dealing with all of the even look and feel. Press your mouse button on the yes button and see what happens to the highlighting around the button. Right, you're seeing it's interacting, and you're interacting with it the way that you normally would. Okay, so let's talk about window, sorry, geometry managers. This geometry manager, basically the way we think about geometry managers in the context of a grid is we think about placing our widgets inside of a grid. And the grid is not something you pre-define. The grid basically is a concept that winds up expanding as you wind up adding more and more widgets. So when you first get going, you can say, put this widget at position zero, zero. And then forever more, you're gonna put stuff at one, zero, zero, one, whatever. You're gonna essentially address this grid like it was like an array. It will, that original widget that you had at zero, zero is gonna wind up staying there. But if I wind up creating something at 10, 10, and then I wind up starting my main loop, they're gonna not wind up being very far apart from each other. You're gonna wind up realizing that there's nothing in between. And this geometry manager is gonna just put them together because it's gonna say, oh, that's really what you meant to do. You wanna have all your widgets closed together. Now, you don't have to have it that way. You could create essentially empty labels that have a certain screen size and put those in between your two widgets that you set up. But when you first get going, and the idea with a grid manager is that you really know where you want everything to be with respect to each other. And you also sort of wanna know the relative sizes of these things. And we're doing this in abstract sense. A could be a label, it could be a button. It could be some text entry. It could be a checkbox. It could be all these concepts of the types of things you would do inside of a GUI. But for now, we're just keeping it abstract. Rows go down when we index it from the top left at zero, zero. So if I wanna put something in at A, that's gonna be at row zero, column zero. If I wanna put something at D, that's going to start at the top left corner. So I'm gonna index that at row zero, column three. But I can say I want that to span two columns and I want it to span two rows. So the X and Y sizes in this abstract sense are equal. And it's only when you start your main loop that it will pack everything together. So if I don't declare B, C, F, G, and I, but I declare A, E, H, and I use my grid manager to do that, and D, J, K, it'll just put all that stuff together as if this stuff in the middle didn't matter. So empty cells are ignored. There's no fixed grid size. That's essentially what the grid manager will work out for you and will be kind of your liaison to the underlying, the underlying window manager will say, I'd really like it because this was requested from the person that wrote this code for this whole root window to be of this size. And I really would like for this label to be of this size and say, you know, row zero, column zero. And it will try to faithfully deal with that for you. The thing to recognize is that when you're doing, when you're doing gridding, this widget, which again will do it in an abstract sense, will have some natural size inside of that grid. Now, the default is for that widget to essentially float at the center of that cell. But if I want to, I can stick that widget onto different parts of that cell. So as I wind up increasing my overall GUI size, my overall root window, I can basically say, consider the left side and the top side, the west or the north. So this is also kind of weird. X is increasing towards the east and Y is increasing towards the, Y is increasing towards the south. Anyway, so I declare this thing with, I want it to be sticky in the north and the west. Is that clear? So when I expand that out, so I could expand, if this was just basically one widget inside of one root window, that's sort of the simplest version of this, but it's the same sort of meaning when you're part of a big grid. When I expand that out, it's going to keep this thing stuck here and that thing stuck over there. If I stick on the east and the west and I wind up expanding my widget out, I wind up doing what you would naturally think where you wind up pulling this thing apart and stretching it apart. Maybe this is where GUI comes from. We can also set sort of the sense of how much weight these different cells will have when we're doing gridding so that when I expand and contract my entire root window, it's going to give more weight to different cells so we'll try more faithfully to keep those cells visible to you. If you give very low weight to some of the cells in your grid and then you start closing your window and making it smaller and smaller, the ones that have low weight will preferentially get removed and the ones that have high weight will wind up being shown. So if you're building one of these huge simulations and you wind up scrunching it down, you can actually set the minimum size of the root window. You could also set the maximum size of the root window. But as you get to your minimum size, perhaps nothing becomes as important as that one button that says run, right? Or maybe it's also showing you the results of the simulation. So you have to think about that if you really care about this stuff in detail. So if you run a file for the command line pythongrid.py, it should pop up something like this and you can see the different ways in which I've stylized each one of these labels. Some of these things are sunken. Some of those I don't actually stylize the outside of those. And you can just play around with shrinking, opening and closing these various things. And even if you want to, setting different weights to these different cells. This is just to give you a sense of what the grid manager is doing to give you a sense of what the stickies mean. You can see right here that some of these things are sticky in the east-west direction. Some of these things are gonna be sticky all the way around like the D. And the F and the G looks like F is sticky on the east direction and G is sticky on the west direction. And by doing that and putting them into adjacent columns in the same row, then I wind up basically having those two things stuck together. Shall I try that? You guys wanna run that too? Let's see here. Oh, they popped it up over here. Okay, so I can shrink this out. And you see that for the way that this manager is dealing with the different weights is it's putting, and there's that error, it's putting more weight on D, J and K. But it's more or less doing what you think it should be doing. If I really wanted I to be a fixed here, I could do that as well. But let's, it's not there, it's happening with the main loop. You can see basically how we do some colorization with this background. We can set different borders. You can set different reliefs as they're called. So ridges and grooves and sunken. You can put padding around them in the X direction. So you can have essentially a set amount of space and yet still do the sticky thing. So here is where I've done the gridding. So I've said, okay, I've got a label called A. Let's grid that and put that into column zero, row zero, and you set the different stickies and then you get what we got. And here I'm setting the different weights of the columns. Okay, any questions about that? I mean, this is really getting into the look and feel of your interaction with your GUI, but the GUI really is about stylization. So it's worth spending a little bit of time on this and understanding how this all works. Okay, so there's another geometry manager, which is not called grid, it's called PAC. And PAC is a little conceptually harder. The idea is that when you do a PAC, the ordering matters. So by the way, when I do that gridding, I could have gridded G before I gridded A and as long as I had the same calls, I would get exactly the same results. If I wind up packing, the order in which I wind up packing my widgets into my root window actually matter. So here you don't talk about east, west, north and south. You talk about sides. You talk about whether you're gonna pack this into the top or the bottom. So the way to think about it is just, we'll just go through an example of packing a bunch of different widgets inside of my window. I'll pack something to the top. So that's basically saying, push this thing as far up to the top as you can within this window. Do another one, let's put another widget in and pack it as far up as you can to the top. And now I'll put another one in, so maybe I wanna have some sort of display and some sort of button and then at the very bottom, no matter what happens, I wanna have a quit button. There I might wanna say, pack it into the bottom. And when you do that, it will fill as faithfully as it can into the available space and now I have all this space. And now if I pack something to the left, it's going to do the best it can to basically put it over there. And as I change the sizes of the windows, these things are all gonna basically keep the same sense, but obviously the overall sizes of the actual widgets will wind up changing. So let's play a little bit around with the packing manager. So we'll create a button, which will be called quit. And you notice I haven't given a command. I could actually destroy the root window, that's one command, but I'm not gonna actually have this do anything. It can still be a button that does nothing and still stylized and still has text associated with it. And it's gonna be color or peach puff. I don't know why people use peach puff. Anyway, so then I'm gonna create something new, a new type of widget, which I haven't shown you yet. This is a check button. And it will also be associated with the root window. We'll do three of those things, A, B, and C. And then we'll create a label called what grade should I give you in this class? Hitting a little bit at home here. So and then I will wind up packing first the label and I'm gonna stick it to the top and I wanna make sure that it fills out in the X direction. So I've basically just completely grabbed all of the X, everything in the X direction. I'll share the implications of that in a minute. Okay, and now I want to pack something on the bottom and that's the quit button. And I'm gonna have that fill all the way out to the bottom. And then I'm gonna expand this thing out. What is it, A on the left? I wanna stick that on the left. And then likewise we'll do A, B, and C. And then we wind up getting something that looks like this. I think there must be, I know I'm missing some code because I didn't actually pack B and C in there. And I didn't show you that I'm running the event loop. But I think in pack.py we can take a look at that. That should be there. So there's pack.py. So we created a root and put a title on it. Yeah, and indeed, I didn't show you everything. There's a B and the C. And I kept, you notice I kept putting those other buttons or those other check buttons just onto the left. So the way in which we think about what's happening in the window, in the geometry manager sense. I don't think I have any chalk. So I'll just have to describe it. Ah, here we go, okay. So what I did at the start, so here's my root window. What I did is I put something at the top. And if I had said, put something onto, pack something onto the left. In principle, I could have taken some of this real estate here. But I said, I don't really want to have that happen. I want you to expand this out in the X direction. So I really grabbed everything at the top. And now if I had packed something on the left, it's gonna stick it over here or pack it on the right. But I did the same thing with the button here. This is the quit button. And then I said, oh, well, I don't really care exactly where all this stuff goes in the middle. I'll stick the A here, the B here, and the C here. And then I said, you do your thing and you decide where you want to put everything. And this expand equals one, which should actually be expand equals true. That's deciding if there's no, if nothing else is taken up when I actually say run event loop, what's the behavior in terms of filling up stuff at the end? And so it's doing what we think it should be doing. Okay, so here's a tiny exercise for you to do is take that pack.py code and change the name of it. And instead of packing things in, you can use exactly the same check buttons, but use the grid manager to get the same sort of look and feel. I'll give you a couple of minutes for that. So you're gonna say, button name dot grid, and then you're gonna have to give it some attributes. You kind of feel like we need some music to code with. This is relaxed coding. Peach Puff music, exactly, exactly. Do you have an app for that? Code that up using tkinter. The quicker you finish, the quicker we end this music. If you have questions, just shout them out. You should be approximately the same. You have to worry about your stickies and your column spans. Who's got it? Okay, I'll wait a minute. Yeah, work on the stickies. Well, first of all, in a grid sense, how many rows and how many columns should I have just by looking at this visually? Three by three, that's right. Okay, so anyone wanna talk about how they did their gridding? Well, where should I put that label E? What grade should I give you in the class? I put it at zero zero, and what else do I wind up saying about it? Yep, column span of three, sticky on, what did you say? Northeast and West. You could also do South too, if you wanted to basically really stick at the top and have this other stuff stick to it. That's getting into the subtlety of it, but certainly East and West and probably North. The one on the South, that's up to you. Where am I putting quit? That's row what? Row two column, also with a column span of three, good. And probably there you wanna do East, South and West for your sticky. And then it looks like A, B and C wanna be put in row one. And I wanna stick that at zero, so row one column zero, that's for A, B will be row one column one, et cetera. And probably the stickies I wanna have just on the North, or if I really wanted to, we could have them on East and West and also North. And don't worry so much about this white space, we got this from the packing where we packed these two up to the top and then we packed the quit button down there. If you really wanted to have this extra space and you wanted to ensure that you had extra space, you could create a label that was in the, I guess you'd have to move this down to the fourth row and you'd have to put something in the third row and have that have a column span and essentially be empty if you wanted to essentially ensure that space. Yeah, that's right, you would do that because you'd have empty stuff and then it would just essentially ignore those when it actually did the gritting. But that's a very, very important thing that we just hit upon, I wasn't planning on doing that. What happens when you decide, I really don't like where that widget is, I wanna add something, I wanna put it over there. The problem with the grid manager is that if you haven't laid everything out already and know exactly what it's gonna look like, chances are you're gonna have to go through and you're gonna actually have to change manually which row and which column something goes in. So now if I decide I really wanna put something in here that says D or no pass, I'd have to actually physically go and change this quit to be on the fourth row and then I'd have to add stuff to this third row where quit used to be. So this makes the iteration, the creation of the GUI a little bit painful but if you know exactly what it's gonna look like already, gritting is a pretty nice way of basically just ensuring that's where it goes. And then place, which we won't have time to talk about is a third geometry manager that has a very different paradigm. You're there essentially putting things in an X, Y location sense inside of your root window and you say put it at essentially pixel number this and you could have things like overlapping each other and all that. That's not very often used in TK Enter. So we haven't really done anything interesting yet other than just understand the layouts. I've introduced you to the check buttons. I've introduced you to buttons and labels but now it's interesting to start seeing what we can do. We've already seen actions that a widget can take in one, I think the only example I've shown so far is where we just print something out but obviously you could click on a button and the action could be go off and run a simulation that runs in a multi-core parallel environment and come back 10 days later and get some result. Doesn't have to be something that's trivial. It can be anything that Python can do that's considered a function. We can also like when we did the brushing example for your homework, when we did the Matplotlib week, you can also bind different events inside of different widgets or inside of even different root windows. So when I click the left button, I can catch that and I can take action when I do that or if I wanna bind another type of thing, like I wanna bind a key press, I can do that as well. There are other things that I can do. I can make sure that when a variable is changed, a certain special type of variable that I actually wind up defining. I can have a string variable, a boolean, a double, an int. I can say var equals string var and this comes from tkinter namespace and then I can set it to be hello and then I can set different types of callbacks on when that variable winds up changing in whatever way and by who. We'll see more of that in just a little bit. So let's do a watching variables example because now we start to get into some of the interesting sort of interconnected functionality of GUI programming. So we'll create a root window, we'll create a new variable which will be a string type variable and we'll create an entry and we're gonna create a new attribute of that entry. So this is a new type of widget, not a button, not a label, but something called entry and this is essentially a text entry and the variable will be declared to be my var so it's going to be of type string. I'm gonna pack that into the root and I'm now going to create a function which will say what the new variable is and this new variable, actually all these variables declared in this way will have various different methods. One will be set, the set method and the other will be get so I can get the value of that variable and then for us to do this we have to create a callback. So the callback function is named changing and I'm going to set a trace on that variable and when it's written, when it's changed because somebody's writing to it I want to essentially issue that callback and I'm going to print the name of that variable and inside of that variable, inside of that function, excuse me, the callback function I'm gonna print the value. So we can try this, it's events0.py of course I put it up on my other window here. So I set it to be by. Now you notice nothing happened when I just clicked inside of this but if I delete this I'm changing the value of that variable and now I can say hello. Every time I change it, that variable's being watched I call that callback function called changing and you notice I'm not violating anything by essentially typing it in a float. It can be anything at once because I'm saving it as a string. Now I think if I declared that as an int var and I started typing in letters I think basically the variable would get set to nothing. I don't think you actually throw an error but that's something you could wind up checking. Any questions about that? For some reason it's not getting me out of the main loop so I'll just delete. So that's interesting, it wasn't catching a keyboard interrupt inside of the main loop. You can actually bind a keyboard interrupt inside of the main loop if you want to take different actions like perhaps gracefully shut down or send you an email. So let's bind some events, something bind.py. So we'll create a callback and we have a little dictionary here and we're going to wind up basically getting a type of event. It'll become more clear when I actually show you what we're doing. Okay, so forget about the callback for just a second. I create a root window with just this TK and then creating something called a canvas which is yet another type of widget and a canvas does what you think a canvas should do. You should be able to paint inside of that and you could show text inside of it if you really want to. Usually what you wind up doing inside of a canvas is you wind up showing like an image or a movie inside of a canvas. I'll pack that in, I'll make sure that the focus of the mouse is set on that canvas. So that's what this focus underscore set does. And then I will bind a bunch of different callback functions to different types of events. So when I do a left mouse click, that's called the button one event. When I click enter, that'll be a different one. When I press a key, so that's anything that isn't an enter, isn't a button click, I'll wind up binding that. And you see what I'm doing, I'm actually just calling the same callback for all of them and then I'm taking different actions depending upon what type of event that was. So when we create an event, you basically pass a bunch of attributes to the callback function. You actually just pass a pointer to the instance of that event. And that event has attributes like type and you can look up in the manual pages what a button press is of type four and enter is of type seven, a key is of type two. So it'll have some value associated with that. And then it's going to print out the location in the canvas in X and Y of where that happened. It's going to print out a character associated with that, a symbol associated with that and it'll actually tell me what the button number is. So let's, you can just try that out. You can try that at home too. So it's bind.py. So here's my canvas and I'm moving around inside of here. I'm not doing anything and then I'll click and you see I got button number. So that's button one. I think if I right click, I don't actually capture that. If I press the V button and I'm staying at the same location, then I wind up capturing that. If I move my mouse up to this corner over here, you can see that it's zero index from the top. I'll press the B button and I'm getting a key event down here. Now we'll press enter, right? So you can capture all this stuff inside of a canvas. If you want to, you could capture this inside of other types of widgets. It's most natural to capture these types of things inside of a canvas. But of course, if I click on a button, you're already going to take action on the clicking of that button. You usually wouldn't want to capture mouse movements, say around a button, but if you really wanted to, you could do something like that too. So this is nice. Like if I wanted to draw something now, you essentially could create very easily a draw function because I could just draw lines as this person is sort of dropping the mouse down, pressing the button, letting go. I could bind essentially a button one unclick and then you could just draw lines on the canvas. All right, so let's do something, let's see, we did binding events. Now let's actually do something slightly interesting where we're using a GUI. We'll watch a string variable and we're going to wait for that string variable to change. But what we're going to do is a quote of the day generator and I'll sort of show you what the code looks like and then we'll talk about what's actually happening. And this is only the code snippet for this file called events.py. When I say show quote, that is basically going to wind up being a callback from a button which we'll actually stylize and we'll say show quote. First of all, what it does is it's going to configure the button so that it is disabled so that I can't keep on clicking that button. What I'm then going to do is call something called update idle tasks. An update idle task basically says, if you've got a queue of things that you need to do and show the user, let's say I want to change the color of a label and I want to change it from peach puff to orange puff to yellow puff. If I do this line after line, eventually the window manager is going to say, oh, what's the last thing you actually wanted to do where I'm just sitting around waiting for something to happen? Oh, that was the yellow puff, I'll just show it that way. But if I really wanted to force it to update that screen, effectively this is the equivalent in Matplotlib of a draw where you're saying, draw what you have sort of remaining to do in your queue. In this case, I want to disable a button so that no one can press it again. But I don't want to wait for a long time. I want, as soon as somebody gets into the show quote, I want to disable that button and I want it to actually take action. I want to actually be disabled. I don't want anyone to be able to just keep on pressing a button. Then what I'm going to do is set the string variable to be whatever comes back from this call to urlllib and this is a url called ihartquotes.com and you can just get random quotes of the day and you can do this from the command line with a W get if you want to, we're going to do it inside of our little GUI. And then when it's done, we want to set this button back to being normal and then I'm going to update everything that's on the screen, that's inside of my window. So not just the button, but now I want to update what I'm going to wind up showing you in the center of this GUI. So the GUI is going to look like this. When I click on get quote, I want to gray that out so that I can't take action anymore. And you also see that we'll actually give some meaning to what this quit thing is. But I'm going to wind up watching this QT so it actually can happen asynchronously. Until QT changes, until I get an update, that's going to keep on having the same value as it had before. So when I click get quote, it doesn't erase QT, it doesn't erase that string on the screen, it will just wait until a new thing comes back. That value will change and then I'm going to say root update is going to change everything here. So let's give that a try. It was called events.py, right? Okay, so when I start off, that value of QT is just going to be click get quote. So let's try that. Ooh, that's a terrible one, let's change that. Okay, this is at least about code. Fortran will persist for some time probably for at least the next decade, that was probably said in the 60s. So you can see that we're updating what it is. We're essentially printing that out to the command line. Ooh, that was very poorly stylized. Probably need to make- It says what? It's about to disable the button. You want it to disable like it's supposed to be. Are you clicking it? No, if you just keep on clicking it, it's without you actually seeing it becoming undisabled, it's actually becoming enabled and it's essentially queuing up your clicks. But it's not running these things in succession. It is actually waiting until it becomes disabled. And you could set something where you wait for 10 seconds if you really wanted to before it becomes enabled again. I have no control over what is said here. That should be the disclaimer. Okay, so are there any questions about what's actually happening here? Does everyone see what we're doing? We're basically watching a string, we're changing that string and we're basically doing what you think should be happening instead of a GUI. We're allowing this thing to be enabled and disabled as that string is essentially being queried off of the web. Okay, so here's your breakout question. Create an instant calculator, sort of like what you get now from the Google search window where you're going to basically start typing and as soon as you type something that has meaning in a sort of evaluation sense from a math sense, you're going to wind up printing out the results. So here I start typing P and I got nothing up here and then as soon as I type Pi, this should appear instantly. And then if I keep on typing, I get nothing until I wind up getting a meaningful expression. So every time you change that variable, you're going to do an evaluation of that string. So you're going to watch that variable and just so you don't do anything malicious to yourself or to others, you can create a safe evaluation expression where you do an evaluation of whatever that string is, but you essentially turn off all the possible Python things you can do like OS system, RM star dot star and all that. And the only things that are available then are the variables associated with the math module. Okay, break out. Did you get a funny quote of the day? Yeah, I gotta be a little careful. So I think we'll get going. I think most of you got it already. If you wanna see how to do this in TKInter, I can show that later, but I wanna get moving on to traits and I'll actually show you this solution as implemented with sort of the traits paradigm. But effectively, I think most of you got it just so what you wanna do is you obviously wanna watch a variable associated with this text entry. So you're gonna define that as some string. You do an evaluation and you can wrap that in a tri-accept. Obviously if you have an error then you don't wanna do anything, but if you don't have an error, then you probably wanna update this text value associated with the label and there as I was saying before, you would instantiate that label and maybe call it L or my label and then you'd say text equals whatever you want it to say when you start and then inside of your callback function because you're now watching the string associated with this entry, you would do the evaluation and then you would say label name.configure text equals and that allows you to change the value of it. Okay, so I wanna move on to traits. As I said at the beginning, this is a project that came out of end thought and what traits allows you to do is sort of think about your data in an object-oriented way of course and now start creating sort of a complex set of variables that may all be related to each other. So now this is really thinking about object-oriented programming well and now GUI programming just sort of comes for free. Once I know the relationship between different types of objects and I've sort of made sure that every variable is used in the proper way that it should, then the creation of a GUI is nothing more than essentially creating a view of that object and instead of doing it on the command line you wind up doing it basically inside of a window. So traits adheres to this so-called MVC paradigm model view controller paradigm and it's basically what I just said. The model is what manages the data, the state and the internal logic of the application, the view formats the model into a graphical display that the user is allowed to interact with and the controller is really sort of managing the interaction between those two. It's sort of letting the user interact with the view of the data and then it's taking action inside of that model. That's a little abstract, we'll see how that actually works in just a bit. But the important thing is it allows you to think first about the data and then you start thinking about the visualization. So now you would think about your lambda expression and think about what is it that I'm really talking about? Well, I've got a label and I've got some entry and this is changing and this is gonna be of type string and I wanna now print that out. Now you start thinking about these things in terms of objects and all of that kind of event loop and lookup kind of happens for you in a nice elegant way. Some features of traits. One is initialization that all traits have default values. So here you would instantiate perhaps that label inside of the calculator and it would have a default starting value. Perhaps your calculator would have a default starting value. Validation, so every time you start typing inside of something you start changing a variable. It doesn't really take until you've got the right type. So if you declare this thing to be a float and then you type the letter A it's gonna say I don't know what you're doing here. Delegation, notification, stuff that we'll get into in just a little bit and then of course the visualization part of that. Okay, so this is thinking about modeling data in terms of classes fundamentally, okay? So if you run traits 1.py, so Python traits 1.py, you should get something that pops up that looks a lot like this. Here's the code. We're just pulling a bunch of stuff from the namespace and we're going to, sorry for those that hated our use of bears in the early days of this course and in the bootcamp but we're doing it again. So we're gonna create a class which will be a subclass of an object called has traits and then we're going to declare a bunch of different attributes of this bear. It will have a name and that will be of type string. So it forces us to have a type string. It will have a color associated with it. It will have a weight. It will have a location which is going to be an array type. There'll be a data directory associated with that bear perhaps maybe where its images live and now when I instantiate a bear, I can give it a name, I can give it a weight, I can give it a location and you notice upon instantiation I haven't told it what color it's going to be and where the data directory is gonna be associated with that object but it still winds up having meaning because I have the concept of a default. And then the way where instead of starting the event loop, what you say is configure traits and that's starting the event loop. It's connecting all the different types of classes together because as we'll wind up seeing they can be related to each other. So let's try that. Where's my mouse? It's called traits one, right? Of course, where did that throw it? Here it is. So there, I essentially created a GUI and I didn't say anything about how these GUIs were gonna look but pretty, you know, obviously I could wind up stylizing how this is gonna look but the nice thing is because all these have a sense of what type of object those are, what type of property color now has a GUI associated with it where I can see, I can set my bear to purple, I can open up a nice little representation of my directory tree and I can change that of course. Favorite food, I can enumerate the possibilities of what favorite food are allowed. You can eat a Palo Alto and I can change the name to whatever I want. I can change the weight, et cetera, okay? We haven't actually done anything with that but what you should recognize of course is that as I change it in the GUI I'm actually changing it inside of the instance of that object. And traits exposes to you a bunch of high level types and the color type is pretty cool because it knows how to stylize itself and when you have a GUI now I can just select essentially from a color wheel and I didn't actually have a color where I had a drop down menu. You could tell it when you show a GUI to me that's associated with this color show me now something that looks like a color wheel and it has a sense of what a color wheel is. So there's lots of different things in here. If you're gonna get into this you're gonna wind up getting familiar with all these different predefined types and of course you can define your own type as well. So we can do what we did before or something like our calculator where we'll do a very simple version of that where we have something called underscore variable underscore change that's a special method of one of these has traits objects. So now I'm gonna create a very simple counter it's gonna be a value and it's gonna have an integer and won't have any default it'll be a type integer and I'm going to have something called add one which will be a type button and a button has a couple different things you can do to it. The obvious one is when you click on it and traits way of saying click on it is fired. So if I say add one that's the name of my variable actually underscore add one underscore fired every time I click on add one it's going to do this. It's going to add to itself this value the value attribute it's gonna add one. I have to create a view. So when I actually show this thing I want it to do some things I wanna show I wanna show the value I wanna show a different item perhaps I wanna play around with the labeling so I actually don't wanna say that this thing here is called add one which is what it would say. So I can turn off the label for the add one item you'll play around with how to do all this stuff but it should be pretty clear what's happening. So now I'll just configure traits on that counter and it's gonna get going. So that's called button.py Here it is oops here it is. So add one add one I'm clicking. If I set this to be 12 I clicked. Oh and notice what happened when it was a string I actually got this thing to stylize to look red I didn't have to tell it to do that but if I have a plus b it's clearly unhappy with that and if I click on here it's just doing something to it it doesn't have any meaning when I say a plus b. Did it convert it to true? I don't think so. So that's a complex I could have declared this thing to be a complex variable but I declared it to be an int so it doesn't really have meaning. 1.0 that is not a type int so it's doing some sort of checking of type for me and that's something that we're not really used to in Python but it's doing all that stuff naturally for you. So if I say add one to that saying two what if I said 3.0 and I clicked add one to that four. So it's keeping a sense of that variable but it's pointing to you that it's not actually of type int it's of type float but it's doing what Python likes to do when you're going between floats and ints it does the best it can. Any questions about that? So you notice we're not explicitly watching a variable we're just saying that different types of things like a button have a concept of firing and that gives you your natural callback and notice I had to create a special view inside of that bear so that I didn't actually show you the label for the add underscore one button. Okay so let's do what we did in TKinter but let's do it inside of traits. So I'll create an expression which will be the thing inside of my box there that I'm gonna wind up typing in. I'm gonna have a result which I'm gonna have as default type an expression. When the expression changes I'm gonna be past the old value and a new value and so that's actually how that's called and I'm gonna try to evaluate the new one if not I'm gonna do nothing. I'm not gonna, well yeah I guess I'll show you that. It doesn't stylize very well because I didn't create a view and I didn't tell it how to behave. It's called iCalc so here it is here and I can type one plus one plus one j plus e and you notice I can't actually open this thing up I can't actually make it any bigger. Maybe I don't wanna say the word expression maybe I wanna change this around. What I have to do is create a different or a specialized view of how I want this to be exposed to me whenever I do a configure traits. But you see the functionality is there in just a few lines, fewer lines than you needed before. Okay so view item allows us to create a stylized view of the model and this is the visualization part. So here's this complicated view but it sort of makes sense. We've got some width, we decide it's resizable. It's got a little padding associated with it. I give it a result. I decide which order I want it to be in. I can create a title on there. I can say that the buttons are called okay. You know I can basically create whatever view I want and the cool thing is I can create multiple views so that I can have it in different contexts the same object being displayed in different ways. In this case I'm gonna configure traits with a certain view and that view is be called in this case view one. So we can try that. So that looks really nice. Okay, what happens when I click okay? Ah, it closed for me gracefully. Here's a demonstration of something called delegation which is part of traits. So here I have a simple version of my bear and it is inheriting everything that the has traits object knows about. So I'll give it a first name. I'll give it a wait, I'll give it a float. Of course I could define other methods on this bear just because it's of a subclass of this thing called has traits. If I wanted to we could do all the same stuff that we did when we were talking about the size of the bear population. But here I wind up having a couple of, for every instance I wind up having a couple of different attributes. And favorite food is gonna be an enumerated thing, honey, turkey, Palo Alto and in the default will be honey. And now I can create a subclass of the bear object and this will be apparent. So this will be the sort of sense of what a parent bear is. And I'm going to have a last name and when that last name is changed, so this is the equivalent of what we saw in TK Inter where we were watching a string and then we were setting a watch on that string. Now we're going to see what happens when we change it. The parent's last name changed to something and it used to be the other thing. And then I'm gonna push that out to the command line. And now I'm gonna create a concept of a a daughter or son, a cub. So that'll be a subclass of bear. And we'll have a last name. And here's what's interesting. I can now delegate the last name to whatever its father is. So if I declare a variable called father, that father better be of type bear and it will then grab the last name of the bear. So it's the equivalent of when we did the underscore underscore in it inside of a subclass and then you sort of run the underscore underscore in it of a base class and then you grabbed all the attributes from there. Effectively that's what we're doing except these things are intimately connected. So if I change one, I automatically update the other. There'll be a father and a mother and it'll be of instance of type parent. So I'll create Yogi, which will be of type parent. First name will be Yogi, a weight of something. Last name Morgan. Sally, last name is Clime. And then they're going to make a cub which will have the first name of Saki and Oskie, excuse me, I don't want to think of it. I really would like to have Saki when this is over. That is, yeah, maybe a Freudian slip, maybe. Anyway, so father is Yogi and mother is Sally. And so, oops, so all that stuff should make sense and we can try to run traits too and see what happens. Where is it? So it just basically dumped out because I didn't have inside of traits too this actual configure. So that's everything I showed you already, but I want to show Oskie and now I've got my edit properties. I didn't do any special view here so it's showing to me in any order I want. And look, father is of type father. If I click on that, I wind up getting the properties of the father and it's got honey so I can change that. And I can change the last name of the father to be my last name. And what you can see is that Oskie's last name is now Bloom because I changed the father's name and Oskie's last name has been delegated to whatever the father's name is. That's obviously a contrived example but now I can look at the mother and let me try to change that. Fernando's last name and you notice nothing changed. Oh, last name. Oh, I don't know why that changed to Perez. That shouldn't have done that. Oh no, it didn't change here. It was doing the callback but it didn't actually change the last name of Oskie. So that's that kind of intimate connection across classes. This is the sense of delegation and you now have the sense of inheritance being also something that you can play with at the GUI level and not just at the sort of command line level. Another thing you might wanna do, of course, is not just deal with numbers and deal with pointers to other classes of objects but maybe you actually wanna do something of interest where you're playing with data and you'd love to be able to stylize a plot and you'd like to play around with, say, the zoom of the plot and the color scheme and all the things that you would naturally wind up doing in Matplotlib. Unfortunately, it's a little bit contrived to be able to use Matplotlib within side of a traits GUI. There are ways to do it and on the next slide, I'll show you how to do it but it's still obviously very powerful if you're gonna be doing a very complicated traits model and then you wanna actually do something with Matplotlib at the same time, right? You'd like to be able to interact with those plots. So you have to import something called MPL Figure Editor which is part of traits and you have to create some sort of object which has as its base class that has traits object and you create something which is a variable of type instance and then you have to create a figure associated with that and then you have to create a special view of that figure and you have to give a sense in the view of what the editor is and that editor comes from your MPL Figure Editor and then you can run all that stuff. So that should actually wind up working and I think I have an example of that in the folder that I gave you but as you already know, Matplotlib and this is sort of the last third of what I was gonna tell you about today has its own widgets. So if you import matplotlib.widgets as WG and then you type WG.tab, you will see that there's a lot of the concepts of what we've talked about today already built inside of matplotlib and you can start essentially creating directly and natively within matplotlib the types of widgets that you might wanna be interacting with if indeed what you're looking at is playing with some sort of figure. So again, you can do it within traits and that's not unreasonable and then you can bring in all the functionality of matplotlibs and yet still use a nice traits view but matplotlib itself, as long as you don't need all the special things traits can do, can obviously be very powerful. So here you actually wanna be able to interact with say the amplitude and the frequency of the sine wave. Perhaps you wanna change the color of that and the widgets give you access to that. So let's take a look at the slider demo. So that's the sort of typical matplotlib markup that you see sort of gray backgrounds. You see when I move my cursor in here I get all the functionality. Now I can zoom in on this thing, can go back but interestingly, I can change the amplitude and the frequency. These red lines are the default so I can set the concept of a default and if I reset, it goes back to its original and change the line color, et cetera. And then you can obviously decide where you wanna stick all these different widgets. So this is pretty powerful if you're gonna be writing something that's gonna allow you to interact with your data in a way that we'd like to be interacting with our GUI data, right? It's like we'd like to be not just showing something, going on the command line, changing one value, seeing what happens. You'd like to be able to do this in quasi real time. So matplotlib is great if you're just dealing with data traits is sort of the next thing you would wanna think about if you're also gonna be dealing with lots of objects and connections between objects. And TKinter is really kind of a basic functionality of GUIs within Python. When you, I believe when you pull in that Canvas editor, it is doing the proper event handling for you but I don't know the details of how you get access to like a button press within it but I'm pretty sure you can declare traits, attributes inside of a class that are the results of having pressed a button. But whether you can say underscore button press underscore fired, I'm not exactly sure if that will work. That is what I'm talking about being able to do. I mean, you could really clue something if you wanted to and basically create that window and sort of figure out a way to coerce like the result of the creation of a GIF or PNG from having run matplotlib. And every time you press go, you fire matplotlib and then you're just showing the result of the PNG. That would be the clues you way to do it but to have it interact directly with it, you have to use that template that I showed you on the previous slide. I should also say about matplotlib is that there has been some work although I don't know how much in earnest and who's doing it to use not as the back engine, you're like coca or WX or AGG, your windows managers instead using as a back engine, HTML5. So that when you're actually interacting with matplotlib, you're doing it inside of a browser. And so you could be pressing buttons inside of a browser and that's been created for you, essentially using HTML5. That's been around since 0.99-ish. I don't know how far along that's getting. It's not been put in, but I think it even comes with the most recent end thought distribution. Well, maybe they ship it, they ship it. Yeah. But it is currently the description that you gave of the Fuji way getting into the thing. It fires up its own server, right? It's essentially creating a web server. That's right. It sends it over the events. It captures the events on the web browser and then sends them over, re-renders the whole SVG and sends it back to you. There's a work underway to make that more interactive. But right now it's that sort of the Fuji way. Like it's not this lightning fast. You'd think SVG is local to my browser. No, it only sends you a fully rendered SVG thing. And when you pan, it'll send you a full new rendered SVG thing right now. There's people working on something to move that like this. Cool. All right, so unless there's any other questions, I'll give you your homework. Okay, so your homework. I don't know what's a six. Should be eight. Well, anyway. Is it homework six? Really? Aren't we in week eight? Yes, but we start with a zero. We start with a zero and we have one. And then we come up with the same. Okay, homework six. Use traits and the traits UI to create a GUI application that consists of a search box, a text display, an image display and a series of buttons. The application will accept search strings in the search box and then run an internet image search. The first return image from the search will be downloaded. The image URL displayed in the text display field and the actual image will be displayed in the image display field. Buttons will provide the user the ability to basically essentially run the search but then do manipulations on the current image inside of the display like I could blur or rotate. And so what we're asking you to do is provide at least three unique and interesting image manipulation functions. So those of you that did some of this stuff in the XML RPC or obviously many of you who solved the or attempted to solve the image classification problem, you're already very familiar with different types of interesting things you can do on images. So you can use any image search you want. Yahoo has a pretty nice search module and you can decide how you wanna do display but effectively what we're looking for is a GUI that looks something like this where if I queried money Python and humor I'd wind up getting back some image. It will display that image and then I can blur it, sharpen it, smooth it, do edge, whatever manipulation on all this stuff. Yes. Like what? Sure. I'd like for you to get used to doing it with traits because I think that's a really nice model but if you're already used to using PyQT, that's fine. Yeah, yeah. Any other questions? Okay.