Chapter 6. Windows, Views, and Controls

All of the objects that you interact with on your computer screen are displayed within windows. This includes what we consider “normal” windows (those with titlebars and controls), as well as menu items, pop-up contextual menus, floating palettes, sheets, drawers, and the Dock.

Windows and the Window System

Two interacting systems create and manage Cocoa windows. On one hand, Mac OS X’s window server creates a window and displays it on screen. The window server is a process that uses Quartz—the low-level drawing system—to draw, resize, hide, and move windows. As depicted in Figure 6-1, the window server also detects users events (such as mouse clicks or keyboard key presses) and forwards them to applications.

Cocoa and the window server
Figure 6-1. Cocoa and the window server

On the other hand, the window created by the window server is paired with an object supplied by the AppKit—an instance of the NSWindow class. Each physical window in a Cocoa program is managed by an instance of NSWindow or a subclass. As shown in Figure 6-2, when an NSWindow object is created, the window server creates the physical window being managed. The window server references the window by its window number and the NSWindow object instance by its own identifier.

NSWindow objects and window server windows
Figure 6-2. NSWindow objects and window server windows

Window, View, and Application

Three classes explicitly define the functionality at the core of a running application: NSWindow , NSView , and NSApplication . Each class plays a critical role in drawing the user interface of the application and directing user events to the various parts of a program. Each class inherits functionality from the NSResponder and NSObject classes, as shown in Figure 6-3. The structure of their interaction is sometimes called the " core program framework.”

The core program framework
Figure 6-3. The core program framework
NSResponder

NSResponder is an abstract class that enables event handling in all classes that inherit from it. It defines the set of messages invoked when different mouse and keyboard events occur. It also defines the mechanics of event processing among objects in an application. We’ll cover events in more depth in Chapter 8.

NSWindow

An NSWindow object manages each physical window on the screen. It draws the window’s frame area and responds to user actions that close, move, resize, and otherwise manipulate the window. The main purpose of an NSWindow is to display an application’s user interface, or at least a part of it, in its content area. The content area is that space below the titlebar and within the window frame.

NSWindow allows you to assign a custom object as its delegate to participate in its activities. This allows you to add application-specific window functionality to your application without requiring knowledge of the NSWindow class internals.

NSView

Any object you see in a widow’s content area is an instance of a subclass of the NSView class. Each view owns a rectangular region associated with a particular window. A view produces the image content for that region and responds to events occurring within it.

Graphically, a view can be regarded as a framed canvas. The frame locates the view in its superview, defines its size, and clips the drawing to its edges. The frame can be moved around resized, and rotated in the superview. Within the frame is the bounds of the view—the rectangle within which the view draws itself.

Views draw themselves as an indirect result of receiving the display message or one if its variants. This message leads to the invocation of a view’s drawRect: method and the drawRect: methods of all subviews of that view. The drawRect: method should contain all the code needed to redraw the view completely.

NSApplication

Every application has exactly one NSApplication object instance to supervise and coordinate the overall behavior of the application. This object dispatches events to the appropriate windows, which, in turn, distribute them onto their views. The application object manages its windows; it also detects and handles changes in their status, as well as its own active and inactive status. The application object is represented in each application by the global instance variable NSApp.

Key and Main Windows

Windows have numerous characteristics, the first of which being that they can be onscreen or offscreen. Onscreen windows are layered on the screen in tiers managed by the window server. Onscreen windows also can carry a status: key or main. Offscreen windows are hidden or minimized to the Dock and are not visible on the screen.

Key window

The key window responds to key presses for an application and is the primary recipient of messages from menus and dialog boxes. Usually a window is made key when the user clicks it. Each application can have only one key window at a time.

Main window

The main window is the principle focus of user actions for an application. Often user actions in a modal key window (typically a dialog box, such as the Font dialog or an Info window) have a direct effect on the main window. Main windows often have key status.

The Window Menu

Cocoa applications usually include a Window menu in the menu bar at the top of the screen. The Window menu automatically lists the windows that have a titlebar, are resizable, and can become the main window. When a window’s title is changed, that new title is reflected in this menu. Figure 6-4 shows a Window menu in Project Builder, with two open windows.

Window menu in Project Builder, with two open windows
Figure 6-4. Window menu in Project Builder, with two open windows

Tip

