Application Technologies • 1:12:18
Walk through the steps for quickly and efficiently adding features to your Carbon application. Bring your laptop and get practical guidance from Apple engineers on integrating a Cocoa web view window into a Carbon application, writing your own custom HIView, and more.
Speakers: Deric Horn, Larry Coopet, Bryan Prusha, Curt Rothert
Unlisted on Apple Developer site
Transcript
This transcript was generated using Whisper, it has known transcription errors. We are working on an improved version.
Hello there. Welcome to Carbon Programming Hands-On. For those of you that don't know me, I'm Deric Horn, the Application Technologies Evangelist. That's really the Carbon Cocoa Evangelist. So today we're going to take kind of a tag team approach. We're going to start out by talking a little bit about the announcements made earlier this week.
We'll go into how important it is to make sure that your application is a modern Carbon application, what that means. And then we'll go into a demo that I put together for this, demonstrating a little bit about how to integrate a little bit of Cocoa into your Carbon application.
Then we'll have Larry Coope, one of our SWAT engineers, come up on board and he's going to go over some of the best practices in development, both for Xcode, cross-platform, owning your code. And then again we're going to have Curt Rothert and Bryan Prusha come up on stage, members of the High Level Toolbox team, and they're going to go over the hands-on portion of this on how to build your own custom HIView based on HIFramework. And if you've ever written your own custom HIView, and if you've used HIFramework, you know that that's really the way to go. It makes it much easier to do. And we'll try to leave plenty of time for Q&A afterwards.
So, as you can see, there's a huge number of revenue generating third party applications and also internal applications based on Carbon. Carbon is very critical to the success of the Macintosh platform. It's here to stay. It's not being deprecated. That being said, one critical step moving forward is better integration with Cocoa.
So some of the announcements made this week. Greater integration with Cocoa. We announced the ability to now embed NSViews inside of HIViews. And also we made the announcement where anywhere where we have an HIView that accepts a CG image, we're also going to make that able to accept an NS image. And likewise, going out where it exports the CG image, we'll be able to export an NS image.
Resolution Independence. I think Bertrand laid a stake in the ground saying that we expect all applications to be resolution independent by 2008, but that's really kind of a ramping time. You see that, like hard drives, we're making screens denser and denser pixel-- we're making the screens having higher and higher pixel density. Got that a little bit backwards.
But even today, I mean, I overheard some developers talking that when they go to Macworld right now they have these big 30-inch displays. And currently what they do is they lower the screen resolution so that they can display everything really large. And when I overheard them talking, they said maybe as an alternate approach, they'll just go ahead and they'll increase the scaling factor to like 3.0. And that will be able to take advantage of all the pixels on the screen.
So I think that you can kind of ramp that up so that any time between now and 2008. It's an important direction. The Carbon framework, along with Cocoa, are being updated to support resolution independence. Really what that means to you is that your Windows should now be composited Windows. And you can largely do that just through interface builders, selecting the option.
You should be upgrading all of your artwork and icons, preferably towards using vector-based drawing or else multi-reptifs. And again, I'm sure you've heard this theme over and over, no more Quick Draw. Quick Draw won't scale with resolution independence. 64-bit. So the Carbon framework, fully 64-bit clean, allows you to generate universal binaries, 64-bit on both PowerPC and Intel, so 4x universal. Again, Quick Draw is no longer there for 64-bit apps. And as an example, we mentioned the name cursor support was reliant on Quick Draw, so this also goes away. And the solution is to use NSCursor. We have a sample also available.
So some of the advantages of using Carbon. Well, the Carbon framework is both a mature and a modern framework. I mean, its legacy dates back all the way back to 1984, but it's modern. I mean, we use HIViews. We have alpha blending. You can see things like iTunes. This is a great modern Carbon application.
and others. It's a procedural based API, so for those of you that are more comfortable with procedural based APIs or for instance maybe bringing code over from Linux, Unix, Windows, it may map a little bit better. And also it allows you to control the flow a little bit better. What I mean by that is it gives you finer granularity into the operating system or into the API set.
But because Carbon isn't a monolithic framework, some of the burden is placed on you, a little bit more of the burden. And the burden means that you have to keep up to date. You have to make sure that you keep your Carbon applications up to date and modern.
So this means that you have to make sure that your application is universal and runs correctly. Be adopting things like HIViews, CFData types, Quartz, composited windows. HIViews are a great example. Although we announced that we can now embed NSViews inside of HIViews, HIViews are a modern way of presenting your views. They date back to, I believe, 10.1-- 10.2, I'm sorry.
and the benefits of keeping up. You get your resolution independent, 64-bit apps, the ability to do things like embed NS views and HIViews. Once you start to lag on this stuff, you start to be presented with a large mountain of items that you have to try and conquer to go over this. As you keep up with our modern technology, it's a lot easier to move forward.
So by embedding things like NSViews and HIViews, you get some of those fancy views in Cocoa, like the-- I forget what this one's called-- the level control, I believe. But not only that, we allow you to embed complete NSView hierarchies. So here I have a complete NSView hierarchy, the WebKit view, along with some binding enabled buttons and URL text field here. So let's take a look at what exactly the steps are to embed an NSView in an HIView.
The first step is to create a custom HIView in Interface Builder. You simply drag over your HIView and give it a class ID of com.apple.hicocoview. That's step one. Step two, look through hicocoview.h. You'll really need to get a reference to your HIView, a reference to your NSView, and then call HIView setView.
And now, step three. There is no step three. That's it. That's how easy it is. We have a great sample. It's associated on the ADC site associated with this session, 140. And you can download the HIView NSView sample, which will show explicitly how to embed an NSView hierarchy in an HIView. Now I'd like to quickly go over a quick demo describing how to add a Cocoa window to a Carbon-based application. I'll just spend a couple minutes showing you.
So typically what we'll do is first lay out our Carbon window. Here we go. Now what I've done here is laid out our Cocoa-based window. We have our web view. We've linked up these buttons to our web view. If I bring up the inspector open. We can even see here that we have some bindings associated with it.
So here we'll say that the URL bar will automatically update to have the mainframe's URL as well as the window.
[Transcript missing]
We'll get a browse command. And from the browse command, we'll go ahead and call display Cocoa web window. Let's take a look at that function there.
Here it is. It's really quite simple. We'll go ahead and allocate our web window controller, show the window, and then load the request. This is all the Objective-C code we need right here. We specify the name of our nib here. This is our getter function to return back our web view. And this is how we display it. Now let's go ahead and run this sample and see what it looks like. Hope it works. I didn't make any changes. Let's give it a second here.
See what the OpenGL guys left me with. Here we go. So here we have a Carbon-based window. We'll go ahead and enter in a URL here. When we click the OK button, up we go. Full-featured little web browser. So in this sample associated with this session, we have a step-by-step tutorial of exactly how to do this. The code is right here. It's actually quite simple. So that's it for me. At this time, I'd like to bring Larry Coope up on stage. Go back to slides.
This is the button. Thanks, Deric. My name's Larry Coope. I work in the SWAT team doing custom code support inside DTS. And I just want to take a couple minutes. I do a lot of development with third party developers. And Deric asked me to come up and just talk for 10 minutes about best practices using our tools and getting the most out of your code in a parallel cross-platform environment.
Okay, I just want to talk a little bit about Xcode and our tools. Like Deric said, stay current. In the lab already this week, I've seen several people come in, they're like, we're on Xcode 1.5. And you're like, you should be moving as fast as you can into the Xcode tools. There's a lot of changes happening. You know, universal binary, four-way binaries, Intel transition. Even if you have to support older OSs, Panther, Tiger, obviously, it's important to stay on the current tools. Okay, the documentation alone is going to give you a big lift.
Xcode, use it. Xcode is now a really flexible, powerful development environment. In the past, it's had its issues. And so it's a nice EDE. It's flexible. Xcode knows about structured OS components like frameworks. For people who are used to building flat DLLs and DILIBs on Linux and Windows, structured OS components are really, really useful. So if you're not doing that, you really need to do that.
It's well suited for both individuals and groups. We have a lot of people who have their own Make systems. And they say, how do I do distributed builds like Xcode does? Use Xcode. Xcode build, the command line tool, integrates well with an existing Makefile system. No matter how complicated it can be, Xcode build works really well. And the projects allow your engineers to go directly into IDE mode and get the most out of the tool.
OK, so one of the things that's new to Xcode, or relatively new, I'm just going to touch on, is Xcode configs. If you're not using config files, I cannot stress how much you should be using these, especially if you have multiple projects, multiple targets. You're building applications, frameworks, all these components. What are they? They're text files that hold Xcode settings. They're not strictly GCC settings. These are Xcode settings that get translated into the appropriate build settings.
You include them in your project just like any other text file or source file. It's got an XC config extension, which Xcode recognizes. You can drag and drop your settings from your build settings window into-- thank you. Thanks, Deric-- into your config files. And they're hierarchical. One config can include another. So you can get quite complex build rules set up. They're independent of any individual or specific project.
Unify common project settings, of course. Guarantees that your projects build all the same all the time. Check them into your source code control. OK? So I wanted to do a brief demo. So like Deric, I'm gonna quit what you're doing. I'm just gonna open a new project. Carbon application. Next.
Now typically, you know in Xcode that targets inherit from projects. You go into Build Settings. Let's look at my Customize Settings. There's just a couple. But you know what? We're going to use configs, so we're going to get rid of those. So this is the point of configs. They simplify your life.
You go into Target, and often-- and this is where people coming from Windows and other IDEs get a little bit flustered, because all of a sudden, you see these are just my customized settings. There's a lot of settings in Xcode. So what I'm going to do now is just drag in-- I have some canned config files here that are on the attendee website that you guys can download. If you do C and C++ development, these I find to be a set of rules that I've developed with other third party developers, and they're using these exact config files. So I add those. And now you'll see in my project. project.
Based on pop-up is available. And I'm going to say I want to base my debug config on my debug config file and the release. I want to base on the release. And then I can go to my target here. And I can start deleting all these, except the things that are pertinent to the actual build.
So now the idea is I can really see very quickly what is pertinent to this particular target because all my settings are coming from my config file. And let me just show you that here's the debug settings. You notice the first thing at the top, Include, Config Master. That's my master config. We talked about their hierarchical. One can include another. So if I want to look at that.
And here, this is the master. I'm saying I want to start with a native architecture, SDK root, all these settings. And like I said, you can drag and drop from your build settings window, if you'd like to see how that works.
[Transcript missing]
And the point is, like I said, now all my projects, whether it's static libraries, dynamic libraries, frameworks, bundles, are going to build identically based on my settings. Okay. Back to slides.
Okay, I want to talk a little bit about system standards and development best practices. People, you know, you really need to adopt standards under OS X. Things like bundles and frameworks, you know, people who are building straight, flat die libs, you need to use install names. And if you're not sure what I'm talking about, you can come to the lab tomorrow morning and we can talk about it.
But these structured OS components are really -- they're there for a reason, and they're really required now. Die libs laying all over the place, this is generally a bad idea. Overriding the DYLD library path. People say, oh, I have a collision, my application crashes. You're like, yeah, that's the point of these things.
wanted to show this is not a well-structured Mac application. OK? I hear some laughing. So this is actually-- I've seen this before. You guys, it's just a couple extra steps to actually bundle things, your dial-ups up into structured components frameworks. It's not a big deal. This really benefits your code factoring for the whole Model View Controller.
You're gonna avoid the Windows DLL hell situation, versioning issues. High level support, very high level support for all these components in the system. You don't have to do any extra work. There's a little extra startup cost getting these things built. Very little, but long-term maintenance is much, much easier.
Owning your code. With all the transitions that are going on, it's important that you own your code. Your data types. This allows you to insulate yourself from OS changes like 32 to 64 bit. By typedefing your own types and using those everywhere in your code instead of using int, long, unsigned int, allows you to really, really make rapid changes. I had a bunch of code that was using my TS32 type. I got it to build in 64 bit, and I had a couple of crashers, and I'm like, "Oh, I changed my type." It built.
Make these things meaningful. I know this is brain dead to a lot of people here, but UN32 is not a pointer. So make these types specific. Use GCC. Shoot for zero warnings. I see a lot of code. Saw some in the lab, like, warning. This expression is always true.
And they're like, that's just a warning. It's like, no, that's a problem. So don't assume-- I know you guys who are doing cross-platform development understand this implicitly, but the compilers don't always do the right thing. So when you see things about ambiguous declarations or ambiguous cast, resolve it explicitly. and others. Factor your code. We beat everyone up on the model view controller thing. It's really critical for doing multiplatform development.
This is what you guys are adding your value to your products, your intellectual property code. It's not always the view that's important. That's a critical UI issue, but what you're bringing to your users is your code, which you want to factor out away from the UI. OS Factoring. You know, there's no way to get a perfect solution. So make it work for your code.
Abstraction of OS types. People say, well, I have all this Windows code, and you can define a window ref to be an HWIND on the Mac. We don't care. A lot of people are transitioning. They already have a type for their window types. They just change it to NSWindowPointer, right? So it's like moving to Cocoa for some of their low-level window UI elements is pretty trivial.
Take advantage of system services. Use Core Foundation, as Deric said, and CFStrings. I have people struggling with STL Strings all the time. And we write a class that wraps CFString, wraps C String or STL on other platforms, and in two days, they've eliminated all their Unicode issues and things like that. Especially, it lets you be closer to the metal, which is what you need.
Avoid intermediate representations. OK, when you're using code on the Mac and other platforms, you know, I have a lot of vendors like we pass a string around as a file representation. Like, that's a bad idea. So it's better to use FSRefs. Delay when you need the path. If you want to pass it to a POSIX API, delay that instantiation of that representation to the last possible minute.
If you know, you guys know FSRefs track everything. I can move the file and disk and my FSRef stays valid. A path does not stay valid. And this applies for CFStrings, all the core foundation types. Avoid or delay intermediate representations. Ask for that representation. Like I said, if you need the path, you need it as a Mac Roman string, fine. Ask for it that way.
Just don't assume how it's going to be implemented internally. You know, I make the joke about, I don't come up and punch you in the stomach and say, where do you live? You know, it's like-- you ask. So this is important. It's all about data abstraction. C++ is really great at this. So if you're using C++ already, you know what I'm talking about. That's it. Brian, Kurt?
All right, thank you, Larry. So I'm Bryan Prusha. I'm on the HIToolbox team. I'm going to be bringing up Curt Rothert in a moment to go through some demos. I'm going to go through creating a custom HIView using HIFramework. The first thing I want to say is that we've heard a lot this week about HICocoaView and the ability to add NSViews to your Carbon control hierarchy. And you may be asking yourselves, well, is Carbon going to be here for very long? Why should I worry about HIViews? Well, the HICocoaView is Leopard only. Leopard isn't here yet.
HIViews are here now. And they are backwards compatible to Jaguar. And they're your first best option if you're a HIToolbox Carbon developer for bringing your application forward for new features like 64-bit, getting the performance out of compositing windows, and resolution independence. So now that we know the importance of HIViews, let's get into it.
So HIFramework is a set of sample code. It's available at the attendee website. In fact, we're going to be going over the T-Browser View sample code. So if you can go to the website and pull that down, we can get on the hands-on portion when Curt comes up for a demo.
But it's a set of C++ style wrappers around HI toolbox functionality, around HI object and HIView specifically. And there's a lot of support in there for simplified event handling. If you've been working with Carbon events, they're incredibly flexible, but they're also based on a procedural API, which makes a little more work to get all the parameters in and out, things like that. So HIFramework does a lot of that work for you. And all you have to do in most cases is override virtual methods in your superclass, and all the parameters are handed to you directly.
So there are a couple of important pieces to HI Framework. T-Object is a wrapper around HI Object, HI Object Ref. T-View is a wrapper around HIView Ref and its subclasses in a C++ style from T-Object. T-Carbon Event is a convenience wrapper around Event Ref for Carbon events. And there's a whole suite of-- Accessors for a tCarbon event that make it really easy to pull parameters out and add parameters to an event.
So here's what we're going to be building today, T-Image Browser View. It allows you to add an array of images or an array of image URLs to your browser. There's some browsing buttons to go back and forth, delete button in the lower right hand there. For anybody who was at the State of the Toolbox session last year, you saw a bunch of pictures of our kids. And here they are one year later playing in a G5 box.
All right, first up, subclass. To declare your subclass, since this is a C++ style, just call class TImageBrowserView. Make it a public subclass of TView. Now, there's a little bit more to it than that, because we're bringing together the C++ style of subclassing with the HIObjectStyle subclassing. So we need to tell HIToolbox about your object, register it.
It's very convenient to have a creation API. It wraps a lot of boilerplate code. We'll get into that in a minute. You need a construct proc. This is something that you pass to the toolbox during registration. And when you're called, you just need to return an instance of your C++ object, and of course, your C++ constructor and destructor.
So in registration, we use TView's implementation here. It's called TViewRegisterSubclass. And we're calling it with KImageBrowserViewClassID. This is just a CFString, reverse domain name, com.apple.blah, blah, blah, whatever. And our construct proc. So the HIToolbox now knows about your view. So when somebody asks to create a view, whether procedurally through your create method, or if it's being loaded by or instantiated from a nib, the toolbox can then ask one of your objects to be instantiated. So this is important to call this early on in the lifetime of your application, whether in main, during C++ static initialization, something like that, any time before one of your objects is going to be instantiated.
Now, when your view is loaded out of a nib, something like that, and the toolbox knows it needs to instantiate your object, it's going to call back your construct proc, inside which all you need to do is create an instance of your C++ object and pass it the HI object, in this case an HIView, that the toolbox has created for you. So the toolbox will then take your C++ object and the HI object ref that it created and associate them with each other. Thank you.
And here's the convenience create method I mentioned. Our object takes-- or our view takes a window that it's going to be added to, an HIRect representing its bounds, the origin and size, and a CFArray of image URLs to browse through. So if your view doesn't take any creation parameters, initialization parameters, you can simply call hiobjectcreate with your class ID and have your view created. In our case, we want to create an initialization event, fill it up with each of those parameters, and pass that into hiobjectcreate as well. So it's convenient to be able to call this creation method instead of having to fill out our initialization event all over our code.
OK, at this point, now that we have a subclass, we just want to handle events. HIFramework and TView handle a whole series of events for you, pull out the parameters, and then call your C++ methods. Now for any of the events that are not wrapped, and it's impossible to wrap all events automatically because you have many of your own custom Carbon events.
All the events are routed through TView Handle Event first. So if you need to get first crack at an event before it gets processed or handle it in a custom way, your Handle Event method will be sent a tCarbon event, and you can use all those convenience accessors. So it's still even easier than dealing with a normal event ref. But make sure when you're done doing your processing that you call back into TView's Handle Event. Go call back up to your superclass. Then it can process and send all those events through the C++ structure.
All right, we have a view that is kind of the most basic view you could have. It doesn't really do a whole lot. So let's talk about drawing. Modern Carbon applications, their drawing is based on an invalidation model with compositing windows. You want to set the K window compositing attribute on your window. You can do this in IB in your window. Anytime that you want to make a change to the model behind your view, in this case, for instance, let's say an image is deleted, we'll note that in our model and invalidate our view instead of drawing immediately.
So we could delete an image and add another image, do a whole series of things behind the scenes, invalidate each time. The toolbox will coalesce those invalidations and only send the draw method to your view when it's really necessary to draw. So this is a huge performance win. There are a couple of convenience APIs here for auto-invalidation. They have changed auto-invalidate flags, and you can have your view be automatically invalidated for activates, deactivates, and so on.
So when the toolbox then decides that your view needs to draw, all you need to do is override TView's draw method and handle all your drawing in one place. It'll be handed a context. You can use CG, completely modern, high DPI savvy. and the rest of the team. Now I'd like to bring up Curt and go through each of those steps right in front of your eyes. Thanks Bryan.
So first off, I'm going to bring up my demo monkey. He's my assistant because I don't want to be typing all of this code on my own. Now as Bryan alluded to, the view that we're going to be creating is this image browser view. And it will just show what the target is, what we're aiming for with this particular view. So this is just bringing up the sample that's in the available sample code.
You can see the image browser view is this image in the window. It also has various parts, so you can navigate between the images. So again, this view has an array of paths to different images. And then it has an index of which element in that array it's going to be drawing at a particular time.
So you can use these different parts to navigate between the different images. And this X is also allows you to delete an image from the list. So we have a lot to do, so let's get into actually implementing this. I'm just going to start Xcode from scratch. Eric, you opened too much.
So first off, we need to launch Xcode, and we'll create a new Carbon application. So create a new project, choose Carbon application, and we'll call this
[Transcript missing]
And now we're ready to go. So this is just using the normal template of the Carbon application. But since we're going to be using the HIFramework sample code to do our subclassing, we need to make sure that we add that to this project as well. So let's go open our sources and add HIFramework, which I have here on disk, to the project.
So once the HIV framework is added to the project, we're ready to go and start subclassing. So next, we'll create the image browser view implementation and header file. We'll choose to open a new C++ file. We'll call this the image browser view and get going. Can you in the back see this, read this okay? Should I bump up the font size?
I'll just stick with it for now. If there's a yelling or whatnot, I'll just try to bump up the size. Since we're going to be subclassing from TView, we first need to include a reference to TView. Then, as Bryan mentioned, there needs to be a unique class ID, which is a string which uniquely identifies your class. So we'll define that here. Of course, it's just going to be the reverse DNS name scheme. So in this example, we're just going to use com.appleSample.imageBrowserView. And then we go about declaring our Views subclass.
Currently we're just subclassing from TView. And the member variables here, this is the image. And this just contains an array. It is an array of URLs to different images. And this is the index into the array. There's a few things required by TView, certain methods that we need to have in our class.
So first off, we're going to implement the Construct and Destructor, the Constructor and Destructor. And also if you're to look in the TView header, there is one pure virtual function that needs to be implemented by your subclass, and that's the getKind method. So let's go to the implementation.
We'll add our constructor and destructor. So here, we're basically doing nothing at this point other than initializing our member variables. The constructor is defined to have an HIView ref passed in. We just passed that on to the superclass. Then we have the destructor. And since we contain an array of images, if that array is in existence, we want to make sure that we release it correctly using CF Release. Next, we implement the getKind method. This just returns a unique control kind, which identifies your view as well.
So those are the methods that are required by the TView subclassing. We still need to make sure that our view is registered with the HIObject registration mechanism. And as Bryan mentioned, you need to register by passing in a construction proc. Now, since we're using a C++ subclassing scheme, we'll have that construct proc just be a class method for this class. And it will just be purely a class factory method.
So this is the constructor. The signature is defined in tObject.h, because this is going to be passed into the tViews register subclass method. and we'll go implement that in the implementation file. And since it's just a simple class factory method, all it does is creates an instance of your class and returns it out as the out object parameter.
This is only half the story, of course. This is the factory method to create an instance, but we haven't yet registered with the HIObject registration mechanism. So we'll do that by providing a register class method. This will need to be called outside of the existence of your view, usually, as Bryan mentioned, in the beginning of your application, at launch, startup, initialization time, something like that. Let's go back to the implementation.
And this, quite simply, just calls the TView registration method, passing the class ID, which is the unique identifier for this class, and the construct proc, which we just defined up here. So that takes care of registering your view with the HI Object system and construction of your view.
What HI Object will do after an instance of your class is created is it will send that initialization Carbon event because what you would call, the client would call HI Object create, passing an initialization Carbon event, which you can optionally specify what Carbon events you may look for during initialization.
So what we'll do here is we'll specify that this view will want to get out or extract from that initialization event a path to an image, and that's a good way of initializing this view to have some type of data initially. So we'll just provide a Carbon event parameter type, and we define that. This is just application defined as the four character code IMAG.
are defining that the value for that parameter will be a CFString, and it will represent a path to an image on disk, for example. Now since our view is already currently registered with the HIVobject mechanism, all we need to do is start overriding the methods in TView that we want to handle. So for example, when we want to handle initialization, let's just go ahead and override the initialize method.
Now what we'll be doing there is in the implementation is this section of code. Basically, this is an appropriate place. If you didn't do it in your constructor by initializing your member fields, you can do that here. This is a good place to go ahead and initialize your index to be 0. The URL array will just create a mutable instance of it here.
and optionally extract the event parameters, which we've defined in the header, from that initialization event. So we defined the event parameter to be an image URL of a CFString. If that exists, we go ahead and create a URL ref that represents that image. We add it to our image URLs array, and then we move on.
This is also a good place to change features of the HIView. For example, this view we know is going to be opaque. And this is an optimization. You're telling the toolbox that it's not required to draw the views behind it, because your view is going to be completely opaque. So we're going to do that here. And we're also going to call TView's change autoinvalidation flags so that it will autoinvalidate when the enable or activation state of our view changes. So now we've taken care of initialization, but the view doesn't draw anything at this point yet.
So we'll override the draw method. Now this is just overriding the draw method that's in TView already. So go back to implementation. Drawing is going to require some constants, because we're going to inset the image from the bounds of that view. And I'm also going to include some initial paddings for buttons or parts that we'll be implementing later on as well. I'm just going to put the constants up at the top of the file.
And then go ahead and add the draw method. What draw is going to do is initially get the bounds of our view. And this is a method that's in TView, so we can get the bounds. We fill that bounds with just black, because we want the image to be shown on a black background.
If there is an image to display, we'll go into this block of code. And what this does is it gets the URL that's in that array that we need to display. We create a CG image off of it using this code. We're also smart enough that if the view is either disabled or inactive, we'll transform the image so it has the disabled appearance, because we want it to change its appearance if it's disabled, to provide that feedback to the user.
This next section of the code just determines how to scale the image properly so it fits within the bounds of that view. And then it deals with the centering and position of that image. And finally, it will draw the CG image using the API HIView draw CG image. So now we've got our view to a point where we'll actually display something, but we need to test that. And so we need to somehow add that to this test harness, which in this case is this application. Let's go back to the project and edit main.
Like we mentioned earlier, it's required that you make sure that this class is registered before there's an attempt to instantiate it. So we'll go ahead and do that just at the beginning of May, and I have to include a reference to that file. And here at the beginning of the main, I'm just going to call the class method tImageBrowserViewRegisterClass. And that will ensure that it's registered.
Problem in this one particular case is, since I created a normal Carbon application, this file is just a main.c file, and we're starting to reference C++ code. The compiler is going to-- since it's going to compile with a C compiler, it's going to complain. So we have one of two options.
Let's just get info on this particular file. And we can either, in the file type, change this type to a C++ or just give it the appropriate suffix, which I'm going to do here. I'm just changing the suffix to CP, which will force the C++ compiler to be used, if Xcode comes back for me.
Now there's two ways to instantiate this view now. You can call hiav.create, passing in the class identifier, which is that string, on your own programmatically. Or you can use Interface Builder and create an interface there and instantiate the view that way when that window is loaded from the nib.
Since that's the easiest way for purposes of this example, let's go ahead and do it that way. and resources. We'll open up our nib. This is the initial window. If we go over to the controls palette, in the enhanced controls pane, there's this HIView over here. Let's drag that to our main window.
bring up the inspector. The HIView inspector has this place for a class ID. This is just that unique string that we've already identified in our header. So let's add that here. Let's hit Return. You can see that it changes to have that class ID. And then there's this space also down here in the inspector, which allows you to provide some initialization parameters. Let's go ahead and add an initialization parameter here. This is the four character code, which is that name, which in our case is IMAG. We define that in our header as well.
will be a CFString, and the value will be a Unicode string, which represents a path to the image. So hopefully this image will be available in our desktop pictures. It's just a flowing something or other. Save the nib, go ahead and quit it. Let's build and run and see how it works.
So there's our view. Basically, you've seen how we've gone through and registered with HIObject, where it was able to create an instance of our view, and then once the-- oh, and also pull out the initialization parameter, which is this path to this image on disk. And then the draw method was invoked, in which case we just drew the background image, we created the CG image for it, and we just blasted it into our space. So back to Bryan.
All right, thank you, Curt. All right, Curt just spent 15 minutes showing us how to make a HI image view. We already have one of those. So let's start adding some more interesting content. Let's talk about user interaction. First step of user interaction is mouse tracking. When the user mouses over your view and clicks, the first thing the toolbox needs to know is what part of your view the user clicked in. So if you override TViewHitTest, you'll be past a point, and you'll need to return an enumerated part of your view.
Now by part I mean something special. Let's take the scroll bar for example. There are lots of pieces to it. There's the up and down buttons, page up and down, scroll thumb. Now these are implemented as enumerated parts, not actual sub views, because we wanted to make sure it was nice and lightweight.
So these are literally parts 1, 2, 3, 4, 5. We have the same thing in Image Browser View. We have the entire control, the entire view, and then we have the browse buttons and the delete button. So these again are enumerated parts 1, 2, 3, 4, or something along those lines. You can check in the code.
So what the toolbox is going to do with this information about what part that gets returned, it's going to set the highlight of your control. So you'll want to listen to TVU's highlight changed and invalidate your view. So when you go back to draw again later, you can draw some user feedback. For instance, here you can see that the back browse button is dimmed as if the user is clicking on it.
So once the user has clicked, tracked onto your part, and unclicked, so the mouse up occurs, TView control hit will be called. So if you override this method, you will be reacting to a mouse click that has already occurred. If you need to do anything-- and then you can perform whatever action, clicking on that piece, that part performs. If you need to do anything more interesting with custom tracking, override TView track. This will be sent when there's a mouse down, but the mouse has not been let up yet. So you can use an API like TrackMouseLocation to follow the user's interaction in your view.
Now, make sure not to use any old idioms like, "wow, still down." This is very important. TrackMouseLocation is very modern. It knows about-- it blocks so that as long as there isn't any action going on with the mouse, with the user, your application will be quiescent. It doesn't take away resources from other applications or other threads within your app. It allows Carbon events to fire and timers to fire. "Wow, still down" does not allow those things. You'll peg 100% of the CPU, and other people won't like you very much. So avoid that at all cost.
OK, now that the user can track with a mouse, we want to expand our interaction abilities with keyboard navigation text input. HIFramework has a notion of interfaces. So we want to activate the K keyboard focus interface. What this does is underneath the covers, turns on a series of Carbon events, Carbon event handlers in sequence. So you don't have to worry about that. All you want to do is override C++ methods.
So to receive focus in your view, override TViewSetFocusPart. So as the user tabs through their-- and others. Through their interface, for instance from this edit text control into your view, set focus part will be called. And then you need to set the focus on whichever part makes sense for your view.
As the user continues to tab, a set focus part would be called continually, and you can tab through the different parts of your view. One little feature I want to point out here is the gets focus on click HIView feature. This is important if you have something like a text editing view. You want to make sure that when the user clicks, that focus moves from wherever else it is in the window into that view.
This isn't as important for something like a push button. You wouldn't want to lose focus in your edit text view when you click a button. So make sure it makes sense in your view if you're going to use it, but it's a nice convenience. Now that your view has focus, the whole point is to get keyboard input from the user. So the handle text input override TView text input.
Then at this point, all keystrokes will be sent directly to your view. You can listen to them and react to them appropriately. Now, when the user presses spacebar, this is something important. For accessibility, this represents basically a mouse click on whatever part of your view is focused. So it's important to give a user feedback as if they have performed a mouse click.
So if you call HIView simulate click when the spacebar is hit, this will go through the entire process as if the user has clicked. So you'll be sent the hit test message with that particular area. And your highlight will be changed. You'll invalidate. You'll redraw. And you can give the user some nice feedback Some nice feedback that something is occurring. Then once that's done, go ahead and perform your action.
The third major piece of user interactions is accessibility. You want to make sure that your view is available to as many users as possible. To support accessibility, turn on the accessibility interface and override the T-object suite of accessibility events. Now, these methods you override on T-object rather than T-view, because there are all kinds of UI elements that can be accessible, like menus, windows, your whole application, for instance, not just views. We don't have a lot of time to go into detail on this. There's plenty of examples in the sample code.
One last piece of user interaction I want to go over is drag and drop. Again, set the drag and drop interface activated. You need to enable dragging on your view and your window. HIA framework does not do this for you automatically at this point. A good place to do this is when your view is added to a window. So you can enable that within the owning window change method.
But this is one piece that kind of trips people up sometimes, so make sure that you're doing this whenever your view is added to a window. And then override the TView dragging suite of events. Drag Enter, Track Within, Leave, and Drag Received. You'll be handed a drag ref, and you can do all the same drag machinery that you've done with that in the past. Given that we've covered user interaction, I want to hand things back to Curt to add those to our view.
Now we'll get into implementing support for those various parts, the back, the forward, and the delete part in our view. So let's first add a new member variable to our view, which will remember the currently focused part. We're not going to get into using this initially at this point in time, but we need to make sure that that's available, because some of the other utilities I'm going to define will depend on it.
So let's go define some of those utilities now. I'll go through all of these. This is the section of code that I added. And these are just declaring different utility functions that this particular view is going to need. Your view, of course, will be different. But I'll get into all these in just a moment.
Let's go to the implementation file. Back up at the top of the file, I want to add some additional constants. And these constants will define what the various parts will be. So for example, I now have these new view parts. This is the image part that's going to represent the whole image as a whole. The back, the forward part, those are those triangular arrows, and the delete image part, that will be that X that will be drawn.
Since we added a new member variable, let's make sure that we initialize that correctly. So back in the initialize method, I want to go ahead and set that to some initial value. And now I want to go add those utilities that I declared in the header. So this here, this section of code is to just return a frame for one of those individual parts.
For example, if the part or the back part, if somebody is requesting what rectangle represents that part, we do this calculation to determine that. Similarly for the forward and delete part. The image part itself is just the bounds of the view, but we just inset it a little bit.
The next helper method is this is part available. And all we're doing here is determining whether any of those individual parts is currently available. So if the image part is available, we determine whether the view itself is enabled. And then if there is an index in the array to be shown, we determine whether the browse back forward parts are to be enabled as well.
Next, we have some helper methods to navigate between the different images. Since this view itself stores multiple images, or multiple paths to images, we want to make sure that there's some methods to navigate between those. So we have the next image. And all this does is it increments our index count. If focus is set on one of those additional parts, which we'll also get into later, we make sure that we appropriately set the keyboard focus. But the important part here is that we just increment our index, and we invalidate.
We don't do any drawing in this method because once we do the invalidation, the next time through the draw loop, our draw method will be called. And since our index is incremented, we'll now draw the next image that's stored in our view. Similarly for a previous image method, this just decrements the index and invalidates as well.
We now have some methods for adding images to this view. This will just add a new image URL to the array and invalidate because it will now be shown. A delete current image method, which pretty much does exactly what it says. But the important part here, of course, is that we invalidate after we delete the currently viewed image. Git view part. This returns the part code associated with a given point. So we just iterate through the part frames here, determining whether the point is inside that part. And if it is, then we return that part. Otherwise, we return no part.
And finally, this is just a utility function. Looks like a lot of code, but what it does is it extracts a file URL from a pasteboard. This will be important when we implement drag and drop support because you're past a pasteboard that we can extract URLs from. And then just we'll add that to our image array. Let's go ahead and save that. And now we need to update the drawing. So we go back to the draw method. And after we draw the image, I've added this code, this section, which will draw those various parts.
These parts are, again, the back, the forward, and the delete part of the image. So initially, we get the highlight, because we want to know which part is currently highlighted. and then we have separate blocks for drawing each of those different parts. We get the frame and then we use courts to do the drawing for a triangle, for the back, then again in this section for the forward.
and then in this section for doing the delete part. Another interesting thing that we do here is for each part we determine whether that part is the currently highlighted part, and we draw it differently based on that state. So for example, if the forward part is highlighted, we draw it not quite white. We draw it kind of gray.
And additionally, we make sure that if we can draw it in another state, if the part's not available, we'll draw it with a different shade of gray as well. Now, of course, this is just what this view has chosen to do. Your view, of course, will do whatever it wants to do. Let's go ahead and run this and see how it looks now.
So all we did at this point is just add support for drawing these different parts. So this is the back part, the forward part, and the X part. But you can see that this drawing disabled, because there are no images to navigate between currently. And we haven't hooked up tracking, so hitting any of these parts isn't going to do anything at this point. So the client-- well, the view itself could add some new API or some public methods so the client can add images to it. But we just want to make this really simple. So we'll just implement drag and drop support now. So go back to the header file.
I'm going to want to add one additional member field. This is a flag that tracks whether we are currently tracking a drag. And this is going to be important because when we're dragging items to this view and we're tracking that drag, we want to draw that view differently. Specifically, we want to draw a highlight around that image. So it provides the user feedback that it's tracking.
and then we'll override the drag suite of methods that are in TView. So override drag enter, leave, and drag receive. Now, the one caveat is that we need to make sure that the drag tracking is enabled for the window and also for the control when that view is initially embedded in that window. So we'll do that in the owning window changed method.
TView has this notion of interfaces. So you can activate a particular interface, which is not on by default. And this will install various handlers on for a suite of events. So we want to make sure that we activate the drag and drop interface. Now, if you just go investigate the TView header, you'll see where this is all defined. There are different interfaces available.
We want to make sure that we activate the drag and drop interface. Since we also added a new member variable, we want to make sure that we initialize that correctly. So I'm going to go back to the initialize method and make sure that we are initially not dragging.
Next, I will implement the drag methods. So when the drag enter method is invoked, we get the drag pasteboard. We determine whether there is a file on that pasteboard, if it's something that we can handle. And if so, we flag that we are currently tracking a drag, and then we invalidate. So next time through the draw loop, we'll see that that flag is on, and we'll draw the highlight around our view. Similarly, when the drag leave handler is invoked, we just toggle that flag to false and validate.
If the drag is received, again, we get the drag pasteboard. We extract the images from that pasteboard, or the paths to those images from that pasteboard. And then we go ahead and call our internal utility function, which is just to add the image to our view. And lastly, I'm overriding the owning window change. And again, this is just to turn on tracking for the window and for the view.
Since this is all sample code, you may want to go ahead and change HHR framework so it does this in all cases. We'll update the code along the way as well. And now since we have this flag that determines whether we're tracking a drag, let's go ahead and update the drawing code to make sure that we provide that feedback to the user. So I'll go back to draw.
This is the section where we draw the image. This is the section where we draw the various parts. And now I will add this section of code that if we're currently tracking, we get the highlight brush and then we stroke that rectangle, which will just create a nice highlight rectangle around the view.
Let's see if this is handled correctly now. So again, this is our view. I have this folder opened up to various images in the desktop images folder. Let's go ahead and drag all of these to the view. Now you can see as we drag over the view, there's a highlight that's drawn around the view to provide feedback that it's currently tracking.
And then if we go ahead and drop, those images are added to the view, and the parts change their highlight. For example, we can now navigate back in the list of images, so this now becomes enabled. We haven't hooked up tracking yet, so clicking on this view, or this part of the view, isn't yet going to do anything. Let's go add that support now.
Go back to the header. This is very simple. Since HIFramework takes care of all of the simple tracking, all we need to do-- we don't even need to override the track method. We'll only need to do that if we want to add some additional behavior, like bring up a pop-up menu or something. But for simple tracking, all we need to do is overload these two methods-- hit testing and control hit. Let's go ahead and implement those.
So we override this hit test method. And what we do is we determine what part is represented by that point. And that's by calling this internal method getViewPart. And then we just return that part. So HIFramework, when the user clicks on that part, it will store that part as the highlighted part. And when it validates and redraws our view, we get that highlight state in the draw method, and then we'll draw it differently. And I'll show you exactly what I mean when we go and run this.
Next, we implement the ControlHit method. What this does is this method is invoked after the user releases the mouse over your view, and it also passes in the part which is underneath that point. So we do some various operations. So for example, if the back part is clicked, we go to the previous image. Forward part, we go to the next image, et cetera. Sounds very simple. Let's go ahead and run this.
So again, let's add these images to the view so we can navigate. We go here. And now when I click on the various parts, ah, I did miss one step. And that is I forgot to add some support in the draw method to deal with this various stuff. Or did I? I think I may be incorrect in that.
[Transcript missing]
Finally, let's go and add text input support. We want this view to be navigable by the keyboard, so we want to be able to tab into the view and manipulate it with a keyboard. And we'll do that. overriding the set focus part and the text input methods. Since this is another interface, similar to drag and drop, we need to make sure that we activate the keyboard focus interface.
And then we implement those two methods. First, this looks like a lot of code, but it really isn't. We're overwriting the set focus part, what it passes in the desired focus part that we want to do, and whether we need to focus everything. And this is important because it's not always required that you focus every part of your view.
These Mighty Mouse are crazy things, aren't they? If I go to the keyboard and mouse panel in System Preferences, there are these radio buttons down here to determine how focusing happens. So if this isn't enabled, it won't actually go into all the various parts. It will only work for textbox and lists. But I want to make sure that we can focus into the various parts of this view. So I want to enable focusing for all controls.
So that's what this determines. And then this block of code, we're just switching on the desired focus, and we're determining what is the next part that we can focus. Like if the meta part that's passed into in desired focus is focused next part, we do that calculation to determine which part is going to be focused. Similarly for the previous part.
And in the default case, we try to focus the part requested. We cache that into our internal variable so we can reference that later in our other methods. And also we make sure that the output parameter is set to that new part as well. This block of code just simply invalidates the view if the focus has changed.
Next, we override the text input method. First, we extract the Unicode text from that Carbon event, and then we just switch off of what that character is. So if there is a part that's focused, and the user presses the space bar, we'll simulate a click on that part. Similarly, if the user clicks on any of the deletion keys, like Backspace, Delete, or Clear, we'll simulate a click on the Delete part, because we want to delete that image from the view. And the rest of the code just deals with navigating between the various parts.
Now finally, we must update the draw code because we want, when the various parts are focused, we want to make sure that we draw a spiffy highlight around those parts. So in the draw method, after we do the tracking drag support, We add this block of code, which just determines whether there is a focused part. And if so, get the current highlight brush and then stroke the rectangle around that particular part.
So let's launch our view, grab these images, add them to our view. Now if I hit Tab, the whole image should get a highlight around it, which it does. And additionally, since I turned on full keyboard focus, if I hit Tab again, you'll see that the various parts are highlighted in that view as well.
and they do respond to keyboard events. So hitting the space bar, for example. We'll go back. If I tab to the forward part, and others. That's basically adding keyboard support. That's basically getting your view up to a usable state. And then there's plenty of other options that you can add to your view. And there's some that's in the sample code, such as adding tracking areas and whatnot to your view. So back to you, Bryan.
We go back to the slides. As Curt mentioned, we have a lot more implemented in the full view. We have a caching system to make the drawing much faster. So a little bit of CG image caching there might be helpful. All the accessibility suite of events is overridden. So you can use the accessibility inspector to go take a look at your view.
tracking regions so that when the user mouses over, we get some nice rollover effects. HI Archives, so you can save the state of your view off into a CFData to write out to disk or put on a pasteboard across the wire to another application. Even some open and save sample code to save that archive out to disk. So there's a lot in there. Go take a look.
So what we'd like you to do at this point is play with the sample code. Build it, see what it does, maybe add a feature. Curt mentioned adding a contextual menu. Maybe you want to be able to right click and choose any image that you want, something like that. Try overriding TV track and see if you can add that.
Once you're-- maybe even just create an entirely new view, just something that you'd like to do for your own fun. Some of us think it's fun. The next step is to convert your custom drawing in your application to HIView. So whether you're drawing directly to the window or you have some old C-DEFs, HIViews is really the way to go. Because once you have HIViews, you can support compositing in your window. And compositing is what you need to get some great performance. It's required for resolution independence and 64-bit, which are features that your users are really going to demand.
So take the sample code. It's a set of C++ wrappers. If you already have your C++ framework in your application, maybe you can get some more ideas from it, add to it, make some changes. It's yours. Do whatever you like with it. I hope you've learned a lot. And I'm going to hand things back to Deric to wrap things up.
[Transcript missing]