View this PageEdit this PageAttachments to this PageHistory of this PageHomeRecent ChangesSearch the SwikiHelp Guide
Hotspots: Admin Pages | Turn-in Site |
Current Links: Cases Final Project Summer 2007

Team ASDF Case Page


Team ASDF:
Amlan Dasgupta
Daniel Engel
Arcadiy Kantor
David Shirley

Contents

Comments on each individual milestone
  1. Milestone 1
  2. Milestone 2 - Not an A, no info
  3. Milestone 3
  4. Milestone 4
  5. Milestone 5

How do I...
  1. File I/O in Squeak
  2. Serving static files with Comanche
  3. Taking textual input with Comanche without messing with Postdata
  4. Making use of class names
  5. Squeak saving/basics
  6. Using step
  7. Making and using previews

General tips
  1. Suggestions for getting through the class
  2. Useful links




Milestones

Milestone 1: TimerWidget (top)
Uploaded Image: timer.png
Our timer widget used step to work(See here). Step works perfect for an object that you need to check on periodically. Every second we check the status of our timer widget to see if we need to change the color to indicate behind/ahead status for the presentation.
step
	"This updates the display.  The display is updated once per second as defined by stepTime."
	(self isRunning)
		ifTrue: [
			(self isTimeRemaining)
				ifTrue: [self displayTime: (self timeToString: (self timeRemaining))]
				ifFalse: [self displayTime: (self timeToString: (self timeOver))]
				].
			
	 (self isRunning and: [self isEditing not])
		ifTrue: [self displayAlert].

As you can see all the step function does is update the time and then check if an alert needs to be displayed. The calling of the step function is handled by squeak. Even though our timerwidget was a small assignment there were some problems with it we learned from.

Problems (top)

Our code (top)
Our TimerWidget was basically unchanged in later milestones, so if you would like to look at it see it there.

Milestone 3: Implementing the design (top)
In Milestone #3, we integrated our timer, added a layout chooser, and added a template chooser to allow users to customize their presentations. These features are easy to use, and take hardly any time to set up. In order to start up our project, simply file in our category and execute the following line in a Workspace.
SqueakPoint new openInWorld.

IMPORTANT: The LayoutChooser will not run on a Windows machine. The LayoutChooserMorph uses external files to run. Because of the differences in the Linux and Windows file systems, this part of the program will not run in Windows this part of the program will not run in Windows.

The GenericLayoutMorph Object (top)
We needed a way to represent each individual layout and all of the text and image boxes within them. We created the GenericLayoutMorph class to do this.

This class contains two lists holding the text boxes and image boxes in the layout. LayoutFactory has eleven class methods to initialize the two container lists and appropriately position each of the text boxes and image boxes.
LayoutFactory makeBase.
LayoutFactory makeTitleSlideLayout.
LayoutFactory makeFourPictureLayout.
LayoutFactory makePictureLayout.
LayoutFactory makePictureTextLayout.
LayoutFactory makeTextLayout.
LayoutFactory makeTextPictureLayout.
LayoutFactory makeTextTextLayout.
LayoutFactory makeTextTwoPictureLayout.
LayoutFactory makeTitleOnlyLayout.
LayoutFactory makeTwoPictureTitleLayout.

Each of these class methods refers to one of the specified layouts and returns an instance of GenericLayoutMorph.

The GenericLayoutMorph class also has a copyInto: method that takes in another GenericLayoutMorph. This method transfers the content of the existing morph into the new GenericLayoutMorph.
	orignalTextBoxxes
		do: [:each | textIndex <= newTextBoxxes size
				ifTrue: [(newTextBoxxes at: textIndex)
						contents: each contents.
					textIndex := textIndex + 1]].
	orginalPictureBoxxes
		do: [:each | (picIndex <= newPictureBoxxes size
					and: [each getImageLocation notNil])
				ifTrue: [(newPictureBoxxes at: picIndex)
						setImageFromPath: each getImageLocation.
					picIndex := picIndex + 1]].

