Quicken Hotkeys For Your Date Fields

by Mike Hanson

Published 1998-11-01    Printer-friendly version

Download the code here

I use Quicken for my personal accounting, and I’ve always liked the hot keys for quickly changing the dates. I figured that this would be a great situation for a global template, utilizing some type of support library or class. The Quicken hotkeys are as follows:

T – Today

Plus – Tomorrow (current value plus one)

Minus – Yesterday (current value minus one)

M (as in Month) – The beginning of the current value’s month. If the current value is already the first day of the month, then it goes to the beginning of the previous month.

H (as in montH) – The end of the current value’s month. If the current value is already the last day of the month, then it goes to the end of the next month.

Y (as in Year) – The beginning of the current value’s year. If the current value is already the first day of the year, then it goes to the beginning of the previous year.

R (as in yeaR) – The end of the current value’s year. If the current value is already the last day of the year, then it goes to the end of the next year.

Basic Approach

To achieve this feat, we have to alert each of these keys for all date controls (ENTRY or SPIN) throughout the application. Once the alerts have been added, we have to watch for the alerted keys being pressed, and to respond accordingly.

We’ll write a class to handle the drudgery, reducing the generated source required in each procedure to approximately two lines (Init and TakeEvent).

I decided to use a Class rather than a library, because of the ABC template’s built-in support for utilizing ABC compliant classes. If I wanted to use a library, I would be stuck with determining the best way to include the necessary code in the various MAP sections. As everyone likes to point out: Why reinvent the wheel?

The Class

Class Header

The declaration of the class is wonderfully simple:

  !ABCIncludeFile
  OMIT('_EndOfInclude_', _MhAbQuickenDatePresent_)
_MhAbQuickenDatePresent_  EQUATE(1)
MH::QuickenDateClass      CLASS, TYPE, |
                            MODULE('MHABQDTE.CLW'),
                            LINK('MHABQDTE.CLW',|
                                 _ABCLinkMode_),|
                            DLL(_ABCDllMode_)
Init                        PROCEDURE
TakeEvent                   PROCEDURE,BYTE,PROC
                          END!CLASS
 _EndOfInclude_

As usual, an ABC-compliant INC file must start with !ABCIncludeFile. This instructs the class header checker to include our class in its scan. The next couple of lines, along with the last line, are to ensure that the header is included only once within a given scope.

The interesting thing to note here is that our class encapsulates no data. As I mentioned earlier, I could have made this into a library rather than a class; the lack of class data makes this possible. Alternatively, if each usage of the functions requires persistent data attached to that usage, then the need for an OOP-based implementation would have been more obvious. I might want to add some member data later, though, so it’s probably best to use the OOP approach from the start.

As mentioned earlier, there are only two methods: Init and TakeEvent.

Class Module

Support Procedures

To support our class’ efforts, I’ve created various local procedures in the class module. I could have included these as "PRIVATE" method in my class, but I decided to do it this way instead. The main reason is that the support procedures don’t need to access class data. (Considering there isn’t any class data, this kind of makes sense <grin>.) The start of our module looks like this:

  MEMBER
  INCLUDE('MHAbQDte.INC')
  INCLUDE('EQUATES.CLW')
  INCLUDE('KEYCODES.CLW')
  INCLUDE('ABERROR.INC')
  MAP
    IsDateEntryControl(LONG Control),BYTE
    ChangeDate(LONG Control, |
               LONG D),BYTE
    ChangeDate(LONG Control, |
               BYTE M, BYTE D, |
               SHORT Y),BYTE
    END!

The module needs its include file, along with the basic Clarion keycodes and equates. It also uses some of the error equates (e.g. Level:Benign), so those are also included. (The class doesn’t, however, make use of the error class itself.)

The support procedures include IsDateEntryControl, which checks to see if a given control is a candidate for Quicken hotkeys, and the two ChangeDate procedures, which change the value of the date in the control.

If you are wondering why there are two ChangeDate procedures (and how this is even possible), this is an example of "overloading". Note that each procedure has a different parameter list, so that the compiler can determine which procedure we are calling. I have two of them because sometimes I want to change the date and I know the exact value, and sometimes I known the individual MM/DD/YYYY elements. To save myself the trouble of always using the DATE function, I decided to create an extra version of ChangeDate to deal with it for me. (Call me lazy.)

