TweetFollow Us on Twitter

Cursor Control 2
Volume Number:6
Issue Number:9
Column Tag:C Workshop

Related Info: Quickdraw

Cursor Control

By Robert S. T. Gibson, Ontario, Canada

Note: Source code files accompanying article are located on MacTech CD-ROM or source code disks.

[Robert S. T. Gibson is a Senior Software Engineer at Atryx Software Design in Canada]

Although it is not suggested by anyone who follows the Macintosh user interface, it is occasionally necessary for a program to change or restrict the cursor position. Some programs move the cursor to lock it into a grid, some restrict the cursor to a specific region, and others (usually games) allow it to wrap around the screen. Games sometimes read the cursor position, compare it to the middle of the screen, and then move the cursor to the screen’s center to keep it from reaching the edge.

Cursor tracking is maintained by many system globals. The three we are interested in, however, are MTemp, RawMouse, Mouse, and CrsrNew (see Figure 1).

It is often dangerous to change the System’s low-memory globals if you don’t know their exact effects on your system.

As always, there is a right way and a wrong way to do this. I’ll skip the wrong way and get right to what I believe to be the correct way. The C techniques used here can easily be adapted into other programming languages.

It is a relatively simple task to set the mouse position, but it requires a bit of insight into how the System keeps control.

Figure 1. Mouse Globals

System Control

Operation of the mouse depends on two routines that are called during interrupts. The first routine simply sets the global variable CrsrNew to true if the mouse position has changed. The second routine is a vertical retrace (VBL) task, which checks the value of CrsrNew and draws the cursor in the new position if the value is true.

Providing CrsrNew is set, each time the VBL task is executed, it draws the cursor and copies the value of MTemp, the new location of the mouse, into the variable RawMouse, the saved (old) location. This allows the next execution to examine the two variables. If MTemp is a valid position, RawMouse is set to the new position and the cycle continues. The mouse location is copied in to the global variable Mouse for use by routines such as GetMouse().

Program Control

Only a few simple steps are required to set the mouse position properly and safely:

Step:

1: set MTemp to the new position

2: set RawMouse to new position

3: set CrsrNew to true

First, we should define our variables:

int             *MTemp;
char            *CrsrNew;

MTemp   = (int *)  0x828;
CrsrNew = (char *) 0x8CE;

Notice that MTemp was declared as an integer, and RawMouse and Mouse were not declared at all. Since the three points are in a row, it is much easier to simply declare the first variable as an integer and increment the pointer to each position to be set. NewPos is passed to our routine, containing the point to which the mouse should be moved in the following code excerpt. Remember that points are stored internally as (v,h) or (row,column), instead of (h,v).

!c

odeexamplestart/* 1 */

for (i=0;i<3;i++) {
 *(MTemp+2*i) = newPos.v;
 *(MTemp+2*i+1) = newPos.h;
}


Once the three points are set, the only task which remains to be done is the setting of the CrsrNew variable. To tell the System that the mouse has moved, the boolean must be set with a non-zero (true) value. Since it is set to -1 by the System, it will be so set here. Any true value should work, however.

/* 2 */

*CrsrNew = -1;

The System routine will realize that the global CrsrNew has been changed, will find the new position in MTemp and RawMouse and will draw the cursor in the new position.

Curse Control

It’s unwise to have your program confuse the user by moving the cursor too dramatically from where he would expect it to appear. If you are in a programming situation which is leading you to move the cursor to where you want it, rather than where the user expects it, you should think twice -- and then think again.

Listing:  Control.c

/*******
 * HandleMouse()
 * Checks the cursor position and calls MousePos if necessary
 *******/
HandleMouse(boundsRect)
Rect  *boundsRect;
{
Point   mousePoint;
Point   newPoint;

 GetMouse(&mousePoint);
 LocalToGlobal(&mousePoint);
 newPoint.h = newPoint.v = -1;
 
 if (mousePoint.h <= boundsRect->left)
 newPoint.h = boundsRect->right - 2;
 else
 if (mousePoint.h >= boundsRect->right - 1)
 newPoint.h = boundsRect->left + 1;
 if (mousePoint.v <= boundsRect->top)
 newPoint.v = boundsRect->bottom - 2;
 else
 if (mousePoint.v >= boundsRect->bottom - 1)
 newPoint.v = boundsRect->top + 1;
 
 if ( (newPoint.h + 1) || (newPoint.v + 1) )
 {
 newPoint.h = (newPoint.h != -1) ? newPoint.h : mousePoint.h;
 newPoint.v = (newPoint.v != -1) ? newPoint.v : mousePoint.v;
 MousePos(newPoint);
 }
}
Listing:  ControlMain.C