This code copies the old content into the new text and picture content lists of the GenericLayoutMorph. This method was used when we edited the layout of a slide. See the Editing Slide Layouts section for more information on this procedure.

New Slides (top)
In PowerPoint, whenever you insert a slide into a presentation, it asks you to select a set layout or simply start from a blank one. Modeling our design after that, whenever the user clicks on the “Add Slide” button in the full page controls (which is the bar at the top of the BookMorph), it went to the showLayoutChooser method in the SqueakPoint class which would execute the following line of code:
	(ChooseLayoutMorph newSlideChooser: self) openInWorld.

The ChooseLayoutMorph is a grid of previews of several different types of layouts that we have implemented. In this class, we have a class method named newSlideChooser: that takes in a SqueakPoint object as an argument. This method returns an instance of ChooseLayoutMorph that is used to add new slides to the presentation.



Each of the previews is a button that would call the addSlide: method, which takes in a unique number cooresponding to the selected layout.
addSlide: layoutNumber 
	"Adds a new slide to the presentation."
	| layout slide |
	layout := self getLayout: layoutNumber.
	slide := self getSqueakPoint getPrototypeSlide.
	slide layout: layout.
	slide template: self getSqueakPoint getTemplate.
	self getSqueakPoint addSlide: slide.
	self delete.

This method creates a new Slide, sets the appropriate layout, adds it to the SqueakPoint presentation, and deletes this instance of the ChooseLayoutMorph. It uses the getLayout: method to get an instance of a GenericLayoutMorph corresponding to the layout #. The getLayout: method uses the LayoutFactory to create an appropriate layout.

Editing Slide Layouts (top)
Another feature that our presentation needed to perform was the ability to automatically change the layout of a slide to a different layout, while preserving as much data as possible. In order to do this, a user should click the “Change Layout” button in the full page controls.

Instead of creating a LayoutChooser using the newSlideEditor method like we did earlier, we created a slide layout editor using the following code:
	(ChooseLayoutMorph editLayoutChooser: self) openInWorld.

The class method editLayoutChooser: takes in a SqueakPoint object and returns an initalized ChooseLayoutMorph that will edit the current slide in the SqueakPoint. Instead of going to the addSlide: method, each button click will now call the editLayout: method.
editLayout: layoutNumber 
	"Edits the layout I have."
	| layout slide |
	layout := self getLayout: layoutNumber.
	slide := self getSqueakPoint currentPage.
	slide layout: layout.
	self delete.

In this method, we get the current slide from the SqueakPoint object, set the new layout, and then delete the LayoutChooser.

The Template Class (top)
Another feature that we implemented was the use of a template for the presentation. We decided to implement this by creating a Template class that would contain all of the information that a template should know about. Our templates had two fields, representing the default font color and the background color of our presentation.

Each SqueakPoint object has a template object representing the Template for the presentation. Each Slide has a template: method that takes in a template and applies it to the slide. This method is used when a new slide is created or when the template is changed.

Editing the Template (top)
A user can edit the presentation template by clicking the “Change Template” button in the full page controls. This would call the templateSelector method in SqueakPoint, which would run the following line of code:
	(ChooseTemplateMorph new: self) openInWorld.

This method creates and opens a ChooseTemplateMorph. A ChooseTemplateMorph is a morph containing two drop down lists representing the different fields in the Template.



The user can select the font color and the background color using two drop down lists of the different colors. This morph has two preset templates, Green and Blue templates. These can be set using the green and blue buttons on the ChooseTemplateMorph. Clicking on either of these two buttons or the ok button would call the buttonClicked method.
buttonClicked
	| fontColor backColor template |
	fontColor := self getSelectedFontColor.
	backColor := self getSelectedBackgroundColor.
	template := Template new.
	template setFontColor: fontColor.
	template setBackgroundColor: backColor.
	self getSqueakPoint template: template.
	self delete

