/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- * * The contents of this file are subject to the Netscape Public * License Version 1.1 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.mozilla.org/NPL/ * * Software distributed under the License is distributed on an "AS * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or * implied. See the License for the specific language governing * rights and limitations under the License. * * The Original Code is mozilla.org code. * * The Initial Developer of the Original Code is Netscape * Communications Corporation. Portions created by Netscape are * Copyright (C) 1998 Netscape Communications Corporation. All * Rights Reserved. * * Contributor(s): */ #include "nsTextWidget.h" #include #include #include using std::auto_ptr; #if TARGET_CARBON || (UNIVERSAL_INTERFACES_VERSION >= 0x0330) #include #endif NS_IMPL_ADDREF(nsTextWidget); NS_IMPL_RELEASE(nsTextWidget); //------------------------------------------------------------------------- // ¥ NOTE ABOUT MENU HANDLING ¥ // // The definitions below, as well as the NS_MENU_SELECTED code in // DispatchEvent() are temporary hacks only. They require the menu // resources to be created under Contructor with the standard // PowerPlant menu IDs. All that will go away because: // - nsTextWidget will be rewritten as an XP widget by the Editor team. // - menu handling will be rewritten by the XPApp team. //------------------------------------------------------------------------- #include typedef SInt32 MessageT; typedef PRUint32 Uint32; const MessageT cmd_Cut = 12; // nil const MessageT cmd_Copy = 13; // nil const MessageT cmd_Paste = 14; // nil const MessageT cmd_Clear = 15; // nil const MessageT cmd_SelectAll = 16; // nil enum { menu_Apple = 128, menu_File, menu_Edit }; //------------------------------------------------------------------------- // // //------------------------------------------------------------------------- nsTextWidget::nsTextWidget() : nsMacControl(), nsITextWidget(), Repeater() { NS_INIT_REFCNT(); WIDGET_SET_CLASSNAME("nsTextWidget"); SetControlType(kControlEditTextProc); mIsPassword = PR_FALSE; mIsReadOnly = PR_FALSE; AcceptFocusOnClick(PR_TRUE); } //------------------------------------------------------------------------- // // //------------------------------------------------------------------------- nsTextWidget::~nsTextWidget() { } //------------------------------------------------------------------------- // // //------------------------------------------------------------------------- NS_IMETHODIMP nsTextWidget::Create(nsIWidget *aParent, const nsRect &aRect, EVENT_CALLBACK aHandleEventFunction, nsIDeviceContext *aContext, nsIAppShell *aAppShell, nsIToolkit *aToolkit, nsWidgetInitData *aInitData) { if (aInitData) { nsTextWidgetInitData* initData = (nsTextWidgetInitData*)aInitData; if (initData->mIsPassword) { SetControlType(kControlEditTextPasswordProc); mIsPassword = PR_TRUE; } else if (mIsReadOnly) { SetControlType(kControlStaticTextProc); mIsReadOnly = PR_TRUE; } } Inherited::Create(aParent, aRect, aHandleEventFunction, aContext, aAppShell, aToolkit, aInitData); return NS_OK; } //------------------------------------------------------------------------- // Destroy // //------------------------------------------------------------------------- // The repeater in this widget needs to use out of band notification // to sever its ties with the nsTimer. If we just rely on the // dtor to do it, it will never get called because the nsTimer holds a ref to // this object. // NS_IMETHODIMP nsTextWidget::Destroy() { Inherited::Destroy(); if (mRepeating) RemoveFromRepeatList(); if (mIdling) RemoveFromIdleList(); return NS_OK; } //------------------------------------------------------------------------- // // //------------------------------------------------------------------------- NS_INTERFACE_MAP_BEGIN(nsTextWidget) NS_INTERFACE_MAP_ENTRY(nsITextWidget) NS_INTERFACE_MAP_END_INHERITING(Inherited) #pragma mark - //------------------------------------------------------------------------- // // //------------------------------------------------------------------------- PRBool nsTextWidget::DispatchMouseEvent(nsMouseEvent &aEvent) { PRBool eventHandled = nsWindow::DispatchMouseEvent(aEvent); // we don't want the 'Inherited' nsMacControl behavior EventRecord* theOSEvent; if ((! eventHandled) && (mControl != nsnull)) { switch (aEvent.message) { case NS_MOUSE_LEFT_DOUBLECLICK: case NS_MOUSE_LEFT_BUTTON_DOWN: Point thePoint; thePoint.h = aEvent.point.x; thePoint.v = aEvent.point.y; short theModifiers; theOSEvent = (EventRecord*)aEvent.nativeMsg; if (theOSEvent) { theModifiers = theOSEvent->modifiers; } else { if (aEvent.isShift) theModifiers = shiftKey; if (aEvent.isControl) theModifiers |= controlKey; if (aEvent.isAlt) theModifiers |= optionKey; } StartDraw(); ::HandleControlClick(mControl, thePoint, theModifiers, nil); EndDraw(); eventHandled = PR_TRUE; break; case NS_MOUSE_ENTER: SetCursor(eCursor_select); break; case NS_MOUSE_EXIT: SetCursor(eCursor_standard); break; } } return (eventHandled); } //------------------------------------------------------------------------- // DispatchWindowEvent // // Handle the following events: keys, focus, edit menu commands. // The strategy for key events is to use the native OS event // when it exists, otherwise use the Raptor nsKeyEvent. //------------------------------------------------------------------------- PRBool nsTextWidget::DispatchWindowEvent(nsGUIEvent &aEvent) { // filter cursor keys PRBool passKeyEvent = PR_TRUE; switch (aEvent.message) { case NS_KEY_DOWN: case NS_KEY_UP: { // hack: if Enter is pressed, pass Return nsKeyEvent* keyEvent = (nsKeyEvent*)&aEvent; #if 0 // this hack is no longer needed, since Enter is being mapped to // VK_RETURN in the event handler if (keyEvent->keyCode == kEnterCharCode) keyEvent->keyCode = NS_VK_RETURN; #endif // is this hack really needed? EventRecord* theOSEvent = (EventRecord*)aEvent.nativeMsg; if (theOSEvent && ((theOSEvent->message & charCodeMask) == kEnterCharCode)) theOSEvent->message = (theOSEvent->message & ~charCodeMask) + kReturnCharCode; switch (keyEvent->keyCode) { // case NS_VK_PAGE_UP: // case NS_VK_PAGE_DOWN: case NS_VK_END: case NS_VK_HOME: case NS_VK_LEFT: case NS_VK_UP: case NS_VK_RIGHT: case NS_VK_DOWN: passKeyEvent = PR_FALSE; break; } break; } } // dispatch the message PRBool eventHandled = PR_FALSE; if (passKeyEvent) eventHandled = Inherited::DispatchWindowEvent(aEvent); // handle the message here if nobody else processed it already if ((! eventHandled) && (mControl != nsnull)) { switch (aEvent.message) { case NS_MENU_SELECTED: { nsMenuEvent* menuEvent = (nsMenuEvent*)&aEvent; long menuID = HiWord(menuEvent->mCommand); long menuItem = LoWord(menuEvent->mCommand); switch (menuID) { case menu_Edit: { switch (menuItem) { // case cmd_Undo: case cmd_Cut: case cmd_Copy: { PRUint32 startSel = 0, endSel = 0; GetSelection ( &startSel, &endSel ); if ( startSel != endSel ) { const Uint32 selectionLen = endSel - startSel; // extract out the selection into a different nsString so // we can keep it unicode as long as possible PRUint32 unused = 0; nsString str, selection; GetText(str, 0, unused ); str.Mid(selection, startSel, selectionLen); // now |selection| holds the current selection in unicode. // We need to convert it to a c-string for MacOS. auto_ptr cRepOfSelection(new char[selectionLen + 1]); selection.ToCString(cRepOfSelection.get(), selectionLen + 1); // copy it to the scrapMgr #if TARGET_CARBON ::ClearCurrentScrap(); ScrapRef scrap; ::GetCurrentScrap(&scrap); ::PutScrapFlavor(scrap, 'TEXT', 0L /* ??? */, selectionLen, cRepOfSelection.get()); #else ::ZeroScrap(); ::PutScrap ( selectionLen, 'TEXT', cRepOfSelection.get() ); #endif // if we're cutting, remove the text from the widget if ( menuItem == cmd_Cut ) { unused = 0; str.Cut ( startSel, selectionLen ); SetText ( str, unused ); SetSelection(startSel, startSel); } } // if there is a selection eventHandled = PR_TRUE; break; } case cmd_Paste: { long scrapOffset; Handle scrapH = ::NewHandle(0); #if TARGET_CARBON ScrapRef scrap; OSStatus err; err = ::GetCurrentScrap(&scrap); if (err != noErr) return NS_ERROR_FAILURE; err = ::GetScrapFlavorSize(scrap, 'TEXT', &scrapOffset); // XXX uhh.. i don't think this is right.. long scrapLen = scrapOffset; if ( scrapOffset > 0 ) #else long scrapLen = ::GetScrap(scrapH, 'TEXT', &scrapOffset); if (scrapLen > 0) #endif { ::HLock(scrapH); // truncate to the first line char* cr = strchr((char*)*scrapH, '\r'); if (cr != nil) scrapLen = cr - *scrapH; // paste text nsString str; str.AssignWithConversion((char*)*scrapH, scrapLen); PRUint32 startSel, endSel; GetSelection(&startSel, &endSel); PRUint32 outSize; InsertText(str, startSel, endSel, outSize); startSel += str.Length(); SetSelection(startSel, startSel); ::HUnlock(scrapH); } ::DisposeHandle(scrapH); eventHandled = PR_TRUE; break; } case cmd_Clear: { nsString str; PRUint32 outSize; SetText(str, outSize); eventHandled = PR_TRUE; break; } case cmd_SelectAll: { SelectAll(); eventHandled = PR_TRUE; break; } } break; } } break; } // case NS_MENU_SELECTED case NS_GOTFOCUS: { StartDraw(); ActivateControl(mControl); OSErr err = SetKeyboardFocus(mWindowPtr, mControl, kControlFocusNextPart); nsRect rect = mBounds; rect.x = rect.y = 0; Rect macRect; nsRectToMacRect(rect, macRect); ::InsetRect(&macRect, 2, 2); ::DrawThemeFocusRect(&macRect, true); EndDraw(); StartIdling(); break; } case NS_LOSTFOCUS: { StopIdling(); StartDraw(); OSErr err = SetKeyboardFocus(mWindowPtr, mControl, kControlFocusNoPart); nsRect rect = mBounds; rect.x = rect.y = 0; Rect macRect; nsRectToMacRect(rect, macRect); ::InsetRect(&macRect, 2, 2); ::DrawThemeFocusRect(&macRect, false); DeactivateControl(mControl); EndDraw(); break; } case NS_KEY_DOWN: { char theChar; unsigned short theKey; unsigned short theModifiers; EventRecord* theOSEvent = (EventRecord*)aEvent.nativeMsg; nsKeyEvent* keyEvent = (nsKeyEvent*)&aEvent; if (theOSEvent) { theChar = (theOSEvent->message & charCodeMask); theKey = (theOSEvent->message & keyCodeMask) >> 8; theModifiers = theOSEvent->modifiers; } else { theChar = keyEvent->keyCode; theKey = 0; // what else? if (keyEvent->isShift) theModifiers = shiftKey; if (keyEvent->isControl) theModifiers |= controlKey; if (keyEvent->isAlt) theModifiers |= optionKey; } if (theChar != NS_VK_RETURN) // don't pass Return: nsTextWidget is a single line editor { StartDraw(); ::HandleControlKey(mControl, theKey, theChar, theModifiers); EndDraw(); eventHandled = PR_TRUE; } break; } } } return (eventHandled); } //------------------------------------------------------------------------- // // //------------------------------------------------------------------------- PRBool nsTextWidget::OnPaint(nsPaintEvent &aEvent) { Inherited::OnPaint(aEvent); /* draw a box.*/ if (mVisible && mIsReadOnly) { nsRect ctlRect = mBounds; ctlRect.x = ctlRect.y = 0; //ctlRect.Deflate(1, 1); mTempRenderingContext->SetColor(NS_RGB(0, 0, 0)); mTempRenderingContext->DrawRect(ctlRect); } return PR_FALSE; } #pragma mark - //------------------------------------------------------------------------- // // //------------------------------------------------------------------------- NS_METHOD nsTextWidget::SetPassword(PRBool aIsPassword) { nsresult theResult = NS_OK; // note: it is assumed that we can't have a read-only password field if (aIsPassword != mIsPassword) { mIsPassword = aIsPassword; short newControlType = (aIsPassword ? kControlEditTextPasswordProc : kControlEditTextProc); SetControlType(newControlType); theResult = CreateOrReplaceMacControl(newControlType); } return theResult; } //------------------------------------------------------------------------- // // //------------------------------------------------------------------------- NS_METHOD nsTextWidget::SetReadOnly(PRBool aReadOnlyFlag, PRBool& aOldFlag) { nsresult theResult = NS_OK; // note: it is assumed that we can't have a read-only password field aOldFlag = mIsReadOnly; if (aReadOnlyFlag != mIsReadOnly) { mIsReadOnly = aReadOnlyFlag; short newControlType = (aReadOnlyFlag ? kControlStaticTextProc : kControlEditTextProc); SetControlType(newControlType); theResult = CreateOrReplaceMacControl(newControlType); } return theResult; } #pragma mark - //------------------------------------------------------------------------- // // //------------------------------------------------------------------------- NS_METHOD nsTextWidget::SetMaxTextLength(PRUint32 aChars) { //¥TODO: install a kControlEditTextKeyFilterTag proc return NS_OK; } //------------------------------------------------------------------------- // // //------------------------------------------------------------------------- NS_METHOD nsTextWidget::GetText(nsString& aTextBuffer, PRUint32 /*aBufferSize*/, PRUint32& aSize) { if (!mControl) return NS_ERROR_NOT_INITIALIZED; ResType textTag = (mIsPassword ? kControlEditTextPasswordTag : kControlEditTextTextTag); Size textSize; ::GetControlDataSize(mControl, kControlNoPart, textTag, &textSize); char* str = new char[textSize]; if (str) { ::GetControlData(mControl, kControlNoPart, textTag, textSize, (Ptr)str, &textSize); aTextBuffer.SetLength(0); aTextBuffer.AppendWithConversion(str, textSize); aSize = textSize; delete [] str; } else { aSize = 0; return NS_ERROR_OUT_OF_MEMORY; } return NS_OK; } //------------------------------------------------------------------------- // // Assumes aSize is |out| only and does not yet have a value. //------------------------------------------------------------------------- NS_METHOD nsTextWidget::SetText(const nsString& aText, PRUint32& outSize) { outSize = aText.Length(); const unsigned int bufferSize = outSize + 1; // add 1 for null if (!mControl) return NS_ERROR_NOT_INITIALIZED; auto_ptr str ( new char[bufferSize] ); if ( str.get() ) { ResType textTag = (mIsPassword ? kControlEditTextPasswordTag : kControlEditTextTextTag); aText.ToCString(str.get(), bufferSize); ::SetControlData(mControl, kControlNoPart, textTag, outSize, (Ptr)str.get()); Invalidate(PR_FALSE); } else return NS_ERROR_OUT_OF_MEMORY; return NS_OK; } //------------------------------------------------------------------------- // // //------------------------------------------------------------------------- NS_METHOD nsTextWidget::InsertText(const nsString &aText, PRUint32 aStartPos, PRUint32 aEndPos, PRUint32& aSize) { if (!mControl) return NS_ERROR_NOT_INITIALIZED; Size textSize; ::GetControlDataSize(mControl, kControlNoPart, kControlEditTextTextTag, &textSize); nsString textStr; PRUint32 outTextSize; nsresult retVal = GetText(textStr, textSize, outTextSize); if (retVal != NS_OK) return retVal; if ((aStartPos == -1L) && (aEndPos == -1L)) { textStr.Append(aText); } else { if ((PRInt32)aStartPos < 0) aStartPos = 0; if ((PRInt32)aEndPos < 0) aStartPos = 0; if (aEndPos < aStartPos) aEndPos = aStartPos; textStr.Cut(aStartPos, aEndPos - aStartPos); textStr.Insert(aText, aStartPos); } aSize = textStr.Length(); return (SetText(textStr, aSize)); } //------------------------------------------------------------------------- // // //------------------------------------------------------------------------- NS_METHOD nsTextWidget::RemoveText() { if (!mControl) return NS_ERROR_NOT_INITIALIZED; StartDraw(); ::SetControlData(mControl, kControlNoPart, kControlEditTextTextTag, 0, (Ptr)""); EndDraw(); return NS_OK; } #pragma mark - //------------------------------------------------------------------------- // // //------------------------------------------------------------------------- NS_METHOD nsTextWidget::SelectAll() { if (!mControl) return NS_ERROR_NOT_INITIALIZED; Size textSize; ::GetControlDataSize(mControl, kControlNoPart, kControlEditTextTextTag, &textSize); return (SetSelection(0, textSize)); } //------------------------------------------------------------------------- // // //------------------------------------------------------------------------- NS_METHOD nsTextWidget::SetSelection(PRUint32 aStartSel, PRUint32 aEndSel) { if (!mControl) return NS_ERROR_NOT_INITIALIZED; ControlEditTextSelectionRec textSelectionRec; textSelectionRec.selStart = aStartSel; textSelectionRec.selEnd = aEndSel; StartDraw(); ::SetControlData(mControl, kControlNoPart, kControlEditTextSelectionTag, sizeof(textSelectionRec), (Ptr)&textSelectionRec); EndDraw(); return NS_OK; } //------------------------------------------------------------------------- // // //------------------------------------------------------------------------- NS_METHOD nsTextWidget::GetSelection(PRUint32 *aStartSel, PRUint32 *aEndSel) { if (!mControl) return NS_ERROR_NOT_INITIALIZED; ControlEditTextSelectionRec textSelectionRec; Size dataSize = sizeof(textSelectionRec); ::GetControlData(mControl, kControlNoPart, kControlEditTextSelectionTag, dataSize, (Ptr)&textSelectionRec, &dataSize); *aStartSel = textSelectionRec.selStart; *aEndSel = textSelectionRec.selEnd; return NS_OK; } //------------------------------------------------------------------------- // // //------------------------------------------------------------------------- NS_METHOD nsTextWidget::SetCaretPosition(PRUint32 aPosition) { return(SetSelection(aPosition, aPosition)); } //------------------------------------------------------------------------- // // //------------------------------------------------------------------------- NS_METHOD nsTextWidget::GetCaretPosition(PRUint32& aPos) { PRUint32 startSel, endSel; nsresult retVal = GetSelection(&startSel, &endSel); aPos = startSel; return retVal; } #pragma mark - //------------------------------------------------------------------------- // // //------------------------------------------------------------------------- void nsTextWidget::RepeatAction(const EventRecord& inMacEvent) { if (mControl) { StartDraw(); // This should really live in the window but // we have to set the graphic environment IdleControls(mWindowPtr); EndDraw(); } } //------------------------------------------------------------------------- // // //------------------------------------------------------------------------- void nsTextWidget::GetRectForMacControl(nsRect &outRect) { outRect = mBounds; outRect.x = outRect.y = 0; // inset to make space for border if (mIsReadOnly) outRect.Deflate(1, 1); else outRect.Deflate(4, 4); }