/* CursorControl */ /* by Rob Gibson */ /* August 22, 1989. */
/* MacHeaders Included */
/*********  Project file: Control.c  ControlMain.c             Functions.c 
 MacTraps MousePos.c
 Type: APPL Creator: CCTL **********/

#define ControlDialogID 1000
#define nil 0L
/* important dialog items */
enum{   quitItem = 1,   setItem,   topPosItem,     leftPosItem, 
 bottomPosItem,  rightPosItem,   boxItem     };

  /* Our global variables */
DialogPtr ControlDialog;
Rect    boundsRect;

/**InitMacintosh()  Initialize all the managers & memory ***/
InitMacintosh()
{  MaxApplZone();
 InitGraf(&thePort);
 InitFonts();
 FlushEvents(everyEvent, 0);
 InitWindows();
 InitMenus();
 TEInit();
 InitDialogs(0L);
 InitCursor();
} /* end InitMacintosh */

/**GetBounds()  *  * Get the rect specified in dialog  ***/
GetBounds(theDialog)
DialogPtr theDialog;
{  Str255 str;   longdummy;

 boundsRect.top = GetETNum(theDialog, topPosItem);
 boundsRect.left = GetETNum(theDialog, leftPosItem);
 boundsRect.bottom = GetETNum(theDialog, bottomPosItem);
 boundsRect.right = GetETNum(theDialog, rightPosItem);
} /* end GetBounds */

/*TrackRect() Frame old, new bound rects in current GrafPort**/
TrackRect(oldRect, r)
Rect  *oldRect;
Rect  *r;
{  FrameRect(oldRect);
 FrameRect(r);
} /* end TrackRect */

/**DisplayBounds() Display a rect in the dialog*/
DisplayBounds(theRect, theDialog) 
Rect    *theRect;
DialogPtr theDialog;
{  SetETNum(theDialog, topPosItem, (long)theRect->top);
 SetETNum(theDialog, leftPosItem, (long)theRect->left);
 SetETNum(theDialog, bottomPosItem, (long)theRect->bottom);
 SetETNum(theDialog, rightPosItem, (long)theRect->right);
 SelIText(theDialog, topPosItem, 0, 32767);
} /* end DisplayBounds */

/**SetBoundsLoop() User drags to specify new rect**/
SetBoundsLoop(theDialog)
DialogPtr theDialog;
{  Rect oldRect;
 Rect   newRect;
 GrafPtrsavePort;
 GrafPtrdeskPort;
 Point  firstPoint;
 Point  secondPoint;
 Point  lastSecondPoint;

 GetPort(&savePort);
 OpenPort(deskPort = (GrafPtr)NewPtr(sizeof(GrafPort)));
 InitPort(deskPort);
 SetPort(deskPort);
 PenPat(gray);
 PenMode(notPatXor);
 PenSize(2, 2);
 while(!Button());
 GetMouse(&firstPoint);
 newRect.top = lastSecondPoint.v = firstPoint.v;
 newRect.left = lastSecondPoint.h = firstPoint.h;
 newRect.bottom = newRect.right = 0;
 while(Button()) {
 oldRect = newRect;
 GetMouse(&secondPoint);
 /* If the mouse location has changed then track mouse */
 if (secondPoint.v != lastSecondPoint.v || secondPoint.h != lastSecondPoint.h) 
 {
 /* Create a new Rect making sure it is not an empty Rect */
 if (secondPoint.v > firstPoint.v) {
 newRect.top = firstPoint.v;
 newRect.bottom = secondPoint.v;
 }
 else {
 newRect.top = secondPoint.v;
 newRect.bottom = firstPoint.v;
 }
 if (secondPoint.h > firstPoint.h) {
 newRect.left = firstPoint.h;
 newRect.right = secondPoint.h;
 }
 else {
 newRect.left = secondPoint.h;
 newRect.right = firstPoint.h;
 }
 lastSecondPoint = secondPoint;

 TrackRect(&oldRect, &newRect);
 DisplayBounds(&newRect, theDialog);
 }
 }
 FrameRect(&newRect);
 ClosePort(deskPort);
 DisposPtr((Ptr)deskPort);
 PenNormal();
 SetPort(savePort);
 boundsRect = newRect;
 } /* end SetBoundsLoop */