This method set the Squeakpoint template to the specified template. The SqueakPoint class will then apply the template to all of its slides.

Launching The Timer (top)
Another one of the nice features of our project was the ability to launch a timer that we created in Milestone #1. In order to launch a timer, the user should click the “Timer” button on the full page controls. This will launch
the timer by calling the insertTimer method, which runs the following line of code:
	HandMorph
		attach: (TimerWidget newInBook: self)



In this segment of code, we create a new TimerWidger, and attach it to the HandMorph. This allows the user to place the TimerWidget wherever he or she wishes. For more information on the timer, see our timer widget case page.

Our code (top)
teamASDF-M3Turnin.tar.gz

Milestone 4: Let's make it not suck this time (top)

For our group, milestone four involved creating a LayoutEditor for SqueakPoint. A Layout organizes data to display on a PowerPoint-like slide. The first thing we realized when starting this milestone is that our design from Milestone Three was extremely complicated as well as inflexible for creating layouts on the fly.

Here were our goals for Milestone 4:
  1. Users generate layouts (duh)
  2. Layouts can consist of ANY type of object
  3. Dynamically generate layout previews (iconic representations of each layout)
  4. Save layouts for future use
  5. Improving the GUI

The bulk of our progress in this milestone has been documented in the above links; what hasn't follows.

Improving the GUI (top)
For Milestone 3 our GUI was really rather simplistic and unintuitive. We had extended the fullControlSpecs method of the BookMorph to add our own buttons to the top of the bookmorph. This was, however, rather ugly, not to mention hard to use. For Milestone 4 we went back to the drawing board, and the result was this:
Uploaded Image: m4slideeditmode.png

We created a toolbar that featured three modes: a "slide editor" mode, a "presentation mode" and a "layout builder" mode. The different modes allowed us to present the program's functionality to the user in manageable, intuitive portions. Slide editor mode was the default (the above screenshot shows the presentation in slide editor mode); it allowed the maximum amount of functionality, including editing all text boxes and image boxes, moving them around, and dragging new ones onto the page. You could also apply a template or a layout in slide editor mode. The layout builder mode added the ability to save the current slide as a layout or delete one of the existing layouts. The presentation mode, on the other hand, significantly reduced the number of available options, as it locked down all the content of the page, making text boxes & images uneditable and undraggable, limiting controls to next page, previous page and the timer. For milestone 5 presentation mode also got an "export to server" button. Keeping in mind the class's emphasis on HCI, we feel our control scheme is both intuitive and powerful; hopefully you can learn a lesson from it.

Here is a screenshot of the presentation mode:
Uploaded Image: m4presmode.png

Weaknesses (top)

The remainder of this case will be dedicated to uncovering the weaknesses in our old M3 design, as well as pointing out the superior strength of the new M4 design. To make things easier to understand, we have posted the both M3 and M4 UML diagrams.

Uploaded Image: m3UMLthumb.jpgUploaded Image: m4UMLthumb.jpg
Milestone 3 UMLMilestone 4 UML


As you can see, our design for Milestone 3 involved borderlayouts being nested an arbitrary number of times. Additionally, each nested border layout could hold 0, 1, or 2 different morphs. If you can't see already, there are many problems with this design. First and foremost, the design is too complicated. When you make a design, if at all possible, follow the KISS principle - "keep it simple stupid". With a design as complex as Milestone 3, how could we expect to create layouts on the fly? Next, the design we had relied on layouts being symetric; this flaw obviously limits the users ability to create their own layouts.

The only solution we could provide involved a complete overhaul of our layouts' design. With surprizingly little effort, we layed the groundwork for a very flexible layout editor. The greatest part about the design was its simplicity. The code we wrote for this section was smaller than our code for milestone 3, and it worked better too! Not only can users organize layouts any way they want, but they can also add extra stuff like rectangle borders to make the formatting look nicer. Below is a picture of a layout a user might create. Our design allows a user to create a layout from any slide in their presentation (like making a template of a slide).

