TweetFollow Us on Twitter

Autumn 91 - VALIDATING DATE AND TIME ENTRY IN MACAPP

VALIDATING DATE AND TIME ENTRY IN MACAPP

JAMES PLAMONDON

[IMAGE 028-040_Plamondon_html1.GIF]

MacApp's TEditText class checks strings entered by the user, displaying an error message when an invalid string is encountered. This article shows how TEditText's validation and error notification schemes can be made more flexible, and demonstrates this flexibility in TEditText subclasses for the entry of dates and times. You can try out these classes in the sample program that's on the Developer CD Series disc.

My favorite high school teacher, Mrs. Whalen, had a sign under the wall clock in her classroom that read "Time passes--but will you?" Back when I was in the Class of '78, there were many times I wished that I could set the clock back (during a tough quiz) or forward (on a warm afternoon). Although I can't offer any such hope to the Class of '91, I can at least provide MacApp developers with classes that make the entry of dates and times as easy as I ever dreamed.

I wrote these classes during some recent work on a MacApp application that involved the entry and validation of dates and times. After considering and rejecting all sorts of controls--controls that looked like little monthly calendars, 24-hour clocks, and so on--I settled on simple editable text boxes. I thought that with these boxes, those pesky localization issues that plagued the other designs wouldn't be a problem, because I could use the Macintosh Script Manager to handle the different date and time formats described by the international resources in the operating system. I also figured that if I used MacApp's TEditText class, writing editable text boxes for date and time entry would be trivial. An override here, a little data there, and voilà--done. It wasn't the first time I've been wrong.

But to understand TEditText's flaws, first you have to know how it works.

TEDITTEXT REVEALED

TEditText is a TControl subclass. It encapsulates the Toolbox's TextEdit routines. A TEditText view is to one of MacApp's TDialogViews what an editText item is to one of the Dialog Manager's dialogs: it allows the user to enter strings into a box in a dialog box. In addition, TEditText extends the functionality of editable text items to include the notion of validation. If an invalid string is entered into a TEditText view, an alert is displayed, notifying the user of the problem.

The validation process implemented by TEditText centers on its Validate method. In TEditText, a valid string is any string that's not longer than the maximum allowed length, which is specified by the application's author. If the string is valid, the Validate method returns the constant value kValidValue; otherwise--that is, if the string is too long--it returns the error code kTooManyCharacters. TNumberText, a subclass of TEditText that handles the entry and validation of integer numbers, can return additional error codes--kValueTooSmall, kValueTooLarge, or kNonNumericCharacters.

[IMAGE 028-040_Plamondon_html2.GIF]

Figure 1 MacApp's Validation Error Alert

The only place Validate is ever called in MacApp is from the TDialogView method DeselectCurrentEditText. If Validate returns a value other than kValidValue, that value is assumed (in a call to TDialogView.CantDeselect) to be an index into a string list resource called kInvalidValueReasons. It's expected that the string at that index will describe the error encountered. This string is then displayed in an error alert that tells the user why the string entered is invalid. Figure 1 shows the alert displayed when the user types too many characters into a TEditText view.

My dad used to say "Whenever a guy's telling you what he's gonna dofor you, start worrying about what he's gonna doto you." It wasn't long before I realized that TEditText was like that. I had hoped that it would be easy to extend the checking done in TEditText.Validate to include checking for a valid date or time, but it wasn't. To add this kind of checking, I was going to have to rewrite Validate from scratch-- just the kind of thing object-oriented programming is supposed to prevent.

DON'T FIX WHAT AIN'T BROKE
When in the course of application programming it becomes necessary to replace a mechanism written by the MacApp engineers, one should declare the causes that impel this decision. I hold these truths to be self-evident:

  1. That the reuse of existing code is preferable to the addition of new code.
  2. That the addition of new code is preferable to the alteration of existing code.
  3. That the alteration of existing code is preferable to missing a deadline.

MacApp's approach to text validation fails to meet a number of these criteria. First, it assumes that new error strings will simply be added to the STR# resource called kInvalidValueReasons, with new error codes indexing the added strings. However, this won't work: TDialogView.CantDeselect uses a constant, kNoOfDefaultReasons, to indicate the number of strings in this resource. It can only be changed by altering and recompiling MacApp--a violation of self-evident Truth #2.