/*HandleControlDialog()  Main event loop  *  ****/
HandleControlDialog(theDialog)
DialogPtr theDialog;
{  EventRecord   event; /*  Filled by GetNextEvent */
 Booleanfinished = false; /*  Are we done? */
 int    chosen;
 char   theChar;

 while (!finished) /*  do this until we selected quit */
 { /* continue with the normal get next event stuff... */
 if (GetNextEvent(everyEvent, &event))
 /*  if there was an event... then  */
 {
 if (event.what == keyDown || event.what == autoKey)
 { 
 theChar = (char) (event.message & charCodeMask);
 switch(theChar) { 
 case ‘Q’:
 case ‘q’:
 case ‘.’:
 chosen = quitItem;
 event.what = 0; /* remove event */
 finished = true;
 ClickButton(theDialog, quitItem, 2);
   break;
   case ‘\t’:
 case ‘\b’: 
 break;
   case ‘\r’:
 case ‘\003’:
 case ‘S’:
 case ‘s’:
 chosen = setItem;
 event.what = 0; /* remove event */
 ClickButton(theDialog, setItem, true);
 SetBoundsLoop(theDialog);
 ClickButton(theDialog, setItem, false);
 break;
 default:
 if (theChar < ‘0’ || theChar > ‘9’)
 event.what = 0;
 break;
 }
 } else if (event.what == updateEvt)
 {
 SetPort(theDialog);
 BeginUpdate(theDialog);
 FrameItem(theDialog, boxItem);
 DrawDialog(theDialog);
 DrawDefaultBtn (theDialog, setItem);
 EndUpdate(theDialog);
 }
   }

 if (!finished) {
 if   (IsDialogEvent(&event))
 if (DialogSelect(&event, &theDialog, &chosen)) {
 GetBounds(theDialog);
 switch (chosen) {
 case 1:
 finished = true;
 break;
 case 2:
 ClickButton(theDialog, setItem, true);
 SetBoundsLoop(theDialog);
 ClickButton(theDialog, setItem, false);
 break;
 default:
 break;
 } /*  end of if switch  */
 } 
 HandleMouse(&boundsRect);
 } /*  end of if (!finished) */
 } /*  of event loop  */

 DisposDialog(theDialog);
} /* end HandleControlDialog */

/*****  * SetUpDialog()  *  * Set up dialog stuff  *  *****/
SetUpDialog() {
 int    itemType;
 Handle Hdl;

 ControlDialog = GetNewDialog(ControlDialogID, nil, -1L);
 CenterWindow(ControlDialog, &screenBits.bounds);
 DisplayBounds(&screenBits.bounds, ControlDialog);
 ShowWindow(ControlDialog);
 boundsRect = screenBits.bounds;
 HandleControlDialog(ControlDialog);
} /* end SetUpDialog */

/*****  * main()  *  * Call the main procedures  *  *****/
main()

{  InitMacintosh();
 SetUpDialog();
} /* end main */
Listings:  Functions.C

/****
 * GetEText()
 * Get text of an ETItem
 ****/
GetEText (theDialog, theItem, s)
DialogPtr theDialog;
inttheItem;
char    *s;
{
 int     theType;
 Handle Hdl;
 Rect box;

 GetDItem (theDialog, theItem, &theType, &Hdl, &box);
 GetIText (Hdl, s);
} /* end GetEText */

/****
 * GetETNum()
 * Get number from an ETItem
 ****/
GetETNum(theDialog, theItem)
DialogPtr theDialog;
inttheItem;
{
 Str255 s;
 long theNum;
 
 GetEText(theDialog, theItem, &s);
 StringToNum(s, &theNum);
 return(theNum);
} /* end GetETNum */

/****
 * SetEText()
 * Set text of an ETItem
 ****/