The IsDateEntryControl function looks like this:

IsDateEntryControl   FUNCTION(LONG Control)
  CODE
  IF Control
    CASE Control{PROP:Type}
    OF CREATE:Entry OROF CREATE:Spin
      IF UPPER(SUB(Control{PROP:Text}, 1, 1)) = 'D'
        IF ~Control{PROP:ReadOnly}
          RETURN True
        END!IF
      END!IF
    END!CASE
  END!IF
  RETURN False

As you can see, it must be an ENTRY or SPIN control with an @D picture, and it must not have the READONLY attribute. If all of these conditions are true, then the function returns True. Otherwise, it drops through to the bottom and returns False.

The ChangeDate functions look like this:

ChangeDate     FUNCTION(LONG Control, LONG D)
  CODE
  CHANGE(Control, D)
  RETURN Level:Notify

ChangeDate     FUNCTION(LONG Control, |
                        BYTE M, BYTE D, |
                        SHORT Y)
  CODE
  RETURN ChangeDate(Control, DATE(M, D, Y))

Notice that the first one is responsible for changing the value of the date and displaying the result. It does both of these with one CHANGE() command. At this point the function always returns Level:Notify, to indicate that something has happened. In the future I may decide to change this behavior, so I’ve coded it this way for now. The second version of ChangeDate simply uses the DATE function to pass a value to the other ChangeDate.

Class Methods

The Init method is straightforward:

MH::QuickenDateClass.Init   PROCEDURE
C                           LONG,AUTO
  CODE
  LOOP C = FIRSTFIELD() TO LASTFIELD()
    IF IsDateEntryControl(C)
      C{PROP:Alrt,255} = PlusKey
      C{PROP:Alrt,255} = MinusKey
      C{PROP:Alrt,255} = TKey
      C{PROP:Alrt,255} = MKey
      C{PROP:Alrt,255} = HKey
      C{PROP:Alrt,255} = YKey
      C{PROP:Alrt,255} = RKey
    END!IF
  END!LOOP

It simply loops through all of the controls on the window, checking if each of them is an applicable date entry control. When it finds one, it alerts all of the necessary keys. PROP:Alrt is an array, and notice that each PROP:Alrt assignment uses element number 255. If you don’t know how many elements are already present, you can use a high number to drop the new alert key on top of any existing ones.

The TakeEvent method is a little stranger:

MH::QuickenDateClass.TakeEvent FUNCTION
C         LONG,AUTO
D         LONG,AUTO
M         BYTE,AUTO
Y         SHORT,AUTO
R         BYTE(Level:Benign)
  CODE
  IF EVENT() = EVENT:AlertKey AND FIELD()
    C = FIELD()
    IF IsDateEntryControl(C)
      D = CONTENTS(C)
      CASE KEYCODE()
      OF PlusKey
        R = ChangeDate(C, D+1)
      OF MinusKey
        R = ChangeDate(C, D-1)
      OF TKey
        R = ChangeDate(C, TODAY())
      OF MKey
        R = ChangeDate(C, |
          MONTH(D) + |
          CHOOSE(DAY(D)=1,11,12), |
          1, |
          YEAR(D)-1)
      OF HKey
        R = ChangeDate(C, |
        DATE(MONTH(D) + |
        CHOOSE(DAY(D+1)=1, 2, 1), |
        1, |
        YEAR(D)) - 1)
      OF YKey
        R = ChangeDate(C, 1, 1, |
        YEAR(D) - |
        CHOOSE(DAY(D)=1 |
        AND MONTH(D)=1, 1, 0))
      OF RKey
        R = ChangeDate(C, 12, 31, |
        YEAR(D) + CHOOSE(DAY(D)=31 |
        AND MONTH(D)=12, 1, 0))
      END!CASE
    END!IF
  END!IF
  RETURN R

First it checks that EVENT:AlertKey has occurred, and that the alert key belongs to a FIELD() (rather than the Window). If the field is an appropriate date entry control, then it proceeds to determine the required action, if any.