Also, the error-code-equals-string-index scheme can be a problem when one combines existing class libraries; two different TEditText subclasses, written independently, may use the same error codes (and string indices) to indicate different problems. Resolving this conflict would probably require changing and recompiling at least one of the conflicting classes.

Further, the use of error strings can cause problems during localization since not all languages can stick an arbitrary string into a sentence and have the result make any sense. Static error strings also give little context--they may not be able to display the invalid string, or a valid example string, to help the user figure out what went wrong. For all of these reasons, MacApp's use of a single error string list--with Validate's result being used as an index into this list--seems inappropriate. Each class should instead build its own error strings in any manner it sees fit, using its own string lists as necessary.

That's not all. The error alert displayed when invalid strings are encountered has only one button. But what if two or more alternative actions can be taken in response to the entry of an invalid string?

Consider the following validation case (which has nothing to do with dates or times). Assume that the user needs to enter the name of a category--like Work, School, or Personal--into an editable text box. If the string the user enters matches the name of an existing category (for example, "Work"), the string is valid; otherwise--for example, if the user types "Wirk"--the string is invalid.

[IMAGE 028-040_Plamondon_html3.GIF]

Figure 2 Two-Option Validation Error Alert

In addition, we want to allow the user to add new categories to the list by entering them into the editable text box. To do this, we must distinguish those entries that are simply mistyped (like "Wirk") from those intended to become new category names (like "School" or "Personal"). In effect, we need to present the user with a two-button dialog box like that in Figure 2.

Unlike the default MacApp validation alert, which has only one button, the dialog box in Figure 2 allows the user to decide whether the entered string--"Personal," in this case--is valid or invalid.

TVALIDTEXT: THE UNAUTHORIZED BIOGRAPHY

So to extend validation to dates and times, I decided to write two new classes, TDateEditText and TTimeEditText. After writing these classes, I realized that they had so much validation code in common that it made sense to put this code in a common superclass. I called this superclass TValidText.

TValidText is a pretty simple extension to TEditText. It adds three notions to TEditText-- strictness, required value, and an invalid entry alert ID. It also significantly enhances the text validation process.

TValidText is an example of an "abstract class"--a class that's never expected to be instantiated directly. It exists only to factor out the code that's expected to be common to its subclasses. All of TValidText's subclasses will simply inherit its validation and error reporting code, while overriding a few methods to implement their own specific validation tests and error messages.

The class declaration for TValidText is as follows:

TValidText = OBJECT(TEditText)
    fStrict:    BOOLEAN;
    fRequired:  BOOLEAN;
    fAlertID:   INTEGER;
    .
    .    See the Developer CD Series disc for method declarations.
    .
END; {TValidText}

TValidText's fStrict field, a Boolean variable, determines whether or not strict checking will be used when validating. This field exists here because both the date and time classes needed the concept. TValidText itself doesn't use fStrict, except to get and set its value. It might be more general to implement it as a scalar (maybe a signed byte) to provide multiple strictness levels. We'll look at strictness again in the discussion of the date and time classes later in this article.

The fRequired field answers the question of whether an empty string is valid or not. As far as TValidText is concerned, if fRequired is true, an empty string is invalid; otherwise, it's valid. TValidText's subclasses may add additional conditions to the notion of validity by overriding the method IsValid and calling the inherited version. Both the date and time editing classes do this, as we'll see later.

The fAlertID field contains the resource ID of the alert to be displayed when the current text doesn't pass validation. It may contain the value phInvalidValue (defined in UDialog), or the resource ID of any other ALRT resource. It would be easy to override the routines involved to display a MacApp dialog box rather than a Toolbox alert, in which case fAlertID could be the ID of the appropriate view resource.

THE NATURE OF VALIDITY

The TValidText declaration introduces a new method, IsValid:
FUNCTION TValidText.IsValid(
    VAR     theText: Str255;
    VAR     whyNot: INTEGER)
    :BOOLEAN;