SetEText (theDialog, theItem, s)
DialogPtr theDialog;
inttheItem;
Str255  s;
{
 int     theType;
 Handle Hdl;
 Rect box;

 GetDItem (theDialog, theItem, &theType, &Hdl, &box);
 SetIText (Hdl, s);
} /* end SetEText */

/****
 * SetETNum()
 * Set number in an ETItem
 ****/
SetETNum(theDialog, theItem, theNum)
DialogPtr theDialog;
inttheItem;
long    theNum;
{
 Str255 s;
 
 NumToString(theNum, s);
 SetEText(theDialog, theItem, &s);
} /* end GetETNum */

/***** LToGRect()  Convert a local rect to global *****/
LToGRect(r)
Rect  *r;
{
 Point  pt1,
 pt2;

 pt1 = topLeft(*r);
 pt2 = botRight(*r);
 LocalToGlobal(&pt1);
 LocalToGlobal(&pt2);
 Pt2Rect(pt1, pt2, r);
} /* end LToGRect */

/*****
 * CenterWindowPoint()
 * Calculates the topleft co-ords of a window,
 * taking screen size into account
 *****/
Point CenterWindowPoint (theRect)
Rect  *theRect;
{
 int    theInd = (screenBits.bounds.bottom<350) ? 3:4;
 Point  thePt;
 int    int1, int2;

 int1=((screenBits.bounds.right-screenBits.bounds.left-theRect->right+theRect->left) 
/ 2);
 int2=((screenBits.bounds.bottom-screenBits.bounds.top-theRect->bottom+theRect->top+20) 
/ theInd);
 SetPt(&thePt, int1, int2);
 return(thePt);
} /* end CenterWindowPoint */

/*****
 * CenterWindow()
 * Centers a dialog or window
 *****/
void CenterWindow(theDialog)
DialogPtr theDialog;
/* Center window - center slightly higher for large screens */
{
 Point  thePt;
 Rect newBounds;

 newBounds = *&theDialog->portRect;
 LToGRect(&newBounds);
 thePt = CenterWindowPoint(&newBounds);
 MoveWindow(theDialog, thePt.h, thePt.v, 0);
} /* end CenterWindow */

/****ClickButton() Simulate a click in a button ****/
ClickButton(theDialog, ID, method) 
/* 0 is off, 1 is on, 2 simulates a click */
DialogPtr theDialog;
intID;
intmethod;
{
 int    itemType;
 Handle item;
 Rect box;
 long ticks;

 GetDItem(theDialog, ID, &itemType, &item, &box);
 HiliteControl((ControlHandle) item, (method >= 1));
 if (method >= 2)
 {
 Delay(8L, &ticks);
 HiliteControl((ControlHandle) item, 0);
 }
} /* end ClickButton */

/****
 * FrameItem()
 * Frame a dialog item in current pen modes
 ****/
void FrameItem (theDialog, item)
DialogPtr theDialog;
intitem;
{
 int    optType;
 Handle btnHdl;
 Rect   optBox;

 SetPort(theDialog);
 GetDItem(theDialog, item, &optType, &btnHdl, &optBox);
 FrameRect(&optBox);
} /* end FrameItem */

/**** DrawDefaultBtn() Outline the default button ****/
void DrawDefaultBtn (theDialog, item)
DialogPtr theDialog;
intitem;
{
 int    optType;
 Handle btnHdl;
 Rect   optBox;

 SetPort(theDialog);
 GetDItem(theDialog, item, &optType, &btnHdl, &optBox);
 PenSize(3, 3);
 InsetRect(&optBox, -4, -4);
 FrameRoundRect(&optBox, 16, 16);
} /* end DrawDefaultBtn */
Listing:  MousePos.C

/********
* MousePos()
* Set the mouse position, August 22, 1989
* by Robert S. T. Gibson for MacTutor
********/
void MousePos(newPos)
Point newPos;
{
 int    *MTemp;
 char *CrsrNew;
 int    i;

 MTemp = (int *)  0x828;  /* Set up our globals... */
 CrsrNew = (char *) 0x8CE;

/* Points are stored as (row, column) or (v, h) in memory...*/
 /* Set our globals... */
 for (i=0;i<3;i++) {
 *(MTemp+2*i) = newPos.v;
 *(MTemp+2*i+1) = newPos.h;
 }
 *CrsrNew = -1;  /* There’s a new position */
}
 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Alfred 4.2 - Quick launcher for apps and...