Not all applications have a Window menu, but it is automatically provided by Cocoa, and you should always use it. Also note that a list of an application’s windows can be obtained by Control-clicking on the application’s icon in the Dock.

Panels

A panel is a special kind of window that usually serves some auxiliary function in an application. For example, much of the functionality of Interface Builder, such as the view’s palette and inspector, is implemented using panels. To support the roles they typically play, panels differ from windows in the following ways:

  • To reduce screen clutter, an application’s panels—except for attention panels—are displayed only when the application is active. For example, when you have more than one application running, only the panels for the active application are in the foreground.

  • Panels can become the key window, but never the main window. For example, when working in Photoshop, you have a main window where you create and edit images. The other panels, such as the Layers panel, are open but not active (or key) until they are clicked; the focus then changes to that panel.

The user can close a panel that is the key window by pressing the Escape key (if the panel has a close button).

The View Hierarchy

Inside of each window—inside the area enclosed by the titlebar and the other three sides of the frame—lies the content view . The content view is the root, or top, view in a hierarchy of views that belongs to the window. Like a tree, one or more views may branch from the content view. For example, each button, text field, and label in the Currency Converter application from Chapter 5 is a view located within the content view of the window, as illustrated in Figure 6-5. Enclosure determines the relationship between each view and its subviews.

Currency Converter views contained by the content view
Figure 6-5. Currency Converter views contained by the content view

The core program framework provides several ways for your application to access the participating objects, so you need not define outlets or instance variables for every object in the hierarchy.

  • By sending the appropriate message to the NSApp global variable, you can obtain the application’s NSWindow objects.

  • You can get the content view of a window by sending it the contentView message. From the returned NSView object, you can get all subviews of the view.

  • You can obtain from an NSView instance most of the objects that it references. For example, you can discover its window, its superview, and its subviews.

The relationship between these parts of an application’s view hierarchy is shown in Figure 6-6.

Hierarchical relationship between major view hierarchy components
Figure 6-6. Hierarchical relationship between major view hierarchy components

Coordinate Systems

Positioning of windows and views, as well as the correct propagation of events within them, requires the use of a set of coordinate systems. These systems then help locate objects in relation to the various parts of the onscreen display. The following three types of coordinate systems are used by Cocoa:

  • Screen coordinates

  • Window coordinates

  • View coordinates

Screen Coordinate System

The screen coordinate system is the basis for all other coordinate systems. Think of the entire screen as occupying the upper-right quadrant of a two-dimensional coordinate grid, as shown in Figure 6-7. The screen quadrant has its origin in the lower-left corner. The positive x-axis extends horizontally to the right, and the positive y-axis extends vertically upward. Each unit in the coordinate system represents an on-screen pixel.

Screen coordinate system
Figure 6-7. Screen coordinate system

Tip

Although Figure 6-7 represents the coordinate system using a single display device, the screen coordinate system is really a logical rectangular union of all the screen rectangles of all physical frame buffers attached to the computer. The origin lies at the lower-left corner of that unioned rectangle.

The screen coordinate system has just one function: to position windows on the screen. When your application creates a new window, it must specify the window’s initial size and location in screen coordinates.

Window Coordinate System

The window coordinate system defines the coordinates used within a single window on screen. It differs from the screen coordinate system in only two ways:

  • It applies only to a particular window. Each window has its own coordinate system.

  • Its origin is at the lower-left corner of the window. If the window moves, the origin and the entire coordinate system move with it.

View Coordinate System

For drawing, each view uses a coordinate system transformed from the window coordinate system, or from its superview in the case of a contained view. This coordinate system has its origin point in the lower-left corner of the view, as shown in Figure 6-8, making it convenient for drawing operations.

Window and view coordinate systems
Figure 6-8. Window and view coordinate systems

This set of coordinate systems has several implications that are important in the layout and drawing of user-interface elements:

  • Subviews are positioned in the coordinates of their superview.

  • Each view’s coordinate system is a transformation of its superview’s system.

  • When a view is moved or transformed, all subviews are moved or transformed in concert.

  • Because a view has its own coordinate system for drawing, drawing instructions remain constant, regardless of any change in position of itself or its superview.

Controls, Cells, and Formatters