If it’s PlusKey, MinusKey, or TKey, then the action is quite simple: change the date to the new value. Handling YKey and RKey is similar, because the first day of the year is always 1/1 and the last is always 12/31. The only difference is that we’re using the alternate form of ChangeDate. The calculations for the beginning and end of the month, however, are much harder. Here are the tricks that I used:

  • To calculate the beginning of the month (or the beginning of the previous month), I utilize a quirk in Clarion’s DATE function. If you specify a month greater than 12, it automatically subtracts 12 and adds 1 to the year. So if the date is 5/15/1998, then first day of the month is DATE(5+12, 1, 1998). If, however, the date is 5/1/1998, then the first day of the previous month is DATE(5+11, 1, 1998).
  • The hard part in determining the end of the month is that the months are different lengths. I actually calculate the first day of the next month, then subtract one to determine the end of the current month. For example, if the current date is 5/15/1998, then the last day of the month is DATE(5+1, 1, 1998)-1. If, however, we’re already at the end of the month at 5/31/1998, then the first day of the next month is DATE(5+2, 1, 1998)-1. I determine if we’re already at the end of the month by adding one to the current date and checking if the day equals one.

The Template

We’re going to use a global extension template, so that we can add to once to the application to get complete coverage. Remember from my prior articles that a global template can place code into the embeds of all procedures. Here’s the first line of the template:

#EXTENSION(mhQuickenDate,'Quicken Date Entry'),APPLICATION

The APPLICATION attribute indicates that the extension can only be added to the global extensions (contrasted with the PROCEDURE attribute). There are no prompts to enter, as the actions of the template are consistent and well defined. Next we have to place our code at the various embed points. First we ensure that the ABC include files have been scanned:

#ATSTART
  #CALL(%ReadABCFiles(ABC))
#ENDAT

If the window for the current procedure contains at least one date entry control, then the MH::QuickenDateClass class header file must be included in the module:

#AT(%GatherSymbols),WHERE(%HasQuickenDateControl())
  #CALL(%AddModuleIncludeFile(ABC), 'MH::QuickenDateClass')
#ENDAT

If there is a date entry control, then the class is added to the procedure’s local data section:

#AT(%LocalDataClasses), WHERE(%HasQuickenDateControl())
mhQuickenDate MH::QuickenDateClass
#ENDAT

After the window is opened, the controls are initialized with the PROP:Alrt keys:

#AT(%WindowManagerMethodCodeSection, 'Init', '(),BYTE'),|
    WHERE(%HasQuickenDateControl()), PRIORITY(8050)
mhQuickenDate.Init
#ENDAT

Finally, the events are monitored for handling Quicken date hotkeys.

#AT(%WindowManagerMethodCodeSection, 'TakeEvent',|
     '(),BYTE'), |
     WHERE(%HasQuickenDateControl()), |
     PRIORITY(1300)
ReturnValue = mhQuickenDate.TakeEvent()
IF ReturnValue THEN RETURN ReturnValue.
#ENDAT

Most of these embeds apply only WHERE(%HasQuickenDateControl()). This template group looks like this:

#GROUP(%HasQuickenDateControl),AUTO
#EQUATE(%RetVal, %False)
#FOR(%Control)
  #CASE(%ControlType)
  #OF('ENTRY')
  #OROF('SPIN')
    #IF(UPPER(SUB(EXTRACT(%ControlStatement,|
        %ControlType,1),2,1)) = 'D')
      #SET(%RetVal, %True)
      #BREAK
    #ENDIF
  #ENDCASE
#ENDFOR
#RETURN(%RetVal)

The AUTO attribute indicates that the group will be declaring one or more new symbols, but those symbols are no longer needed when the group has finished. In this case, we are creating a %RetVal and initializing it to %False. Note the similarity between this #GROUP and IsDateEntryControl in the Class module. They both perform the same function.

Conclusion

Well there you have it. With the addition of a single global extension, your entire application has really cool Quicken-style date entry hot fields. Not only that, but now you should be even more comfortable with classes, templates, and alert key processing, and date manipulation. For the complete source code to this and many other useful and educational templates and classes, go to my web site at www.BoxsoftDevelopment.com and download the free MHAB*.EXE.

Printer-friendly version

 
 

Search

 

Advanced Search
Topical Index

Related Articles

Subscribe to
ClarionMag

One year: $184

(includes all back issues since '99)

Renewals from $134

Two years: $274

Renewals from $224

More Info

Subscribe Now!

ClarionMag Blog

RSS Feeds

Updates via Email

Enter your Email


Powered by FeedBlitz

Quick Links