Uploaded Image: contentlayout.pngUploaded Image: layoutexample.png


The entire principle of our Milestone 4 design lay in the use of class names. A layout was composed of two a Layout and LayoutProperties. A Layout of course was composed of many LayoutProperties. Each property knows which type of morph it represents. Additionaly, a property knows its size, position, and color. That is all the information needed to duplicate a basic morph. Recreating a layout is just a matter of putting all of the properties on a slide. Easy!

In order to apply a layout, the user should select one from a list, but the user should not have to remember what the layout looks like. We need to make a preview of each layout. The problem? We don't know what the layout looks like. Lucky for us, PasteUpMorph has a built-in function that takes a miniature picture of itself. Check out the code.

The only thing left to do is save layouts. We already made a class LayoutManager that held an instance of all the layouts a user has created. At first we spent a few hours writing text parsers and asString functions four our layout objects. BAD! Squeak already has a way to save objects. We just saved the entire LayoutManager to a file. Whenever we opened another Squeak Point, we could bring it back to life as if were never closed.

As stated earlier, the bulk of the usefull information from this project can be found in the how-to section, but hopefully you have already noticed the links peppered throughout this post :)

Our code (top)
TeamASDF-M4-Turnin.tgz

Milestone 5: Putting it online (top)
The point of Milestone 5 was to allow users to export presentations for remote access over the web. We served up our presentations using the Comanche server that runs on Squeak.

We should note that our motto for creating and designing this milestone was "use Squeak as little as humanly possible." In the name of this, our 'export' function exported all the information we may need about a given presentation. Once a presentation was exported the user could very easily remove the entire presentation from Squeak and it would still be accessible via the internet unless the user specifically deleted it from the server, and every function we provided via the internet would still work. The downside of this approach is that the version of the presentation available online is not automatically updated every time a presentation is altered. Rather, the user has to re-export the presentation each time he wants to post a new version.

Now, let's break down the way we accomplished some of our stuff.

SqueakPointServerManager class: Managing the server (top)
We decided early on that we would need a morph to control our server, letting us start and stop serving pages, as well as delete the presentations that were already created. So we, uh, made one.
Uploaded Image: m5-serverManager.png

The functionality of the buttons is pretty self-explanatory; you can see some detail behind the server code in our description of how we worked with comanche. This also accessed our delete presentation screen, which dynamically generated a list of all the directories in /projects and offered each of them up for deletion:
Uploaded Image: m5-deletePresentation.png

Exporter class (top)
Exporting the presentation generates the following things:
We decided that we were going to store all of our server-accessible presentations in a single directory, ./projects. To export into this directory, we used the exportToServer method, reproduced below. This class method takes in a SqueakPoint for export and is the central driver for writing the images, HTML pages and metadata out to the web. The method is initiated by hitting a button in the Presentation Mode (Export to Server).
exportToServer: squeakPoint
	| pages ans filepath filename text |
	pages := squeakPoint pages.
	text := ''.
	ans := FillInTheBlankMorph request: 'What would you like to call the presentation?'.
	"It turns out squeak is retarded, so we're going to fix our problem with  
	a workaround by getting the specific path to the location of the Squeak 
	image and going from there."
	filepath := FileDirectory on: FileDirectory default pathName , '/projects/' , ans asString , '/'.

	filepath exists
		ifTrue: [filepath recursiveDelete]. "If a folder with this name already exists, delete it."
	Transcript show: filepath.
	filepath assureExistence. "This will create the directory if it does not already exist."
	squeakPoint setToPresentationMode.
	"Here we're going to write out all of the images of the SqueakPoint;
	the images within the squeakpoint; and all of the text from each textbox within the Squeakpoint
	into a file."
	pages
		do: [:each | 
			filename := './projects/' , ans asString , '/' , 'slide' , (squeakPoint pageNumberOf: each) asString , '.jpeg'.
			Transcript show: filename.
			each imageForm writeBMPfileNamed: filename.
			(each morphsOfType: InsertImageMorph)
				do: [:image | image originalForm writeBMPfileNamed: './projects/' , ans asString , '/' , image name , '.jpeg'].
			(each morphsOfType: TextBoxMorph)
				do: [:textBox | text := text , textBox contents asString , ' '].
			text := text , '`'].
	text
		storeOn: (FileStream fileNamed: './projects/' , ans asString , '/text.txt').
	"Now we're going to generate and write the HTML pages for the SqueakPoint."
	squeakPoint writeHTMLFiles: ans.<img src="http://coweb.cc.gatech.edu:8888/cs2340/uploads/5080/m4UML.jpg" width=944 height=730 alt="Uploaded Image: m4UML.jpg">
	"And now we'll regenerate the index file dynamically based on the existing presentations."
	Exporter generateIndexFile