Controls are the user-interface objects that enable users to signal their intentions to an application and control what happens. Cells are rectangular areas embedded within a control. Each control can have one or more cells, allowing a single control to have multiple active areas. Figure 6-9 shows the relationship between controls and cells.

Controls and cells
Figure 6-9. Controls and cells

Controls and cells lie behind the appearance and behavior of most user-interface objects in Cocoa, including buttons, text fields, sliders, and browsers. Although they are quite different types of objects, they interact closely. Controls are responsible for the following:

  • Displaying the control to the user

  • Accepting user events, such as clicking or typing

  • Sending actions to other objects in response to a user event

A control usually delegates the first two responsibilities to cells. Cells, which are subclass instances of the NSCell class, let you display text or images in a view without the full overhead of an NSView subclass. This allows for greater flexibility when creating a control, such as a spreadsheet table, with many identical elements.

The controls that Cocoa provides fall into the categories listed in Table 6-1.

Table 6-1. Cocoa’s NSView controls

Control

Description

Boxes

Group together other views, including controls, in an area that can have a border and title

Browsers

Display a list of data and allow the user to select items

Buttons

Send an action message to a target when clicked

Combo Boxes

Allow a user to enter a value either by entering it directly into a text field or choosing it from a pop-up list of preselected values

Forms

Group a related set of text fields

Image Views

Display a single image in a frame and, optionally, allows a user to drag an image to it

Matrices

Group cells that work together in various ways, such as radio buttons

Outline Views

Display hierarchical data to let the user expand or collapse rows

Progress Indicators

Show that a lengthy task is underway and, optionally, can display how much of that task is complete

Sliders

Display a range of values and have an indicator, or knob, indicating the current setting

Steppers

Increment or decrement a value, such as a date or time, that is displayed next to them

Tab Views

Group views on multiple pages together into one user-interface element

Table Views

Display a set of related records, with rows representing individual records and columns representing the attributes of those records

Text Fields

Display text that a user can select or edit

Text Views

Allow the editing of text

Controls act as managers of their cells, telling them when and where to draw and notifying them when a user event occurs in their areas. This division of labor, given the relative “weight” of cells and controls, conserves memory and provides a great boost to application performance. For example, a matrix of buttons can be implemented as a single control with many cells instead of as a set of individual controls.

A control does not need a cell associated with it, but most user-interface objects available in Cocoa are cell-control combinations. Even a single button—from Interface Builder or programmatically created—is a control (an NSButton instance) with an associated cell (a NSButtonCell instance).

The cells in a control such as a matrix must be the same size, but they can be of different classes. More complex controls, such as table views and browsers, can incorporate various sizes and types of cells. Most controls that use a single cell, such as NSButton, provide convenience methods so you don’t have to deal with the contained cell directly.

Cells and Formatters

When looking at the contents of cells, it is natural to consider only text (NSString) and images (NSImage). The content seems to be whatever is displayed. However, cells can hold other kinds of objects, such as dates (NSDate), numbers (NSNumber), and even application-supplied custom objects, which are shown in the user interface as strings.

One way to make your application’s user interface more attractive is to format the contents of fields that display currencies and other numeric data. Fields can have fixed decimal digits, limit numbers to specific ranges, have currency symbols, and show negative values in a special color.

Formatters are objects that translate the values of certain objects to specific on-screen representations. Formatters can also convert a formatted string on a user interface into the represented object. For example, Figure 6-10 shows how a date formatter translates the contents of an NSDate object into a specific string for display.

A date formatter
Figure 6-10. A date formatter

You can create, set, and modify formatter objects programmatically or with Interface Builder. Formatter objects handle the textual representation of the objects associated with the cells and translate what is typed into a cell to the underlying object. You can attach a formatter object to a cell in Interface Builder or use the setFormatter: method of NSCell to associate a formatter with a cell programmatically.

A Formatted Cell Example

To show formatters in action, we’re going to create a simple application that shows the current date and time in a text field. In Project Builder, create a new Cocoa Application (File New Project Application Cocoa Application) named “Simple Date”, and save it in your ~/LearningCocoa folder.

Open the main nib file

Begin by opening the application’s main nib file in Interface Builder:

  1. In Project Builder’s Groups & Files pane, click on the disclosure triangle next to Resources to reveal the MainMenu.nib file.

  2. Double-click on the nib file to open it in Interface Builder.

A default menu bar and window will appear when the nib file is opened.