Alfred is an award-winning productivity application for OS X. Alfred saves you time when you search for files online or on your Mac. Be more productive with hotkeys, keywords, and file actions at... Read more
MenuMeters 2.0.8 - CPU, memory, disk, an...
MenuMeters is a set of CPU, memory, disk, and network monitoring tools for Mac OS X. Although there are numerous other programs which do the same thing, none had quite the feature set I was looking... Read more
ClipGrab 3.8.15 - Download videos from Y...
ClipGrab is a free downloader and converter for YouTube, Vimeo, Facebook and many other online video sites. It converts downloaded videos to MPEG4, MP3 or other formats in just one easy step Version... Read more
Malwarebytes 4.6.11.3824 - Adware remova...
Malwarebytes (was AdwareMedic) helps you get your Mac experience back. Malwarebytes scans for and removes code that degrades system performance or attacks your system. Making your Mac once again your... Read more
Safari Technology Preview 14.1 - The new...
Safari Technology Preview contains the most recent additions and improvements to WebKit and the latest advances in Safari web technologies. And once installed, you will receive notifications of... Read more
Mactracker 7.9.7 - Database of all Mac m...
Mactracker provides detailed information on every Mac computer ever made, including items such as processor speed, memory, optical drives, graphic cards, supported OS X versions, and expansion... Read more
Tor Browser 10.0.2 - Anonymize Web brows...
The Tor Browser Bundle is an easy-to-use portable package of Tor, Vidalia, Torbutton, and a Firefox fork preconfigured to work together out of the box. It contains a modified copy of Firefox that... Read more
iTubeDownloader 6.5.24 - Easily download...
iTubeDownloader is a powerful-yet-simple YouTube downloader for the masses. Because it contains a proprietary browser, you can browse YouTube like you normally would. When you see something you want... Read more
TeamViewer 15.11.6 - Establish remote co...
TeamViewer gives you remote control of any computer or Mac over the Internet within seconds, or can be used for online meetings. Find out why more than 200 million users trust TeamViewer! Free for... Read more
ffWorks 2.1.18 - Convert multimedia file...
ffWorks, focused on simplicity, brings a fresh approach to the use of FFmpeg, allowing you to create ultra-high-quality movies without the need to write a single line of code on the command-line.... Read more

Latest Forum Discussions

See All

Lamplight City is a detective adventure...
Lamplight City is a steampunk adventure game that's available now for iOS after previously launching on PC. It sees players take on the role of private investigator Miles Fordham who is trying to get justice for his clients whilst tracking down... | Read more »
Genshin Impact Progression Guide - Mid-G...
Once you get up to around Adventure Rank 25, Genshin Impact starts to turn into a different thing. What was once a story-driven affair evolves into something more like a traditional MMO. | Read more »
Pixboy, the hit retro-themed platformer,...
Following its successful debut on PC and Nintendo Switch last summer, retro-themed platformer Pixboy has made the lead to mobile devices. The award-winning indie hit, which was developed by a two-person team at Oaky Games, has drawn comparisons to... | Read more »
Raziel: Dungeon Arena's newest char...
Raziel: Dungeon Arena's October update offers a little bit more than usual Halloween-themed outfits you often get this time of year, though those are here too. But beyond that, there is a new character, mercenary system and much more. In need of... | Read more »
Genshin Impact - Marvelous Merchant Even...
We're month since Genshin Impact's initial release, and we're already here at our second event. The Marvelous Merchant event has already been underway for two days and we have just the info you need to complete this silly scavenger hunt each day... | Read more »
Crash Bandicoot: On The Run! spins onto...
There’s a Crash Bandicoot endless runner coming to mobile sometime next spring. [Read more] | Read more »
Mulled 2 is a refreshingly simple ball-p...
Some of the greatest mobile games are actually the simplest ones, and it’s this philosophy that Mulled 2 was built on. Out now for iOS and Android, the puzzle sequel takes the accessible mechanics of its predecessor and builds on them with enticing... | Read more »
Physics-based tower defense game Goblin...
Indie developer Arif Games has released Goblin Raiders on iOS and Android. [Read more] | Read more »
PUBG Mobile has provided yet another upd...
PUBG Mobile has been making a point of publicly mentioning all of their ongoing efforts to vanquish cheating from the popular battle royale. Today two teams within the company have provided updates on their progress. [Read more] | Read more »
Zombieland: AFK Survival is celebrating...
Zombieland: AFK Survival is currently celebrating its one-year anniversary. If you don't quite recognise the name that's because it initially launched as Zombieland: Double Tapper. Anyway, the game is celebrating turning one with two Halloween-... | Read more »