Once this method executes, the presentation can be accessed via the server.

Searcher (top)
The searcher class takes in a project name and then reads in a special metadata file, "projects/project_name/text.txt", which contains the text of all the textboxes in the presentation split up by slide. It searches this file and then returns an ordered collection of slides on which the search term was found. Here is the method it uses:
searchFor: phrase projectName: pname 
	| ret text subtext |
	ret := OrderedCollection new.
	text := (FileStream fileNamed: './projects/' , pname , '/text.txt') contentsOfEntireFile.
	subtext := text
				subStrings: (OrderedCollection new add: '`').
	1
		to: subtext size
		do: [:i | ((subtext at: i)
					findString: phrase)
					> 0
				ifTrue: [ret add: i]].
	^ ret

The OrderedCollection this method returns is used to generate an HTML page of search results, which is served up to the user.

HTML Generation (top)
There's not a whole lot to HTML generation, really. The only interesting thing we did is generate image maps, and that's probably not going to be useful for anyone who's taking this class in the future. If you're curious, check out the HTMLLink and HTMLPage classes.

Our code (top)
TeamASDF-M5-Turnin.tgz

A demo (top)
We've also put up a live demo of one of our presentations, available here. Please note that since this is just served as regular HTML, the searching does not currently work.



How do I...


File I/O in Squeak (top)
The FileStream class allows us to write or read from a file. The put: method appends a character to the end of the stream.
text do: [:each| stream put: each].
In this piece of code, text is a string, and stream is an instance of the FileStream class. This line of code individually takes every character of the string and writes it to the file.
stream contentsOfEntireFile.
In this line, stream is an instance of FileStream, and this returns a string with the contents of the file.
Also, the FileStream class has a fileOutClass:andObject: method that outputs the information for a specified object into the file. This is similar to the Serializable interface in Java, as it outputs all of the information for the object into the file.
stream fileOutClass: nil andObject: obj.
To retrieve the object from the file, the fileInObjectAndCode method of FileStream returns a Collection of all of the objects in one file.
stream fileInObjectAndCode.

Serving static pages with Comanche (top)
There are several useful guides on the CoWeb for getting Comanche up and running (we used the one here and it worked quite well), but one thing we had significant problems figuring out was how to get plain Comanche to serve up static files that we had somewhere in the file system. We wound up working out our own methods; while I dearly hope they are not the most efficient way to accomplish what we wanted to using Comanche, since they're kind of backwards, they DO work and they are able to handle any static file you want to serve up. Also, please note that these methods are only known to work in Squeak 3.7, as apparently some of the features we relied on are broken in Squeak 3.8. (Don't you just love that?)

The main handler: Processing requests (top)
For the intents of this process, I'm going to assume that one of the things you did when starting your server is execute a line like this:
	ma
		addPlug: [:request | "setup action on to trigger on Http requests"
			self processHttpRequest: request].