Create the user interface

Set the size and initial location of the application’s main window by resizing and moving the window in Interface Builder

  1. Move the window near the upper-left corner of the screen by dragging its titlebar.

  2. Make the window smaller using the resize control at the bottom-right corner of the window, as shown in Figure 6-11.

Cocoa window with resize control
Figure 6-11. Cocoa window with resize control

Add a text field

Now, add a text field object to the application’s window.

  1. Select the Views palette by clicking the second button from the left in the toolbar of the Cocoa Views window, as shown in Figure 6-12.

    Interface Builder’s Views palette
    Figure 6-12. Interface Builder’s Views palette
  2. Drag a text field object onto the window.

  3. Resize the text field to make it wider, using the handles on the text field, as shown in Figure 6-13.

    Resizing a text field
    Figure 6-13. Resizing a text field

Create a controller

We’ll create a very simple controller, MyController, which will be a subclass of NSObject. To define it:

  1. Click the Classes tab of the MainMenu.nib window.

  2. Select NSObject from the list of classes.

  3. Press Return to create a new subclass of NSObject, and rename it MyController.

Define an outlet

Now the controller needs a way to send messages to the text field in the main window. Use Interface Builder to create an outlet for that purpose.

  1. Select the MyController class in the Classes window.

  2. Open the Show Info window (Tools Show Info, or Shift-

    image with no caption

    -I), and select Attributes in the pull-down menu.

  3. In the Outlets tab, click the Add button, and add an outlet named textField (as shown in Figure 6-14); enter the name, and press Return.

Adding an outlet
Figure 6-14. Adding an outlet

Generate a controller instance

As the final step of defining the controller in Interface Builder, create an instance of the MyController class.

  1. Select MyController in the Classes pane of the MainMenu.nib window.

  2. Choose instantiate from the Classes menu (Classes Instantiate MyController, or Option-

    image with no caption

    -I).

When you instantiate a class (that is, create an instance of it), Interface Builder switches to the Instances pane and highlights the new instance, as shown in Figure 6-15. The instance is named after the class.

Instances pane showing a Controller object instance
Figure 6-15. Instances pane showing a Controller object instance

Tip

In fact, the instantiate command does not generate a true instance of MyController. It creates a proxy object used within Interface Builder for defining connections to other objects in the nib file. When the application is launched and the nib file’s contents are loaded, the runtime system creates a true instance of MyController and uses the proxy object to establish connections to other objects.

Connect the controller to the interface

Now that you have created an instance of MyController, you can use it to declare a connection between it and the text field you created earlier.

  1. In the Instances panel of the MainMenu.nib window, Control-drag a connection line from the MyController instance to the text field. When the text field is outlined, as shown in Figure 6-16, release the mouse button.

    Connecting the instance to the text field
    Figure 6-16. Connecting the instance to the text field
  2. Interface Builder brings up the Connections pane of the Show Info window, as shown in Figure 6-17.

  3. Select textField, and click the Connect button.

    Connections pane of the Show Info window
    Figure 6-17. Connections pane of the Show Info window

Generate the source files

Generate the source files so that we can add our controller code and run the application.

  1. Go to the Classes tab of the MainMenu.nib file window.

  2. Select the MyController class.

  3. Choose Create Files from the Classes menu (Classes Create Files for MyController, or Option-

    image with no caption

    -F).

Interface Builder displays the dialog box shown in Figure 6-18.

The Create Files dialog box
Figure 6-18. The Create Files dialog box
  1. Verify that the checkboxes in the Create column next to the .h and .m files are selected.

  2. Verify that the checkbox next to Simple Date is selected in the Insert into targets column.

  3. Click on the Choose button.

  4. Save the nib file (File Save, or

    image with no caption

    -S).

Now that we’ve built the basic interface, we can leave Interface Builder and switch to Project Builder to complete the application. Click on Project Builder’s icon in the Dock to leave Interface Builder.

Statically type the outlet

By default, outlet declarations are dynamically typed using the id keyword. You can use id as the type for any object, meaning that the class of the object is determined at runtime. When you don’t need a dynamically typed object, you can—and should—statically type it as a pointer to an object. It takes a little extra time, but it is good programming practice. Static typing also allows the compiler to perform type checking, potentially saving you debugging time later.