In addition to returning a Boolean indicating the validity of the given string, IsValid returns in whyNot an indication of why the string is invalid (or the value noErr, if it's valid). This is very similar in functionality to TEditText's Validate routine, with one major difference: the string being validated is passed in as an argument. Where TEditText.Validate assumes that it's supposed to validate the string currently being edited, TValidText.IsValid can be used to test arbitrary strings for validity.

I overrode the Validate method in TValidText to make it a flow-of-control method. It validates the current string and displays the error alert when necessary, as follows:

FUNCTION  TValidText.Validate:LONGINT; OVERRIDE;
    VAR
        parentResult:   LONGINT;
        theText:            Str255;
        whyNot:         INTEGER;
    
    BEGIN
    {Make sure the current text passes the superclass's validation.}
    parentResult := INHERITED Validate;
    IF (parentResult <> kValidValue)
    THEN
        Validate := parentResult
    ELSE
        BEGIN
        GetText(theText);
        IF IsValid(theText, whyNot)
        THEN
            Validate := HandleValidText(theText)
        ELSE
            Validate := HandleInvalidText(theText, whyNot);
        END;    {else}
    END;    {Validate}

This structure places the responsibility for handing invalid cases in the class itself, rather than relying on MacApp's code for mapping error codes to error strings in TDialogView.CantDeselect (never trust a method with a contraction in its name). Given this structure, you can change any step in the validation process without changing the nature of validation itself by overriding IsValid, HandleValidText, or HandleInvalidText. That's the whole idea behind flow-of-control methods.

HandleValidText simply returns kValidValue (defined in UDialog), after notifying its superview that the text is valid. Two lines of code--no fuss, no muss.

HandleInvalidText has to do a little more, but not much. It calls the method ValidationErrorAlert to notify the user of the problem. Although the default alert has only an OK button, I've also added support for a Cancel button. If the user clicks OK, HandleAlertAccepted is called; otherwise--if the user clicks Cancel-- HandleAlertCancelled is called.

FUNCTION  TValidText.HandleInvalidText(
                    VAR     theText:        Str255;
                            theError:       INTEGER)
                            :LONGINT;
    BEGIN
    IF ValidationErrorAlert(theText, theError)
    THEN
        HandleInvalidText :=
                HandleAlertAccepted(theText, theError)
    ELSE
        HandleInvalidText :=
                HandleAlertCancelled(theText, theError);
    END;    {HandleInvalidText}

In either case, a handler routine is called. Again, this kind of flow-of-control method, which calls other methods to do the dirty work, is a very useful addition to the object programmer's repertoire.

ValidationErrorAlert is equally trivial, consisting of only two lines. The first is a call to PrepareErrorAlert, while the second displays the alert itself, returning TRUE if the user accepts the dialog box and FALSE if the user cancels out of it.

PrepareErrorAlert is also only two lines of code:

PROCEDURE TValidText.PrepareErrorAlert(
                    VAR     theText:        Str255;
                            theError:       INTEGER);
    {This routine sets up the dialog that is displayed by
     ValidationErrorAlert.}
    VAR
        theString:      Str255;
    
    BEGIN
    {Get the best string to describe the given error.}
    ErrorToString(theError, theString);
    ParamText(theString, '', '', '');
    END;    {PrepareErrorAlert}

PrepareErrorAlert converts the given error code to a string by calling ErrorToString, and then calls ParamText to get the string into the dialog. The error code was generated by IsValid way back in the Validate method. The essential feature of these routines is that they're all teeny-tiny pieces of code, each with a single, well-defined goal. Any one of them can be overridden in isolation, to tweak the validation mechanism one way or another. I think you'll find it to be a big improvement over TEditText's validation mechanism.

VALIDATING THE DATE

The TDateEditText class allows the user to enter a date string and have the Macintosh Script Manager's LongDateTime routines figure out what date it is, in a convenient, internationally compatible manner. It can display the resulting date in any of the three formats supported by the Script Manager: short (9-13-91), abbreviated (Fri, Sep 13, 91), or long (Friday, September 13, 1991).

TDateEditText overrides four TValidText methods to implement date validation: IsValid, HandleValidText, ErrorToString, and PrepareErrorAlert.

IsValid looks pretty complicated, and it is--by my standards, anyway. It has to set up not only its Boolean return value, but also an error code if the given text is not valid. This latter chore is complicated by the optional strict checking, embodied in fStrict. The Script Manager provides two different levels of error messages when converting dates (and times) to strings. It will take almost anything you give it and make a date out of it, but it will warn you about leftover characters, nonstandard separators, and the like. Strict checking for dates means that only perfectly formed date strings will be accepted, while nonstrict checking means that so long as a date can be extracted from the string, you don't want to hear the Script Manager complain about how hard it was to get it.

FUNCTION  TDateEditText.IsValid( VAR theText: Str255;
    VAR whyNot: NTEGER) :BOOLEAN;
OVERRIDE;

    VAR
        theError:       INTEGER;
        valid:          BOOLEAN;
        dateSecs:       LongDateTime;

    BEGIN
    IF (NOT INHERITED IsValid(theText, theError))
    THEN
        BEGIN
        valid  := FALSE;
        whyNot := theError;
        END
    ELSE
        BEGIN
        {Use the Script Manager to convert the date string to a
         LongDateTime.}
        theError := StringToDate(theText, dateSecs);
        IF fStrict
        THEN
            valid := (theError = noErr) | (theError = longDateFound)
        ELSE        
        {Error codes >= noErr mean a valid date was found.}
            valid := (theError >= noErr);
        IF (theError = dateTimeNotFound) &      {Date isn't found,}
            (NOT fRequired) &               {empty strings are OK,}
            (Length(theText) = 0)       {and the string is empty.}
        THEN        {Empty string is OK if entry isn't required.}
            valid := TRUE;
        IF valid
        THEN
            whyNot := noErr
        ELSE
            whyNot := theError;
        END;    {else}
    IsValid := valid;
    END;    {IsValid}

HandleValidText just sets the fDateSecs instance variable to reflect the date of the given string, and then calls the inherited version of the HandleValidText routine in TValidText.

Likewise, ErrorToString catches those errors that it knows about and converts them to strings; others, it just passes on to the inherited version of ErrorToString. Don't you love inheritance?

PROCEDURE TDateEditText.ErrorToString( theError: INTEGER;
        VAR theString: Str255);
OVERRIDE;
    {This routine sets theString to the string that best
     explains the given error. It's intended to be called
     only from PrepareErrorAlert.}
    VAR
        strIndex:   INTEGER;

    BEGIN
    CASE theError OF
        {These are the error codes returned by the Script Manager's
         string-to-date routine.}

        {strIndex 1 contains the default string, "invalid date".}
        leftOverChars:          strIndex :=  2;
        sepNotIntlSep:          strIndex :=  3;
        fieldOrderNotIntl:      strIndex :=  4;
        extraneousStrings:      strIndex :=  5;
        tooManySeps:            strIndex :=  6;
        sepNotConsistent:       strIndex :=  7;
        tokenErr:               strIndex :=  8;
        cantReadUtilities:      strIndex :=  9;
        dateTimeNotFound:       strIndex := 10;
        dateTimeInvalid:        strIndex := 11;
            
        OTHERWISE               strIndex :=  0; {Not our error.}
        END;    {case theError}
    IF (strIndex > 0)
    THEN    {It's an error we know how to describe, so handle it.}
        GetIndString(theString, kInvalidDateReasons, strIndex)
    ELSE    {Never heard of it - ask our superclass to handle it.}
        INHERITED ErrorToString(theError, theString);
    END;    {ErrorToString}

TDateEditText.PrepareErrorAlert (below) calls ErrorToString to convert the given error code to a string. This string will then be displayed in the validation error alert (see Figure 3). It also converts the current system date to a string to be displayed in the alert, where it will serve as an example of the proper date format.

PROCEDURE TDateEditText.PrepareErrorAlert(
                    VAR     theText:            Str255;
                            theError:           INTEGER);
                            OVERRIDE;   
    VAR
        errString:      Str255;
        dateSecs:       LongDateTime;
        dateString:     Str255;
    
    BEGIN
    {Get the current date, as a string.}
    GetCurrentDate(dateSecs);
    DateToString(dateSecs, shortDate, dateString);

    {Get the best string to describe the given error.}
    ErrorToString(theError, errString);
    ParamText(errString, dateString, '', '');
    END;    {PrepareErrorAlert}

[IMAGE 028-040_Plamondon_html4.GIF]

Figure 3 TDateEditText's Validation Error Alert

TDateEditText.PrepareErrorAlert can't call the version of PrepareErrorAlert it inherits from TValidText. The inherited version's call to ParamText would cloud the effect of the override's call. I got around this by duplicating the body of TValidText.PrepareErrorAlert in the override, and not calling the inherited version at all. This duplication is a violation of Truth #1 (reusing code is better than writing new code), but I couldn't figure out how to avoid it--so I just duplicated it, thus adhering to Truth #3 (anything's better than missing a deadline).

Eventually, if an invalid date like February 31 has been entered by the user, TDateEditText displays an alert similar to that shown in Figure 3.

VALIDATING THE TIME

Given the description and discussion of TDateEditText above, the most striking thing about TTimeEditText is its similarity to TDateEditText. That shouldn't be surprising. The validation of dates has been designed to follow a particular series of steps, which can also be applied to time. Validating time therefore involves subtly tailoring the behavior of a few steps rather than writing the validation logic from scratch. You can see this in the code on theDeveloper CD Series disc, which includes a test application and its source.

WHAT'S DONE IS DONE

If you build the test application yourself and bang on it even a little, you'll find a bug: if you've got invalid text in one editText item, and click on the other, you'll see the invalid entry alert twice. This is very annoying. Fixing this problem requires a change to MacApp, however, because that's where the bug lives. Consider MacApp's TEditText.HandleMouseDown method:

FUNCTION TEditText.HandleMouseDown(
                            theMouse:       VPoint;
                    VAR     info:           EventInfo;
                    VAR     hysteresis: Point;
                    VAR     theCommand: TCommand)
                            : BOOLEAN;
                            OVERRIDE;
    BEGIN
    {Get the floating TE installed if necessary.}
    IF IsViewEnabled & (gTarget <> fTEView)
    THEN
        DoChoice(SELF, fDefChoice);
    HandleMouseDown := INHERITED HandleMouseDown(
                        theMouse, info, hysteresis, theCommand);
    END;    {HandleMouseDown}

The call to DoChoice goes to TDialogView, which attempts to deselect the currently edited item with a call to its Validate method. If validation fails, the validation failure alert is displayed. The subsequent call to INHERITED HandleMouseDown eventually calls DoMouseCommand. This call, in turn, creates and returns a control tracker that eventually calls TDialogView.DoChoice again. TDialogView.DoChoice again attempts to deselect its currently edited item, and the validation again fails (since nothing has changed), displaying the invalid entry alert for the second time.

To fix the problem, we must add an override of DoMouseCommand to TEditText. Just overriding DoMouseCommand in TValidText won't fix the problem, since the flaw is in TEditText itself.

FUNCTION TEditText.DoMouseCommand(
                        VAR     theMouse:       Point;
                        VAR     info:           EventInfo;
                        VAR     hysteresis: Point)
                                : TCommand;
                                OVERRIDE;   BEGIN
    IF (gTarget = fTEView)      {Only true when validation succeeds.}
    THEN        {Validation has succeeded, so Do the Right Thing.}
        DoMouseCommand := INHERITED DoMouseCommand(
                                theMouse, info, hysteresis)
    ELSE        {Validation failed - stop cold.}
        DoMouseCommand := NIL;
    END;    {DoMouseCommand}

Thus INHERITED DoMouseCommand is called only when all is as it should be. (I'd like to thank Tom Dinger for suggesting this solution, which is cleaner than my originally proposed change to TEditText.HandleMouseDown.) I added this change to my copy of MacApp, and used it to build the compiled version of the sample application you'll find on theDeveloper CD Series disc--so it demonstrates the fix, not the bug.

. . . AND NOW, THE QUIZ

That about wraps up TValidText, TDateEditText, and TTimeEditText. Their use is demonstrated further in the accompanying sample program. You can use the TValidText class as a common basis for the validation of any quantity-related editable text control--such as controls for numbers, currency, and weights and measures--in the same uniform and flexible manner. If you have any questions, I'm sorry, but your time is up: please put down your pencils, and pass your papers forward. Class dismissed!


ACKNOWLEDGMENTS
This article wouldn't have been possible without the support of Steve Starr, Marian Cauwet, or Ed Lauing, who have made Power Up Software such a great place to work, or my family, who have made my home such a great place to live. To the former, I give my thanks and respect; to the latter, my love. Many thanks also to my editor, Geta Carlson.

Special thanks to the MacApp engineers, past and present, for writing such a great piece of work. If inflexible string validation is the worst flaw I can find in MacApp, it must be pretty good. And thanks to our technical reviewers: Jesse Feiler of The Philmont Software Mill, author of articles on topics related to this one; Carl Nelson, President of Software Architects and former President of the MacApp Developer's Association (MADA); Bryan Stearns, Macintosh guru and Tech Note author; and David Taylor of Bear River Associates, author of Calendar Creator 1.0, the first shipping retail application written using MacApp 2.0.

WHAT ABOUT MACAPP 3.0?

MacApp is an evolving system, just like the Macintosh itself. With the coming of System 7, a new and more powerful version of MacApp is in the works: MacApp 3.0.

Totally rewritten in C++, this new version of MacApp is still in development; however, an early version, MacApp 3.0b1, is available to Macintosh developers on the Essentials*Tools*Objects CD #5.

MacApp 3.0 will offer a lot of new features, most of them directed toward support for System 7. A discussion of most of these features is far beyond the scope of this article. With regard to text validation, MacApp 3.0's display of validation error alerts is much improved.

In MacApp 3.0, the TargetValidationFailed method has been added to TEvtHandler to allow each class to handle validation errors in a class-specific manner. An override of TargetValidationFailed in TDialogTEView calls another override of the same method in TEditText, which displays the error alert and restores the TEditText's previous contents. Thus the validation error alert is posed by TEditText, not by TDialogView, as was the case in MacApp 2.0. That's a big improvement.

Unfortunately, the nature of TEditText's Validate routine remains unchanged in MacApp 3.0. If your application requires more flexible validation than that provided by MacApp, you may still need to use some of the techniques described in this article.

JAMES PLAMONDON was born, is hanging around for a while, and will soon die, like everybody else. But he's trying to have fun in the meantime. As a software engineer at Power Up Software Corporation of San Mateo, California, he has worked on an as yet unreleased MacApp product he can't talk about, and he has recently begun working on the Microsoft Windows version of this product--but he can't talk about that either. The founder of the Bay Area MacApp Developer's Association (BAMADA), his interests include raising his four kids, following international politics, writing the Great American Computer Game, and trying to convince Apple to port MacApp to Windows. (If only he could get his kids to write the international version of his game using MacApp for Windows, his life would be complete.) *

The fRequired field might better be called fEmptyStringValid or something to this effect. I called it fRequired to match a comment in the method TNumberText.Validate in the UDialog unit of the MacApp 2.0 source code. *

THANKS TO OUR TECHNICAL REVIEWERS Jesse Feiler, Carl Nelson, Bryan Stearns, David Taylor *

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Daylite 6.7.3 - Dynamic business organiz...
Daylite helps businesses organize themselves with tools such as shared calendars, contacts, tasks, projects, notes, and more. Enable easy collaboration with features such as task and project... Read more
VirtualBox 6.0.10 - x86 virtualization s...
VirtualBox is a family of powerful x86 virtualization products for enterprise as well as home use. Not only is VirtualBox an extremely feature rich, high performance product for enterprise customers... Read more
BetterTouchTool 3.149 - Customize multi-...
BetterTouchTool adds many new, fully customizable gestures to the Magic Mouse, Multi-Touch MacBook trackpad, and Magic Trackpad. These gestures are customizable: Magic Mouse: Pinch in / out (zoom)... Read more
Dropbox 77.4.131 - Cloud backup and sync...
Dropbox is an application that creates a special Finder folder that automatically syncs online and between your computers. It allows you to both backup files and keeps them up-to-date between systems... Read more
MainStage 3 3.4.3 - Live performance too...
Apple MainStage makes it easy to bring to the stage all the same instruments and effects that you love in your recording. Everything from the Sound Library and Smart Controls you're familiar with... Read more
Paperless 3.0.6 - $69.95
Paperless is a digital documents manager. Remember when everyone talked about how we would soon be a paperless society? Now it seems like we use paper more than ever. Let's face it - we need and we... Read more
TextMate 2.0.rc.29 - Code/markup editor...
TextMate is a versatile plain text editor with a unique and innovative feature set which caused it to win an Apple Design Award for Best Mac OS X Developer Tool in August 2006 A rapidly growing... Read more
Notability 4.0.4 - Note-taking and annot...
Notability is a powerful note-taker to annotate documents, sketch ideas, record lectures, take notes and more. It combines, typing, handwriting, audio recording, and photos so you can create notes... Read more
CleanMyMac X 4.4.4 - Delete files that w...
CleanMyMac makes space for the things you love. Sporting a range of ingenious new features, CleanMyMac lets you safely and intelligently scan and clean your entire system, delete large, unused files... Read more
Geekbench 4.4.0 - Measure processor and...
Geekbench provides a comprehensive set of benchmarks engineered to quickly and accurately measure processor and memory performance. Designed to make benchmarks easy to run and easy to understand,... Read more

Latest Forum Discussions

See All

TEPPEN guide - Tips and tricks for new p...
TEPPEN is a wild game that nobody asked for, but I’m sure glad it exists. Who would’ve thought that a CCG featuring Capcom characters could be so cool and weird? In case you’re not completely sure what TEPPEN is, make sure to check out our review... | Read more »
Dr. Mario World guide - Other games that...
We now live in a post-Dr. Mario World world, and I gotta say, things don’t feel too different. Nintendo continues to squirt out bad games on phones, causing all but the most stalwart fans of mobile games to question why they even bother... | Read more »
Strategy RPG Brown Dust introduces its b...
Epic turn-based RPG Brown Dust is set to turn 500 days old next week, and to celebrate, Neowiz has just unveiled its biggest and most exciting update yet, offering a host of new rewards, increased gacha rates, and a brand new feature that will... | Read more »
Dr. Mario World is yet another disappoin...
As soon as I booted up Dr. Mario World, I knew I wasn’t going to have fun with it. Nintendo’s record on phones thus far has been pretty spotty, with things trending downward as of late. [Read more] | Read more »
Retro Space Shooter P.3 is now available...
Shoot-em-ups tend to be a dime a dozen on the App Store, but every so often you come across one gem that aims to shake up the genre in a unique way. Developer Devjgame’s P.3 is the latest game seeking to do so this, working as a love letter to the... | Read more »
Void Tyrant guide - Guildins guide
I’ve still been putting a lot of time into Void Tyrant since it officially released last week, and it’s surprising how much stuff there is to uncover in such a simple-looking game. Just toray, I finished spending my Guildins on all available... | Read more »
Tactical RPG Brown Dust celebrates the s...
Neowiz is set to celebrate the summer by launching a 2-month long festival in its smash-hit RPG Brown Dust. The event kicks off today, and it’s divided into 4 parts, each of which will last two weeks. Brown Dust is all about collecting, upgrading,... | Read more »
Flappy Royale is an incredibly clever ta...
I spent the better part of my weekend playing Flappy Royale. I didn’t necessarily want to. I just felt like I had to. It’s a hypnotic experience that’s way too easy to just keep playing. | Read more »
Void Tyrant guide - General tips and tri...
Void Tyrant is a card-based dungeon-crawler that doesn’t fit in the mold of other games in the genre. Between the Blackjack-style combat and strange gear system alone, you’re left to your own devices to figure out how best to use everything to your... | Read more »
Webzen’s latest RPG First Hero is offici...
You might be busy sending your hulking Dark Knight into the midst of battle in Webzen’s other recent release: the long-anticipated MU Origin 2. But for something a little different, the South Korean publisher has launched First Hero. Released today... | Read more »

Price Scanner via MacPrices.net

Amazon drops prices, now offers clearance 13″...
Amazon has new dropped prices on clearance 13″ 2.3GHz Dual-Core non-Touch Bar MacBook Pros by $200 off Apple’s original MSRP, with prices now available starting at $1099. Shipping is free. Be sure to... Read more
2018 15″ MacBook Pros now on sale for $500 of...
Amazon has dropped prices on select clearance 2018 15″ 6-Core MacBook Pros to $500 off Apple’s original MSRP. Prices now start at $1899 shipped: – 2018 15″ 2.2GHz Touch Bar MacBook Pro Silver: $1899.... Read more
Price drop! Clearance 12″ 1.2GHz Silver MacBo...
Amazon has dropped their price on the recently-discontinued 12″ 1.2GHz Silver MacBook to $849.99 shipped. That’s $450 off Apple’s original MSRP for this model, and it’s the cheapest price available... Read more
Apple’s 21″ 3.0GHz 4K iMac drops to only $936...
Abt Electronics has dropped their price on clearance, previous-generation 21″ 3.0GHz 4K iMacs to only $936 shipped. That’s $363 off Apple’s original MSRP, and it’s the cheapest price we’ve seen so... Read more
Amazon’s Prime Day savings on Apple 11″ iPad...
Amazon has new 2018 Apple 11″ iPad Pros in stock today and on sale for up to $250 off Apple’s MSRP as part of their Prime Day sale (but Prime membership is NOT required for these savings). These are... Read more
Prime Day Apple iPhone deal: $100 off all iPh...
Boost Mobile is offering Apple’s new 2018 iPhone Xr, iPhone Xs, and Xs Max for $100 off MSRP. Their discount reduces the cost of an Xs to $899 for the 64GB models and $999 for the 64GB Xs Max. Price... Read more
Clearance 13″ 2.3GHz Dual-Core MacBook Pros a...
Focus Camera has clearance 2017 13″ 2.3GHz/128GB non-Touch Bar Dual-Core MacBook Pros on sale for $169 off Apple’s original MSRP. Shipping is free. Focus charges sales tax for NY & NJ residents... Read more
Amazon Prime Day deal: 9.7″ Apple iPads for $...
Amazon is offering new 9.7″ WiFi iPads with Apple Pencil support for $80-$100 off MSRP as part of their Prime Day sale, starting at only $249. These are the same iPads found in Apple’s retail and... Read more
Amazon Prime Day deal: 10% (up to $20) off Ap...
Amazon is offering discounts on new 2019 Apple AirPods ranging up to $20 (10%) off MSRP as part of their Prime Day sales. Shipping is free: – AirPods with Charging Case: $144.99 $15 off MSRP –... Read more
Amazon Prime Day deal: $50-$80 off Apple Watc...
Amazon has Apple Watch Series 4 and Series 3 models on sale for $50-$80 off Apple’s MSRP as part of their Prime Day deals with prices starting at only $199. Choose Amazon as the seller rather than a... Read more

Jobs Board

*Apple* Systems Architect/Engineer, Vice Pre...
…its vision to be the world's most trusted financial group. **Summary:** Apple Systems Architect/Engineer with strong knowledge of products and services related to Read more
*Apple* Graders/Inspectors (Seasonal/Hourly/...
…requirements. #COVAentryleveljobs ## Minimum Qualifications Some knowledge of agricultural and/or the apple industry is helpful as well as the ability to comprehend, Read more
Best Buy *Apple* Computing Master - Best Bu...
**710003BR** **Job Title:** Best Buy Apple Computing Master **Job Category:** Store Associates **Location Number:** 000171-Winchester Road-Store **Job Description:** Read more
Best Buy *Apple* Computing Master - Best Bu...
**709786BR** **Job Title:** Best Buy Apple Computing Master **Job Category:** Sales **Location Number:** 000430-Orange Park-Store **Job Description:** **What does a Read more
Geek Squad *Apple* Master Consultation Agen...
**709918BR** **Job Title:** Geek Squad Apple Master Consultation Agent **Job Category:** Services/Installation/Repair **Location Number:** 000106-Palmdale-Store Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.