As you can see, this will call the method "processHttpRequest:" and pass the request (which is an object of type HTTPRequest) into it. This method is where you are going to do all the interesting things to handle your code.

First of all, however, let's consider some of the useful fields that we have in an HTTPRequest. Here's an excerpt from the class comment on HTTPRequest:
The 'url' parameter of the request is what we use to determine what page to show the user. The queryString, on the other hand, will come in handy later.

Now, let's actually look at the processHttpRequest: method:
processHttpRequest: request 
	"Will either generate a dynamic search result page or pass things to  
	another method to serve static pages"
	"the only dynamic page generation we need"
	request isPostRequest
		ifTrue: [(request url endsWith: 'search')
				ifTrue: [^ (self serveSearchResults: request)
						asHttpResponseTo: request]].
	"handle text file request cases"
	(request url endsWithAnyOf: #('.html' '.htm' '.php' '.txt' '.css' ))
		ifTrue: [^ (self serveText: request url)
				asHttpResponseTo: request].
	"handle binary request cases"
	(request url endsWithAnyOf: #('.png' '.jpg' '.jpeg' '.gif' '.bmp' ))
		ifTrue: [^ HttpResponse
				fromFileStream: (self serveBinary: request url)].
	"handle default index"
	(request url compare: '/')
			== 2
		ifTrue: [^ (self serveText: '/index.html')
				asHttpResponseTo: request].
	"handle unrecognized messages"
	^ 'You ask for weird shit<br>server have nothing for you.' asHttpResponseTo: request
As you can tell, there are four major cases handled by this method, as well as a catchall for requests the server does not understand. The searching case has its own section dedicated to it. Let's look at the other three cases separately.

Serving existing ASCII files (top)
Installing Comanche automatically edits several built in system classes to add an "asHttpResponseTo:" method. One of the more significant objects that is edited is the String object, which allows us to serve up any string to the user's web browser. When that string happens to be HTML, the browser treats the page like an HTML page. Here's the way we do it in code:
serveText: url 
	"Serve a static html page."
	| retpath filecontents textInFile |
	"Get the path of the file we want to show."
	retpath := FileDirectory on: basepath pathName , url.
	"return 404 page if the real page does not exist."
	(retpath fileExists: retpath fullName)
		ifFalse: [retpath := FileDirectory on: basepath pathName , '/404.html'].
	"Generate a file stream and read it into a string, then return it."
	filecontents := FileStream fileNamed: retpath fullName.
	textInFile := filecontents contentsOfEntireFile.
	^ textInFile
The string object that this method returns then has the "asHttpResponseTo:" method called on it, and the user's browser displays the specified HTML page.

Serving existing binary files (top)
Binary files are handled somewhat differently from text files, as we cannot simply serve them as strings. Rather, in this case we generate an HttpResponse object using the instantiation class method in the HttpResponse class, "fromFileStream:". We get the FileStream we want to serve from the "serveBinary:" method, reproduced here:
serveBinary: url 
	"Serve binary files."
	| retpath filecontents |
	"Get the path of the file we want to show."
	retpath := FileDirectory on: basepath pathName , url.
	"If the file exists, return a FileStream to it, otherwise return a FileStream to our 'not found' image."
	(retpath fileExists: retpath fullName)
		ifTrue: [filecontents := FileStream fileNamed: retpath fullName.
			^ filecontents]
		ifFalse: [retpath := FileDirectory on: basepath pathName , '/404.png'.
			filecontents := FileStream fileNamed: retpath fullName.
			^ filecontents]
We used this code to serve images, but there's no real reason that it can't handle any other type of binary file.

Serving a default index page (top)
Sometimes there are cases where the user will just type the address of your server and not provide the specific page he wants to access (such as when you type "http://www.google.com/" instead of "http://www.google.com/index.html"). To handle this case, we had a rather basic check in our server. By checking if the "url" parameter of "request" was "/", we were able to get our server to serve up the index.html page in these cases. That's basically all there is to serving static pages with Comanche.

Taking textual input with Comanche without messing with Postdata (top)
One of the things we had to handle for our project was searching the presentation, which is covered in some detail in our description of milestone 5. Doing the actual search and generating an HTML page of results was not particularly difficult, though. The harder part was getting the text the user wanted to search for into Squeak. While we've covered the dynamic page generation as well for the sake of completeness, let's begin with the trick we used to get the user's search string in a format we could use.

Cheating with JavaScript (top)
In squeak, each HTTPRequest may be a PostRequest, which is generated when a form is submitted. Unfortunately, we were unable to figure out how to get the data contained in this request. To compensate, we decided to cheat with JavaScript.

The simple javascript we wrote ran every time a user clicked the "search" button. It took the contents of the search box and changed the action attribute of the form to be "search?search_term".
<script language="JavaScript" type="text/JavaScript">
	function doSearch() {
		ourText = document.getElementById("search");
		ourVal = ourText.value;
		ourForm = ourText.form;
		ourForm.action = "search?" + ourVal;
	}
	</script>
If you'll recall the description of serving static pages in Comanche, the portion of the URL beyond the question mark is accessible using the queryString attribute of an HttpRequest.

This simple script let us avoid hours of further research into postdata, which is pretty much not documented anywhere.

Generating a dynamic page (top)
Here is how we dealt with the queryString and generated our results page:
serveSearchResults: request 
	"Generate and serve up the results page for searches."
	| projname urlbits searchMatches retString |
	"Get the project name from the URL."
	urlbits := request url
				subStrings: (OrderedCollection new add: '/').
	projname := urlbits at: 1.
	"Execute the search."
	searchMatches := Searcher searchFor: request queryString projectName: projname.
	"Now generate the HTML based on the results."
	retString := '<html>
 	 <head>
 	   <title>Searched for:' , request queryString , '</title>
  	  <meta content="">
    <link rel="stylesheet" type="text/css" href="../style.css">  	 
 	 </head>
 	 <body>'.
	searchMatches isEmpty
		ifTrue: [retString := retString , request queryString , ' was not found. </br><a href="javascript:history.go(-1)">Go back.</a><br/>']
		ifFalse: [retString := retString , 'You searched for ' , request queryString , '<br/>'.
			searchMatches
				do: [:match | retString := retString , '<a href="slide' , match asString , '.html">Slide ' , match asString , '</a><br/>']].
	retString := retString , '</body></html>'.
	^ retString
The only portion of the code that is not self explanatory is getting the project name from the URL. We needed to do this so the searcher would know what folder this presentation's metadata text file was located in.

Another JavaScript cheat (top)
Here's a very simple script that we used to get our "go to slide" dropdown working. The result of the script is that there is no server-side code to handle the dropdown at all; it simply works like any other link would.
<script language="JavaScript" type="text/JavaScript">
function goToURL() {
	goToBox = document.getElementById("pages");
	targetPage = goToBox.selectedIndex + 1;
	goToForm = goToBox.form;
	goToForm.action = "slide" + targetPage + ".html";
	goToForm.submit();
}
</script>
That's all there is to it.

Making use of class names (top)
Using "Class" objects in squeak can be very powerful. There may be cases where you do not know what object you are creating. That is you can generate an object by sending the "new" message to a Class object. This already sounds too complicated to be useful, so here are some examples.

Select all submorphs of a certain type from another morph (top)
This comes in handy with paste-up morphs (which are used to hold any type of morph). The "world" is actually a paste up morph. Instead of writing separate functions that are specific to certain types of classes, you can write a single generic function that takes in a Class type as a parameter. Check out this function that selects all morphs of a certain type.
morphsOfType: morphKind
	"Returns all submorphs of the givn type."

	^ submorphs select: [: aMorph | aMorph is KindOf: morphKind].
This is a method from one of our group's classes that extended Paste-up morph. Here's a step by step break down of the code:
"morphsOfType: morphKind" can be called using "someInstance morphsOfType: RectangleMorph", specifying what kind of class to search for.
In "submorphs select: aBlock", "submorphs" is an OrderedCollection object. The "select" message will iterate through all elements in the submorphs collection. If the element meets the condition specified in "aBlock", that element is added to the return array.
"[: aMorph | aMorph isKindOf: morphKind]" is the conditional statement tested on every element in "submorphs". Think of it as a miniature function. "[: aMorph |" is the parameter to the function, and "aMorph isKindOf: morphKind" is the body which returns true if the morph passed in is the same kind of class as the one we are looking for (namely "morphKind").

Generating classes at runtime (when you haven't specified what to generate) (top)
Here is the question our group ran into during milestone four: "How do we write a Layout that can hold any type of Morph, while still being able to generate the Layout on the fly?" By using class names, the answer is fairly straight foward. Each Layout stores a list of the classes that it generates. Note that I have left out some implementation details such as storing position and extent for the sake of simplifying this tutorial.

The following example code creates an array of class names, then opens them in the world.
| classArray |

classArray := OrderedCollection new.
classArray add: RectangleMorph.
classArray add: EllipseMorph.
classArray add: BouncingAtomsMorph.
classArray add: TrashCanMorph.


classArray do: [: currentClass | currentClass new openInWorld].
Hopefully this example is self explanatory. The first six lines create an array of class names. The last line iterates through the array and performs the block on each element of the array. The block merely calls new on each class in the array.

Applying this concept to dynamic layout creation is easy. A user selects which morphs to use in a layout. Layout saves which classes they have selected. Therefore Layout can duplicate itself at any point in time.

Squeak saving/basics (top)
One of the problems we occasionally had in the early parts of the semester was the failure to save material. Several times, we had lost code that we had written because of this reason. In most of these cases, we either forgot to save material or had the Squeak VM crash before we could save material.

Squeak provides us with several ways to save material. One of these ways is to fileout the code that you wrote. One can file out an entire category, a class, a method category, or just a single method of a class.

Uploaded Image: fileOutScreen.png

One can then retrieve this code using the FileList and clicking the "File In" button.

Uploaded Image: fileInScreen.png

Another way to save material is by saving the image. By doing this, everything that you have done is saved, including code and all of the current morphs on the screen. A squeak image can be retrieved by supplying it as a command line argument when launching squeak.

Uploaded Image: saveImageScreen.png

Using step (top)
step is a method that is in the morph class. It is a method that is called every n milliseconds, and should be used when a user wants to constantly update something about the morph. The stepTime method defines the amount of time between succesive calls of step. By default, if the stepTime method , the stepTime is 1000 milliseconds.
step
	super step.
	self doProcess.

stepTime
	^ 100.

In this example, the stepTime method tells us that step will be called every 100 milliseconds. In the step method, we call the doProcess method. As a result, the doProcess method in this class will be called every 100 milliseconds.

Suppose a programmer is trying to create a clock morph that displays the current time. This morph should constantly update itself to display the most current time. One way to implement this feature is by using the step method. Inside the step method, the programmer could write code to update the current time. The stepTime method could return 1000, specifing that the clock will be updated every second. This clock timer will now show the current time, and update it each second.

Making and using previews (top)
Using the
thumbnailForPageSorter
method of PasteUpMorph, one can create a small thumbnail of a PasteUpMorph. This was useful to us when we were trying to supply the user with previews of the saved layouts in our layout builder. The method returns a BookPageThumbnailMorph that represents a thumbnail of the PasteUpMorph.

The following image shows a thumbnail (circled in red) that was generated of the world. You can do it too! Open an inspector of the world. Run
self thumbnailForPageSorter openInWorld


Uploaded Image: thumbnails.png



General tips

Suggestions for getting through the class (top)

Useful links (top)
Here is a collection of useful links from Discussion 3:
A few other useful links:


Links to this Page