When you look at the source code for MyController.h, note that generic outlets are declared as follows:

IBOutlet id variableName;

There are two ways to type outlets. The first is to indicate the type in Interface Builder. Take another look at Figure 6-14, and notice the type pull-down as part of the textField outlet definition. You can use the pull-down to select which type of object the outlet should be typed as. The other way is to change the type in the header file. To do this, use the following steps:

  1. In Project Builder, select MyController.h in the Other Sources folder in the left pane.

  2. Change the declaration in MyController.h to match the code shown in Example 6-1. Don’t forget to add the pointer star!

    Example 6-1. MyController header file with a statically typed outlet
    /* MyController */
    
    #import <Cocoa/Cocoa.h>
    
    @interface MyController : NSObject
    {
        IBOutlet NSTextField * textField;
    }
    @end

Implement the awakeFromNib method

When an application is launched, the NSApplicationMain function loads the main nib file. After a nib file has been completely unpacked and its objects connected, the runtime system sends the awakeFromNib message to all objects derived from information in the nib file, signaling that the loading process is complete. All object’s outlets are guaranteed to be initialized when awakeFromNib is called. This lets objects in the nib file do any extra setup required before the user or the rest of the application attempts to interact with them.

In this application, we’ll use the awakeFromNib method to print the current time to the text field in the main window.

  1. In the left pane, click on MyController.m in the Other Sources folder.

  2. Edit the MyController.m file to match the code shown in Example 6-2.

    Example 6-2. Adding the awakeFromNib method
    #import "MyController.h"
    
    @implementation MyController
    
    - (void)awakeFromNib
                                  {
                                      [textField setObjectValue:[NSCalendarDate date]];
                                  }
    @end
  3. Save the project (File Save, or

    image with no caption

    -S).

  4. Build and run the application (

    image with no caption

    -R). You should see a window that resembles Figure 6-19.

  5. Quit the application.

Simple Date application
Figure 6-19. Simple Date application

Add the formatter

Wait a minute . . . our date looks really nerdy. Instead of this representation for the date, we want to make a nicely formatted date. To do this, switch back to Interface Builder to perform the following steps:

  1. Drag a date formatter from the Views palette to the text field, as shown in Figure 6-20.

    Adding a date formatter to a text field
    Figure 6-20. Adding a date formatter to a text field
  2. While the text field is selected, bring up the Show Info window (Shift-

    image with no caption

    -I) if it isn’t open already.

  3. In the Formatter pane of the Show Info window, specify the %c date format, as shown in Figure 6-21.

    Formatter pane
    Figure 6-21. Formatter pane
  4. Save the nib file (File Save, or

    image with no caption

    -S).

  5. Return to Project Builder, and build and run (

    image with no caption

    -R) the project. You should see something like Figure 6-22.

  6. Quit the application (Simple Date Quit NewApplication, or

    image with no caption

    -Q).

    Simple Date application using a formatter
    Figure 6-22. Simple Date application using a formatter

Targets and Actions

The target/action pattern is part of the mechanism by which user-interface controls respond to user actions, enabling users to communicate their intentions to an application. The target/action pattern specifies a one-to-one relationship between two objects: the control (more specifically, the control’s cell) and its target. When a user clicks a user-interface control, it sends an action message to the target, as shown in Figure 6-23.

Target/action pattern
Figure 6-23. Target/action pattern

The target/action relationship is typically defined using Interface Builder, in which you select a target object for a control, along with a specific action message that will be sent to the target. Target/action relationships can also be set (or modified) while an application is running.

Target/Action Example

To show target/action pattern in practice, we are going to modify the Simple Date application we’ve already built.

Add a Refresh button

In Interface Builder, open the MainMenu.nib file, and add a button named "Refresh” to the main window, using the following steps:

  1. In the Cocoa-Views window, grab an NSButton object, and drag it to the main window.

  2. Change the name of the button by double-clicking on the “Button” name to highlight it, typing “Refresh”, then hitting Return to accept the new name. The interface should now look similar to Figure 6-24.

Target/action example interface
Figure 6-24. Target/action example interface

Define an action

When the user presses the Refresh button, we want the date to update itself. To do this, we need to define an action on our MyController object that the button will call. Define an action called refresh: using the following steps:

  1. Select the MyController class in the Classes pane of the MainMenu.nib window.

  2. In the Attributes pane of the Show Info window, click on the Actions tab and then on the Add button.

  3. Change myAction: to refresh: and hit Return to add the action, as shown in Figure 6-25.