Price Scanner via MacPrices.net

Apple now offering $8000 discount on maxed Ma...
Apple’s powerful Mac Pro can be fully optioned to a max of $53,399 by choosing a 2.5GHz 28-core Xeon W CPU, 1.5TB of memory, Two Radeon Pro Vega II Duo video cards with 2x32GB memory each, 8TB of... Read more
How to save $100-$200 on a new MacBook with A...
Are you a teacher, student, or staff member of any educational institution and need a new Apple MacBook? When you purchase a new MacBook using Apple’s Education discount, Apple will take $100-$200... Read more
Amazon offers Apple Watch Series 6 and SE dis...
Amazon is among the first Apple resellers to offer sale prices on new Apple Watch Series 6 and Apple Watch SE models with models available for up to $25 off Apple’s MSRP. These are the same Watches... Read more
3rd generation 12.9″ iPad Pros available toda...
Apple restocked select 3rd generation 12.9″ WiFi iPad Pros starting at only $699 and up to $380 off original MSRP. Each iPad comes with a standard Apple one-year warranty, outer cases are new, and... Read more
Upgrading To The iPhone 12 And Making The Swi...
FEATURE: 10.28.20 – The future of wireless technology is here but before you can jump onto the 5G bandwagon, you’ll need to upgrade to a compatible device like one of the new iPhones Apple... Read more
Gazelle drops prices on Apple’s iPhone 11 mod...
Gazelle is offering a range of discounted pre-owned unlocked Apple iPhone 11 models starting at $475, with some models seeing price reductions of up to $200. iPhones are offered in Fair, Good, and... Read more
B&H Photo has 2020 13″ Dual-Core MacBook...
B&H Photo has 2020 13″ Dual-Core MacBook Airs on sale today for $100 off Apple’s MSRP, only $899. Expedited shipping is free to many addresses in the US. These MacBook Airs are the same models... Read more
New promo at Verizon: New 64GB iPhone 11 Pro...
Verizon is offering the new 64GB iPhone 11 Pro Max for free for customers switching and opening a new line of service. Qualified trade-in required, worth up to $850 credited to your account over a 24... Read more
Apple has 2020 13″ 1.4GHz MacBook Pros in sto...
Apple has Certified Refurbished 2020 13″ 1.4GHz/256GB 4-Core Touch Bar MacBook Pros available for $1099, $200 off MSRP. Apple’s one-year warranty is included, shipping is free, and each MacBook has a... Read more
Amazon offers $50 discount on all new 2020 12...
Amazon has new 2020 Apple 12.9″ iPad Pros on sale today for $50 off MSRP, with prices starting at $949. Shipping is free. These are the same iPad Pros sold by Apple in their retail and online stores... Read more

Jobs Board

Licensed Dental Hygienist - *Apple* Valley...
Park Dental Apple Valley is seeking a compassionate, professional Dental Hygienist to join our team-oriented practice. Clinical Requirements Minnesota Licensure and Read more
Deskside Support Technician - *Apple* , Wind...
Deskside Support Technician - Apple , Windows **Ref No.:** 20-01859 **Location:** Northridge, California As part of a leading IT managed services specialist with more Read more
*Apple* Computing Specialist - Best Buy (Uni...
**789374BR** **Job Title:** Apple Computing Specialist **Job Category:** Store Associates **Store Number or Department:** 000896-Atlanta GA-Store **Job Read more
Geek Squad Advanced Repair *Apple* Professi...
**789256BR** **Job Title:** Geek Squad Advanced Repair Apple Professional **Job Category:** Store Associates **Store Number or Department:** 001135-Gulf Coast-Store Read more
Cub Foods - *Apple* Valley - Now Hiring Par...
Cub Foods - Apple Valley - Now Hiring Part Time! United States of America, Minnesota, Apple Valley Retail Post Date 1 day ago Requisition # 124800 Sign Up for Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.