/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- * * The contents of this file are subject to the Netscape Public License * Version 1.0 (the "NPL"); you may not use this file except in * compliance with the NPL. You may obtain a copy of the NPL at * http://www.mozilla.org/NPL/ * * Software distributed under the NPL is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL * for the specific language governing rights and limitations under the * NPL. * * The Initial Developer of this code under the NPL is Netscape * Communications Corporation. Portions created by Netscape are * Copyright (C) 1998 Netscape Communications Corporation. All Rights * Reserved. */ /*====================================================================================*/ #pragma mark INCLUDE FILES /*====================================================================================*/ #include "CDateView.h" /*====================================================================================*/ #pragma mark TYPEDEFS /*====================================================================================*/ /*====================================================================================*/ #pragma mark CONSTANTS /*====================================================================================*/ // Pane IDs static const PaneIDT eDayEditFieldID = 1; static const PaneIDT eMonthEditFieldID = 2; static const PaneIDT eYearEditFieldID = 3; // Resource IDs static const short cArrowWidth = 11; static const short cArrowHeight = 9; static const UInt32 cBroadcastMessageDelay = 12; /*====================================================================================*/ #pragma mark INTERNAL CLASS DECLARATIONS /*====================================================================================*/ class CDateArrowButton : public LButton { public: enum { class_ID = 'DaBt' }; CDateArrowButton(LStream *inStream); // When clicking in button, send these messages enum { msg_ClickUpArrow = CDateView::paneID_UpButton, msg_ClickDownArrow = CDateView::paneID_DownButton }; virtual void SimulateHotSpotClick(Int16 inHotSpot); protected: // Overriden methods virtual void DisableSelf(void); virtual void EnableSelf(void); virtual Boolean TrackHotSpot(Int16 inHotSpot, Point inPoint, Int16 inModifiers); virtual void HotSpotAction(Int16 inHotSpot, Boolean inCurrInside, Boolean inPrevInside); virtual void HotSpotResult(short inHotSpot); // Instance variables ========================================================== UInt32 mNextBroadcastTime; // Last time a message was broadcast UInt16 mBroadcastCount; // Number of continuous braodcast messages }; /*====================================================================================*/ #pragma mark INTERNAL FUNCTION PROTOTYPES /*====================================================================================*/ #ifdef Debug_Signal #define AssertFail_(test) ThrowIfNot_(test) #else // Debug_Signal #define AssertFail_(test) ((void) 0) #endif // Debug_Signal static inline Boolean IsBetween(long inVal, long inMin, long inMax) { return ((inVal >= inMin) && (inVal <= inMax)); } /*====================================================================================*/ #pragma mark CLASS IMPLEMENTATIONS /*====================================================================================*/ #pragma mark - /*====================================================================================== Register classes related to this class. ======================================================================================*/ void CDateView::RegisterDateClasses(void) { RegisterClass_(CDateView); RegisterClass_(CDateField); RegisterClass_(CDateArrowButton); } /*====================================================================================== Constructor. ======================================================================================*/ CDateView::CDateView(LStream *inStream) : LView(inStream) { mDayField = mMonthField = mYearField = nil; } /*====================================================================================== Constructor. ======================================================================================*/ void CDateView::FinishCreateSelf(void) { // Load in date/time resources Intl0Hndl int0H = (Intl0Hndl) ::GetIntlResource(0); UInt8 leadingDayChar, leadingMonthChar, separatingChar; if ( int0H ) { switch ( (**int0H).dateOrder ) { case mdy: mDayPosition = 2; mMonthPosition = 1; mYearPosition = 3; break; case dmy: mDayPosition = 1; mMonthPosition = 2; mYearPosition = 3; break; case ymd: mDayPosition = 3; mMonthPosition = 2; mYearPosition = 1; break; case myd: mDayPosition = 3; mMonthPosition = 1; mYearPosition = 2; break; case dym: mDayPosition = 1; mMonthPosition = 3; mYearPosition = 2; break; default: /* ydm */ mDayPosition = 2; mMonthPosition = 3; mYearPosition = 1; break; } leadingDayChar = ((**int0H).shrtDateFmt & dayLdingZ) ? '0' : ' '; leadingMonthChar = ((**int0H).shrtDateFmt & mntLdingZ) ? '0' : ' '; separatingChar = (**int0H).dateSep; } else { // Set to defaults separatingChar = '/'; leadingDayChar = '0'; leadingMonthChar = '0'; mDayPosition = 2; mMonthPosition = 1; mYearPosition = 3; } SetRefreshAllWhenResized(false); // Must create the subpanes here for correct function of tabbed edit fields CreateDateFields(leadingDayChar, leadingMonthChar, separatingChar); } /*====================================================================================== Destructor. ======================================================================================*/ CDateView::~CDateView(void) { } /*====================================================================================== Determine if the specified date is valid. inYear (cMinViewYear..cMaxViewYear) inMonth (1..12) inDay (1..31) ======================================================================================*/ Boolean CDateView::IsValidDate(Int16 inYear, UInt8 inMonth, UInt8 inDay) { if ( (inDay < 1) || (inDay > 31) || (inMonth < 1) || (inMonth > 12) || (inYear < cMinViewYear) || (inYear > cMaxViewYear) ) { return false; } DateTimeRec dtRec = { inYear, inMonth, inDay, 1, 1, 1 }; UInt32 seconds; ::DateToSeconds(&dtRec, &seconds); ::SecondsToDate(seconds, &dtRec); return ((inDay == dtRec.day) && (inMonth == dtRec.month) && (inYear == dtRec.year)); } /*====================================================================================== Get the currently displayed date. outYear (cMinViewYear..cMaxViewYear) outMonth (1..12) outDay (1..31) ======================================================================================*/ void CDateView::GetDate(Int16 *outYear, UInt8 *outMonth, UInt8 *outDay) { *outYear = mYearField->GetValue(); *outMonth = mMonthField->GetValue(); *outDay = mDayField->GetValue(); } /*====================================================================================== Set the displayed date to the date specified by the input parameters. If the input parameters specify a date that is not valid, return false and don't do anything; otherwise return true. inYear (cMinViewYear..cMaxViewYear) inMonth (1..12) inDay (1..31) ======================================================================================*/ Boolean CDateView::SetDate(Int16 inYear, UInt8 inMonth, UInt8 inDay) { Boolean rtnVal = IsValidDate(inYear, inMonth, inDay); if ( rtnVal ) { SetDateString(mYearField, inYear, '0'); SetDateString(mMonthField, inMonth, mMonthField->GetLeadingChar()); SetDateString(mDayField, inDay, mDayField->GetLeadingChar()); } return rtnVal; } /*====================================================================================== Set the date displayed to today's date. ======================================================================================*/ void CDateView::SetToToday(void) { UInt32 seconds; ::GetDateTime(&seconds); DateTimeRec dtRec; ::SecondsToDate(seconds, &dtRec); if ( dtRec.year < cMinViewYear ) { dtRec.year = cMinViewYear; } else if ( dtRec.year > cMaxViewYear ) { dtRec.year = cMaxViewYear; } SetDate(dtRec.year, dtRec.month, dtRec.day); } /*====================================================================================== Select the specified date field in this view. ======================================================================================*/ void CDateView::SelectDateField(Int16 inField) { PaneIDT paneID; switch ( inField ) { case eYearField: paneID = eYearEditFieldID; break; case eMonthField: paneID = eMonthEditFieldID; break; case eDayField: paneID = eDayEditFieldID; break; default: Assert_(false); return; } CDateField *newTarget = (CDateField *) FindPaneByID(paneID); Assert_(newTarget != nil); if ( LCommander::SwitchTarget(newTarget) ) { newTarget->SelectAll(); } } /*====================================================================================== Select the first date field in this view if one is not yet selected. ======================================================================================*/ void CDateView::Select(void) { // Check if we're already there if ( ContainsTarget() ) return; // Find the first field LEditField *newTarget; if ( mDayPosition == 1 ) { newTarget = mDayField; } else if ( mMonthPosition == 1 ) { newTarget = mMonthField; } else { Assert_(mYearPosition == 1); newTarget = mYearField; } if ( LCommander::SwitchTarget(newTarget) ) { newTarget->SelectAll(); } } /*====================================================================================== Return true if one of the date fields in this view is the current target. ======================================================================================*/ Boolean CDateView::ContainsTarget(void) { return (mDayField->IsTarget() || mMonthField->IsTarget() || mYearField->IsTarget()); } /*====================================================================================== Listen to message from broadcasters. ======================================================================================*/ void CDateView::ListenToMessage(MessageT inMessage, void *ioParam) { #pragma unused(ioParam) switch ( inMessage ) { case CDateArrowButton::msg_ClickUpArrow: case CDateArrowButton::msg_ClickDownArrow: DoClickArrow(inMessage == CDateArrowButton::msg_ClickUpArrow); break; case CDateField::msg_HideDateArrows: case CDateField::msg_ShowDateArrows: ShowHideArrows(inMessage == CDateField::msg_ShowDateArrows); break; case CDateField::msg_UserChangedText: BroadcastMessage(msg_DateViewChanged, this); break; } } /*====================================================================================== Process a click in an arrow from a key. ======================================================================================*/ void CDateView::DoKeyArrow(Boolean inClickUpArrow) { LControl *theControl; if ( inClickUpArrow ) { theControl = (LControl *) FindPaneByID(paneID_UpButton); } else { theControl = (LControl *) FindPaneByID(paneID_DownButton); } if ( theControl && theControl->FocusDraw() ) { theControl->SimulateHotSpotClick(1); } } /*====================================================================================== Process a click in an arrow. ======================================================================================*/ void CDateView::DoClickArrow(Boolean inClickUpArrow) { #pragma unused(inClickUpArrow) CDateField *theDateField = (CDateField *) LCommander::GetTarget(); Assert_(theDateField); if ( !theDateField ) return; Int16 year; UInt8 month, day; GetDate(&year, &month, &day); // Validate that the current character is OK for the active field Int16 adder = inClickUpArrow ? 1 : -1; switch ( theDateField->GetPaneID() ) { case eDayEditFieldID: day += adder; if ( day > 31 ) { day = 1; } else if ( day < 1 ) { day = 31; } break; case eMonthEditFieldID: month += adder; if ( month > 12 ) { month = 1; } else if ( month < 1 ) { month = 12; } break; default: year += adder; if ( year > cMaxViewYear ) { year = cMinViewYear; } else if ( year < cMinViewYear ) { year = cMaxViewYear; } break; } if ( !SetDate(year, month, day) ) { if ( adder > 0 ) { // Day must be wrong, too large! SetDate(year, month, 1); } else { while ( !SetDate(year, month, --day) ) { ; // Nothing } } } } /*====================================================================================== Hide or show the arrows. ======================================================================================*/ void CDateView::ShowHideArrows(Boolean inShow) { ShowHideArrow(FindPaneByID(paneID_UpButton), inShow); ShowHideArrow(FindPaneByID(paneID_DownButton), inShow); } /*====================================================================================== Filter for incoming data characters. ======================================================================================*/ EKeyStatus CDateView::DateFieldFilter(TEHandle inMacTEH, Char16 theKey, Char16& theChar, UInt16 inModifiers) { EKeyStatus theKeyStatus = keyStatus_PassUp; //Char16 theKey = inKeyEvent.message; //Char16 theChar = theKey & charCodeMask; CDateField *theDateField = (CDateField *) LCommander::GetTarget(); Assert_(theDateField); CDateView *theDateView = (CDateView *) theDateField->GetSuperView(); Assert_(theDateView); if ( !theDateField || !theDateView ) return theKeyStatus; switch (theChar) { case char_UpArrow: case char_DownArrow: theKeyStatus = keyStatus_Ignore; theDateView->DoKeyArrow(theChar == char_UpArrow); break; case char_Tab: case char_Enter: case char_Return: case char_Escape: theKeyStatus = keyStatus_PassUp; break; default: if ( UKeyFilters::IsPrintingChar(theChar) && UKeyFilters::IsNumberChar(theChar) ) { PaneIDT paneID = theDateField->GetPaneID(); Str15 currentText; theDateField->GetDescriptor(currentText); theKeyStatus = keyStatus_Input; Int16 year; UInt8 month, day; theDateView->GetDate(&year, &month, &day); // Validate that the current character is OK for the active field Str15 numString; long wouldBeNumber; theDateField->GetDescriptor(numString); Assert_(numString[0] == 2 || numString[0] == 4); numString[numString[0] - 1] = currentText[currentText[0]]; numString[numString[0]] = theChar; ::StringToNum(numString, &wouldBeNumber); switch ( paneID ) { case eDayEditFieldID: day = wouldBeNumber; break; case eMonthEditFieldID: month = wouldBeNumber; break; default: // eYearEditFieldID year = wouldBeNumber; if ( year > cMaxViewYear ) year -= 100; if ( year < cMinViewYear ) year += 100; if (year != wouldBeNumber) { Str15 temp; theDateField->GetDescriptor(temp); temp[1] = '0' + year/1000; temp[2] = '0' + (year/100)%10; theDateField->SetDescriptor(temp); } break; } if ( !theDateView->IsValidDate(year, month, day) || ((paneID != eYearEditFieldID) && (numString[1] == '0')) ) { if ( theChar == '0' ) theKeyStatus = keyStatus_Reject; else { currentText[1] = currentText[2] = theDateField->GetLeadingChar(); theDateField->SetDescriptor(currentText); } } } else { theKeyStatus = keyStatus_Reject; } break; } return theKeyStatus; } /*====================================================================================== Set the edit field date. ======================================================================================*/ void CDateView::SetDateString(LEditField *inField, UInt16 inValue, UInt8 inLeadingChar) { Str15 valueString, tempString; ::NumToString(inValue, valueString); if ( valueString[0] == 1 ) { valueString[2] = valueString[1]; valueString[1] = inLeadingChar; valueString[0] = 2; } inField->GetDescriptor(tempString); if (! ::EqualString(tempString, valueString, false, false)) { inField->SetDescriptor(valueString); if ( inField->IsTarget() ) { inField->SelectAll(); } inField->Draw(nil); } } /*====================================================================================== Show or hide the arrow nicely. ======================================================================================*/ void CDateView::ShowHideArrow(LPane *inArrow, Boolean inShow) { if ( inArrow ) { if ( inShow ) { inArrow->DontRefresh(true); if ( !inArrow->IsVisible() ) { inArrow->Show(); inArrow->DontRefresh(); inArrow->Draw(nil); } } else { inArrow->Hide(); } } } /*====================================================================================== Finish creating the search dialog. ======================================================================================*/ void CDateView::CreateDateFields(UInt8 inLeadingDayChar, UInt8 inLeadingMonthChar, UInt8 inSeparatingChar) { // Init the panes in the view UInt8 separatingString[2] = { 1, inSeparatingChar }; PaneIDT field1ID, field2ID, field3ID; UInt8 leadingChar1, leadingChar2, leadingChar3; switch ( mDayPosition ) { case 1: field1ID = eDayEditFieldID; leadingChar1 = inLeadingDayChar; if ( mMonthPosition == 2 ) { field2ID = eMonthEditFieldID; leadingChar2 = inLeadingMonthChar; field3ID = eYearEditFieldID; leadingChar3 = '0'; } else { field3ID = eMonthEditFieldID; leadingChar3 = inLeadingMonthChar; field2ID = eYearEditFieldID; leadingChar2 = '0'; } break; case 2: field2ID = eDayEditFieldID; leadingChar2 = inLeadingDayChar; if ( mMonthPosition == 1 ) { field1ID = eMonthEditFieldID; leadingChar1 = inLeadingMonthChar; field3ID = eYearEditFieldID; leadingChar3 = '0'; } else { field3ID = eMonthEditFieldID; leadingChar3 = inLeadingMonthChar; field1ID = eYearEditFieldID; leadingChar1 = '0'; } break; default: field3ID = eDayEditFieldID; leadingChar3 = inLeadingDayChar; if ( mMonthPosition == 1 ) { field1ID = eMonthEditFieldID; leadingChar1 = inLeadingMonthChar; field2ID = eYearEditFieldID; leadingChar2 = '0'; } else { field2ID = eMonthEditFieldID; leadingChar2 = inLeadingMonthChar; field1ID = eYearEditFieldID; leadingChar1 = '0'; } break; } // Field 1 CDateField *theDateField = (CDateField *) FindPaneByID(paneID_DateField1); ThrowIfResFail_(theDateField); theDateField->SetPaneID(field1ID); theDateField->SetLeadingChar(leadingChar1); theDateField->SetKeyFilter(DateFieldFilter); theDateField->AddListener(this); // Separator 1 LCaption *theCaption = (LCaption *) FindPaneByID(paneID_Separator1); ThrowIfResFail_(theCaption); theCaption->SetDescriptor(separatingString); // Field 2 theDateField = (CDateField *) FindPaneByID(paneID_DateField2); ThrowIfResFail_(theDateField); theDateField->SetPaneID(field2ID); theDateField->SetLeadingChar(leadingChar2); theDateField->SetKeyFilter(DateFieldFilter); theDateField->AddListener(this); // Separator 2 theCaption = (LCaption *) FindPaneByID(paneID_Separator2); ThrowIfResFail_(theCaption); theCaption->SetDescriptor(separatingString); // Field 3 theDateField = (CDateField *) FindPaneByID(paneID_DateField3); ThrowIfResFail_(theDateField); theDateField->SetPaneID(field3ID); theDateField->SetLeadingChar(leadingChar3); theDateField->SetKeyFilter(DateFieldFilter); theDateField->AddListener(this); // Up/down arrows LButton *theButton = (LButton *) FindPaneByID(paneID_UpButton); ThrowIfResFail_(theButton); theButton->AddListener(this); theButton = (LButton *) FindPaneByID(paneID_DownButton); ThrowIfResFail_(theButton); theButton->AddListener(this); mDayField = (CDateField *) FindPaneByID(eDayEditFieldID); mMonthField = (CDateField *) FindPaneByID(eMonthEditFieldID); mYearField = (CDateField *) FindPaneByID(eYearEditFieldID); // Set the date to the current date ShowHideArrows(eHideArrows); SetToToday(); } #pragma mark - /*====================================================================================== Constructor. ======================================================================================*/ CDateField::CDateField(LStream *inStream) : LGAEditField(inStream) { mLeadingChar = '/'; Str15 initString = { 2, mLeadingChar, mLeadingChar }; SetDescriptor(initString); } /*====================================================================================== Set the text for the date field. This method does not refresh or redraw the field after setting the text. ======================================================================================*/ void CDateField::SetDescriptor(ConstStr255Param inDescriptor) { if ( FocusExposed() ) { //StEmptyVisRgn emptyRgn(GetMacPort()); ::TESetText(inDescriptor + 1, StrLength(inDescriptor), mTextEditH); } else { ::TESetText(inDescriptor + 1, StrLength(inDescriptor), mTextEditH); } } /*====================================================================================== Disallow pasting and cutting. ======================================================================================*/ void CDateField::FindCommandStatus(CommandT inCommand, Boolean &outEnabled, Boolean &outUsesMark, Char16 &outMark, Str255 outName) { switch ( inCommand ) { case cmd_Paste: case cmd_Cut: case cmd_Clear: case cmd_SelectAll: outEnabled = false; break; default: LEditField::FindCommandStatus(inCommand, outEnabled, outUsesMark, outMark, outName); break; } } /*====================================================================================== Select all text when clicking. ======================================================================================*/ void CDateField::ClickSelf(const SMouseDownEvent &inMouseDown) { #pragma unused(inMouseDown) if ( !IsTarget() ) { if ( LCommander::SwitchTarget(this) ) { this->SelectAll(); } } } /*====================================================================================== Handle a key press. ======================================================================================*/ Boolean CDateField::HandleKeyPress(const EventRecord &inKeyEvent) { Boolean keyHandled; if ( (inKeyEvent.modifiers & cmdKey) || !mKeyFilter ) { keyHandled = LCommander::HandleKeyPress(inKeyEvent); } else { Char16 theChar = inKeyEvent.message & charCodeMask; //EKeyStatus theKeyStatus = (*mKeyFilter)(inKeyEvent, (**mTextEditH).selStart); EKeyStatus theKeyStatus = (*mKeyFilter)(mTextEditH, inKeyEvent.message & keyCodeMask, theChar, inKeyEvent.modifiers); switch ( theKeyStatus ) { case keyStatus_PassUp: keyHandled = LCommander::HandleKeyPress(inKeyEvent); break; case keyStatus_Reject: SysBeep(1); break; case keyStatus_Input: { // Get the current string Str15 numString; GetDescriptor(numString); Assert_(numString[0] == 2 || numString[0] == 4); numString[numString[0] - 1] = numString[numString[0]]; numString[numString[0]] = theChar; Boolean doDraw = FocusExposed(); SetDescriptor(numString); SelectAll(); if ( doDraw ) { DrawSelf(); } keyHandled = true; } break; } } return keyHandled; } /*====================================================================================== Select all text when clicking. ======================================================================================*/ void CDateField::AdjustCursorSelf(Point inPortPt, const EventRecord &inMacEvent) { LPane::AdjustCursorSelf(inPortPt, inMacEvent); } /*====================================================================================== EditField is becoming the Target. ======================================================================================*/ void CDateField::BeTarget(void) { StFocusAndClipIfHidden focus(this); ::TEActivate(mTextEditH); // Show active selection BroadcastMessage(msg_ShowDateArrows); } /*====================================================================================== EditField is no longer the Target. ======================================================================================*/ void CDateField::DontBeTarget(void) { StFocusAndClipIfHidden focus(this); ::TEDeactivate(mTextEditH); // Show inactive selection BroadcastMessage(msg_HideDateArrows); } /*====================================================================================== The user has changed some text, broadcast a message. ======================================================================================*/ void CDateField::UserChangedText(void) { LEditField::UserChangedText(); BroadcastMessage(msg_UserChangedText, this); } #pragma mark - /*====================================================================================== Constructor. ======================================================================================*/ CDateArrowButton::CDateArrowButton(LStream *inStream) : LButton(inStream) { mNextBroadcastTime = 0; mBroadcastCount = 0; } /*====================================================================================== ======================================================================================*/ void CDateArrowButton::DisableSelf(void) { Draw(nil); } /*====================================================================================== ======================================================================================*/ void CDateArrowButton::EnableSelf(void) { Draw(nil); } /*====================================================================================== ======================================================================================*/ Boolean CDateArrowButton::TrackHotSpot(Int16 inHotSpot, Point inPoint, Int16 inModifiers) { mBroadcastCount = 0; mNextBroadcastTime = 0; return LButton::TrackHotSpot(inHotSpot, inPoint, inModifiers); } /*====================================================================================== ======================================================================================*/ void CDateArrowButton::HotSpotAction(Int16 inHotSpot, Boolean inCurrInside, Boolean inPrevInside) { LButton::HotSpotAction(inHotSpot, inCurrInside, inPrevInside); if ( inCurrInside ) { if ( mNextBroadcastTime < LMGetTicks() ) { BroadcastValueMessage(); if ( mBroadcastCount > 4 ) { mNextBroadcastTime = LMGetTicks() + (cBroadcastMessageDelay/3); } else { ++mBroadcastCount; mNextBroadcastTime = LMGetTicks() + cBroadcastMessageDelay; } } } else { mBroadcastCount = 0; } } /*====================================================================================== ======================================================================================*/ void CDateArrowButton::HotSpotResult(short inHotSpot) { if ( mBroadcastCount ) { HotSpotAction(inHotSpot, false, true); } else { LButton::HotSpotResult(inHotSpot); } } /*====================================================================================== ======================================================================================*/ void CDateArrowButton::SimulateHotSpotClick(Int16 inHotSpot) { if ( IsEnabled() ) { mBroadcastCount = 0; mNextBroadcastTime = 0; unsigned long endTicks; HotSpotAction(inHotSpot, true, false); // Do action for click inside ::Delay(4, &endTicks); //HotSpotAction(inHotSpot, false, true); // Undo visual effect HotSpotResult(inHotSpot); ::Delay(4, &endTicks); } }