Defining an action
Figure 6-25. Defining an action

Connect the button to the action

For MyController to receive an action message from the button in the user interface, you must connect the button to the controller. The button object keeps a reference to its target using an outlet; not surprisingly, the outlet is named target. To make this connection:

  1. Click on the Instances tab in the MainMenu.nib window.

  2. Control-drag a connection from the Refresh button to the MyController instance in the MainMenu.nib window, as shown in Figure 6-26. When the instance is outlined, release the mouse button.

    Creating a connection between the Refresh button and the controller
    Figure 6-26. Creating a connection between the Refresh button and the controller
  3. In the Connections pane of the Show Info window, make sure target is selected in the Outlets column.

  4. Select refresh: in the right column, as shown in Figure 6-27.

    Connecting to the refresh: method
    Figure 6-27. Connecting to the refresh: method
  5. Click the Connect button.

  6. Save the MainMenu.nib file (File Save, or

    image with no caption

    -S).

Update the source files

Since we made changes to the controller, the source files need to be updated so that we can add our controller code and run the application.

  1. Go to the Classes tab of the MainMenu.nib file window.

  2. Select the MyController class.

  3. Choose Classes Create Files for MyController (Option-

    image with no caption

    -F). Follow the dialog boxes to save the files into the project.

  4. Interface Builder will warn you that the file MyController.h already exists. Click on the Merge button to bring up the FileMerge tool, as shown in Figure 6-28. If you don’t see the window shown in Figure 6-28, look for the FileMerge icon on your Dock, and click it to bring the FileMerge window to the top.

    Merge tool in action
    Figure 6-28. Merge tool in action
  5. The FileMerge tool consists of three panes. The left pane is the newly generated file from Interface Builder, the right pane is the file in your project, and the bottom pane is the result of the merge. We want to keep our edits that were statically typed for the textField outlet. To do this, we select the #1 arrow, then “Choose right” from the Actions pop-up at the bottom-right corner of the window.

  6. Save the MyController.h file from FileMerge (File Save Merge ,or

    image with no caption

    -S) and then close the window.

  7. Return to Interface Builder. You will be prompted to merge the MyController.m file; do so.

  8. Merge the Files (as shown in Figure 6-29) by selecting “Choose both (left first)” for the first block of code and “Choose right” for the second block of code. Unfortunately, at the time of writing this book, FileMerge isn’t smart enough to handle this merge on its own. If you encounter this problem, you’ll need to add the curly braces after the refresh: method yourself.

    Merging MyController.m
    Figure 6-29. Merging MyController.m
  9. Save the resulting merged file (File Save Merge, or

    image with no caption

    -S), and quit the FileMerge tool (

    image with no caption

    -Q).

  10. Save the nib file (File Save, or

    image with no caption

    -S).

There are other ways of adding outlets and actions to your source code and the nib files that don’t involve using the FileMerge tool. We’ll see some of these other ways in later chapters.

Implement the action method

Now switch back to Project Builder. Our next step is to edit the MyController.m file and insert the code for the refresh: method, as shown in Example 6-3.

Example 6-3. Implementing the refresh: method
#import "MyController.h"

@implementation MyController

- (void) awakeFromNib
{
    [textField setObjectValue:[NSCalendarDate date]];

}

- (IBAction)refresh:(id)sender
{
    [textField setObjectValue:[NSCalendarDate date]];
}

@end
  1. Save the changes to the nib file (File Save, or

    image with no caption

    -S).

  2. Build and run the application (

    image with no caption

    -R).

When the application launches, you can refresh the date display by pressing the Refresh button. Of course, the date won’t change if you’ve selected to show only the date in the text field. If you’ve opted to also display the current time, hitting the Refresh button should update the time.

Tip

As we progress through the chapters in this book, our examples will contain more and more methods. It doesn’t matter to the compiler which order methods appear in your source files; they can be in any order you want.

Exercises

  1. Read the online documentation for the NSWindow and NSView classes.

  2. Give the window of our Simple Date application a title other than “Window”.

  3. Go back to the Currency Converter application in Chapter 5, and statically type the rateField, dollarField, and totalField outlets.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset