Mozilla/mozilla/widget/src/mac/nsTextWidget.cpp
beard%netscape.com 225060e4fa bug #42100, Carbon compatibility. r=gordon, saari, pinkerton, sfraser
git-svn-id: svn://10.0.0.236/trunk@72686 18797224-902f-48f8-a5cc-f745e15eee43
2000-06-20 23:10:06 +00:00

744 lines
20 KiB
C++

/* -*- 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 <ToolUtils.h>
#include <Appearance.h>
#include <memory>
using std::auto_ptr;
#if TARGET_CARBON || (UNIVERSAL_INTERFACES_VERSION >= 0x0330)
#include <ControlDefinitions.h>
#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 <Scrap.h>
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<char> 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<char> 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);
}