diff --git a/mozilla/layout/generic/nsBRFrame.cpp b/mozilla/layout/generic/nsBRFrame.cpp new file mode 100644 index 00000000000..3b595cd87d1 --- /dev/null +++ b/mozilla/layout/generic/nsBRFrame.cpp @@ -0,0 +1,141 @@ +/* -*- 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.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. + */ +#include "nsFrame.h" +#include "nsHTMLParts.h" +#include "nsHTMLIIDs.h" +#include "nsIPresContext.h" +#include "nsIInlineReflow.h" +#include "nsCSSLineLayout.h" +#include "nsStyleConsts.h" +#include "nsHTMLAtoms.h" +#include "nsIStyleContext.h" +#include "nsIFontMetrics.h" +#include "nsIRenderingContext.h" + +class BRFrame : public nsFrame, public nsIInlineReflow { +public: + BRFrame(nsIContent* aContent, nsIFrame* aParentFrame); + + // nsISupports + NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr); + + // nsIFrame + NS_IMETHOD Paint(nsIPresContext& aPresContext, + nsIRenderingContext& aRenderingContext, + const nsRect& aDirtyRect); + + // nsIInlineReflow + NS_IMETHOD FindTextRuns(nsCSSLineLayout& aLineLayout, + nsIReflowCommand* aReflowCommand); + NS_IMETHOD InlineReflow(nsCSSLineLayout& aLineLayout, + nsReflowMetrics& aDesiredSize, + const nsReflowState& aReflowState); + +protected: + virtual ~BRFrame(); +}; + +nsresult +NS_NewBRFrame(nsIContent* aContent, nsIFrame* aParentFrame, + nsIFrame*& aResult) +{ + nsIFrame* frame = new BRFrame(aContent, aParentFrame); + if (nsnull == frame) { + return NS_ERROR_OUT_OF_MEMORY; + } + aResult = frame; + return NS_OK; +} + +BRFrame::BRFrame(nsIContent* aContent, + nsIFrame* aParentFrame) + : nsFrame(aContent, aParentFrame) +{ +} + +BRFrame::~BRFrame() +{ +} + +NS_IMETHODIMP +BRFrame::QueryInterface(REFNSIID aIID, void** aInstancePtrResult) +{ + NS_PRECONDITION(nsnull != aInstancePtrResult, "null pointer"); + if (nsnull == aInstancePtrResult) { + return NS_ERROR_NULL_POINTER; + } + if (aIID.Equals(kIInlineReflowIID)) { + *aInstancePtrResult = (void*) ((nsIInlineReflow*)this); + return NS_OK; + } + return nsFrame::QueryInterface(aIID, aInstancePtrResult); +} + +NS_METHOD +BRFrame::Paint(nsIPresContext& aPresContext, + nsIRenderingContext& aRenderingContext, + const nsRect& aDirtyRect) +{ + if (nsIFrame::GetShowFrameBorders()) { + float p2t = aPresContext.GetPixelsToTwips(); + aRenderingContext.SetColor(NS_RGB(0, 255, 255)); + aRenderingContext.FillRect(0, 0, NSIntPixelsToTwips(5, p2t), mRect.height); + } + return NS_OK; +} + +NS_IMETHODIMP +BRFrame::FindTextRuns(nsCSSLineLayout& aLineLayout, + nsIReflowCommand* aReflowCommand) +{ + aLineLayout.EndTextRun(); + return NS_OK; +} + +NS_IMETHODIMP +BRFrame::InlineReflow(nsCSSLineLayout& aLineLayout, + nsReflowMetrics& aMetrics, + const nsReflowState& aReflowState) +{ + if (nsnull != aMetrics.maxElementSize) { + aMetrics.maxElementSize->width = 0; + aMetrics.maxElementSize->height = 0; + } + + // We have no width, but we're the height of the default font + const nsStyleFont* font = (const nsStyleFont*) + mStyleContext->GetStyleData(eStyleStruct_Font); + nsIFontMetrics* fm = aLineLayout.mPresContext->GetMetricsFor(font->mFont); + + fm->GetMaxAscent(aMetrics.ascent); + fm->GetMaxDescent(aMetrics.descent); + aMetrics.height = aMetrics.ascent + aMetrics.descent; + aMetrics.width = 0; + NS_RELEASE(fm); + + // Return our inline reflow status + const nsStyleDisplay* display = (const nsStyleDisplay*) + mStyleContext->GetStyleData(eStyleStruct_Display); + PRUint32 breakType = display->mBreakType; + if (NS_STYLE_CLEAR_NONE == breakType) { + breakType = NS_STYLE_CLEAR_LINE; + } + + return NS_INLINE_BREAK | NS_INLINE_BREAK_AFTER | + NS_INLINE_MAKE_BREAK_TYPE(breakType); +} diff --git a/mozilla/layout/generic/nsHTMLFrame.cpp b/mozilla/layout/generic/nsHTMLFrame.cpp new file mode 100644 index 00000000000..f460ff0c67f --- /dev/null +++ b/mozilla/layout/generic/nsHTMLFrame.cpp @@ -0,0 +1,626 @@ +/* -*- 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.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. + */ +#include "nsHTMLParts.h" +#include "nsHTMLContainer.h" +#include "nsContainerFrame.h" +#include "nsIDocument.h" +#include "nsIReflowCommand.h" +#include "nsIPresContext.h" +#include "nsIStyleContext.h" +#include "nsViewsCID.h" +#include "nsIView.h" +#include "nsIViewManager.h" +#include "nsIContentDelegate.h" +#include "nsIWidget.h" +#include "nsHTMLIIDs.h" +#include "nsPageFrame.h" +#include "nsIRenderingContext.h" +#include "nsGUIEvent.h" +#include "nsDOMEvent.h" +#include "nsStyleConsts.h" +#include "nsIViewManager.h" +#include "nsHTMLAtoms.h" +#include "nsIEventStateManager.h" +#include "nsIDeviceContext.h" + +class RootFrame : public nsContainerFrame { +public: + RootFrame(nsIContent* aContent); + + NS_IMETHOD Reflow(nsIPresContext& aPresContext, + nsReflowMetrics& aDesiredSize, + const nsReflowState& aReflowState, + nsReflowStatus& aStatus); + + NS_IMETHOD HandleEvent(nsIPresContext& aPresContext, + nsGUIEvent* aEvent, + nsEventStatus& aEventStatus); +}; + +// Pseudo frame created by the root frame +class RootContentFrame : public nsContainerFrame { +public: + RootContentFrame(nsIContent* aContent, nsIFrame* aParent); + + NS_IMETHOD Reflow(nsIPresContext& aPresContext, + nsReflowMetrics& aDesiredSize, + const nsReflowState& aReflowState, + nsReflowStatus& aStatus); + + NS_IMETHOD Paint(nsIPresContext& aPresContext, + nsIRenderingContext& aRenderingContext, + const nsRect& aDirtyRect); + + NS_IMETHOD HandleEvent(nsIPresContext& aPresContext, + nsGUIEvent* aEvent, + nsEventStatus& aEventStatus); + +protected: + void CreateFirstChild(nsIPresContext* aPresContext); +}; + +//---------------------------------------------------------------------- + +nsresult +NS_NewHTMLFrame(nsIContent* aContent, nsIFrame* aParentFrame, + nsIFrame*& aResult) +{ + RootFrame* frame = new RootFrame(aContent); + if (nsnull == frame) { + return NS_ERROR_OUT_OF_MEMORY; + } + aResult = frame; + return NS_OK; +} + +RootFrame::RootFrame(nsIContent* aContent) + : nsContainerFrame(aContent, nsnull) +{ +} + +NS_IMETHODIMP +RootFrame::Reflow(nsIPresContext& aPresContext, + nsReflowMetrics& aDesiredSize, + const nsReflowState& aReflowState, + nsReflowStatus& aStatus) +{ + NS_FRAME_TRACE_REFLOW_IN("RootFrame::Reflow"); + +#ifdef NS_DEBUG + PreReflowCheck(); +#endif + + aStatus = NS_FRAME_COMPLETE; + + if (eReflowReason_Incremental == aReflowState.reason) { + // We don't expect the target of the reflow command to be the root frame +#ifdef NS_DEBUG + NS_ASSERTION(nsnull != aReflowState.reflowCommand, "no reflow command"); + + nsIFrame* target; + aReflowState.reflowCommand->GetTarget(target); + NS_ASSERTION(target != this, "root frame is reflow command target"); +#endif + + // Verify that the next frame in the reflow chain is our pseudo frame + nsIFrame* next; + aReflowState.reflowCommand->GetNext(next); + NS_ASSERTION(next == mFirstChild, "unexpected next reflow command frame"); + + } else { + // Do we have any children? + if (nsnull == mFirstChild) { + // No. Create a pseudo frame + NS_ASSERTION(eReflowReason_Initial == aReflowState.reason, "unexpected reflow reason"); + mFirstChild = new RootContentFrame(mContent, this); + mChildCount = 1; + nsIStyleContext* style = aPresContext.ResolvePseudoStyleContextFor(nsHTMLAtoms::rootContentPseudo, this); + mFirstChild->SetStyleContext(&aPresContext,style); + NS_RELEASE(style); + } + } + + // Reflow our pseudo frame. It will choose whetever height its child frame + // wants + if (nsnull != mFirstChild) { + nsReflowMetrics desiredSize(nsnull); + nsReflowState kidReflowState(mFirstChild, aReflowState, aReflowState.maxSize); + + mFirstChild->WillReflow(aPresContext); + aStatus = ReflowChild(mFirstChild, &aPresContext, desiredSize, kidReflowState); + + // Place and size the child + nsRect rect(0, 0, desiredSize.width, desiredSize.height); + mFirstChild->SetRect(rect); + mFirstChild->DidReflow(aPresContext, NS_FRAME_REFLOW_FINISHED); + + mLastContentOffset = ((RootContentFrame*)mFirstChild)->GetLastContentOffset(); + } + + // Return the max size as our desired size + aDesiredSize.width = aReflowState.maxSize.width; + aDesiredSize.height = aReflowState.maxSize.height; + aDesiredSize.ascent = aDesiredSize.height; + aDesiredSize.descent = 0; + +#ifdef NS_DEBUG + PostReflowCheck(aStatus); +#endif + NS_FRAME_TRACE_REFLOW_OUT("RootFrame::Reflow", aStatus); + return NS_OK; +} + +NS_IMETHODIMP +RootFrame::HandleEvent(nsIPresContext& aPresContext, + nsGUIEvent* aEvent, + nsEventStatus& aEventStatus) +{ + mContent->HandleDOMEvent(aPresContext, (nsEvent*)aEvent, nsnull, DOM_EVENT_INIT, aEventStatus); + + if (aEvent->message == NS_MOUSE_LEFT_BUTTON_UP || + aEvent->message == NS_MOUSE_MIDDLE_BUTTON_UP || + aEvent->message == NS_MOUSE_RIGHT_BUTTON_UP) { + nsFrame::HandleEvent(aPresContext, aEvent, aEventStatus); + } + +#if 0 + if (aEventStatus != nsEventStatus_eConsumeNoDefault) { + switch (aEvent->message) { + case NS_MOUSE_MOVE: + case NS_MOUSE_ENTER: + { + nsIFrame* target = this; + PRInt32 cursor; + + GetCursorAndContentAt(aPresContext, aEvent->point, &target, &mContent, cursor); + if (cursor == NS_STYLE_CURSOR_INHERIT) { + cursor = NS_STYLE_CURSOR_DEFAULT; + } + nsCursor c; + switch (cursor) { + default: + case NS_STYLE_CURSOR_DEFAULT: + c = eCursor_standard; + break; + case NS_STYLE_CURSOR_HAND: + c = eCursor_hyperlink; + break; + case NS_STYLE_CURSOR_IBEAM: + c = eCursor_select; + break; + } + nsIWidget* window; + target->GetWindow(window); + window->SetCursor(c); + NS_RELEASE(window); + } + break; + } + } +#endif + return NS_OK; +} + +//---------------------------------------------------------------------- + +RootContentFrame::RootContentFrame(nsIContent* aContent, nsIFrame* aParent) + : nsContainerFrame(aContent, aParent) +{ + // Create a view + nsIFrame* parent; + nsIView* view; + + GetParentWithView(parent); + NS_ASSERTION(parent, "GetParentWithView failed"); + nsIView* parView; + + parent->GetView(parView); + NS_ASSERTION(parView, "no parent with view"); + + // Create a view + static NS_DEFINE_IID(kViewCID, NS_VIEW_CID); + static NS_DEFINE_IID(kIViewIID, NS_IVIEW_IID); + + nsresult result = nsRepository::CreateInstance(kViewCID, + nsnull, + kIViewIID, + (void **)&view); + if (NS_OK == result) { + nsIView* rootView = parView; + nsIViewManager* viewManager; + rootView->GetViewManager(viewManager); + + // Initialize the view + NS_ASSERTION(nsnull != viewManager, "null view manager"); + + view->Init(viewManager, mRect, rootView); + viewManager->InsertChild(rootView, view, 0); + + NS_RELEASE(viewManager); + + // Remember our view + SetView(view); + } +} + +void +RootContentFrame::CreateFirstChild(nsIPresContext* aPresContext) +{ + // Are we paginated? + if (aPresContext->IsPaginated()) { + // Yes. Create the first page frame + mFirstChild = new nsPageFrame(mContent, this); + mChildCount = 1; + mLastContentOffset = mFirstContentOffset; + + } else { + // Create a frame for the body/frameset child + PRInt32 i, n; + mContent->ChildCount(n); + for (i = 0; i < n; i++) { + nsIContent* child; + mContent->ChildAt(i, child); + if (nsnull != child) { + nsIAtom* tag; + child->GetTag(tag); + if ((nsHTMLAtoms::body == tag) || (nsHTMLAtoms::frameset == tag)) { + // Create a frame + nsIContentDelegate* cd = child->GetDelegate(aPresContext); + if (nsnull != cd) { + nsIStyleContext* kidStyleContext = + aPresContext->ResolveStyleContextFor(child, this); + nsresult rv = cd->CreateFrame(aPresContext, child, this, + kidStyleContext, mFirstChild); + NS_RELEASE(kidStyleContext); + if (NS_OK == rv) { + mChildCount = 1; + mFirstContentOffset = i; + mLastContentOffset = i; + } + NS_RELEASE(cd); + } + } + NS_IF_RELEASE(tag); + NS_RELEASE(child); + } + } + } +} + +// XXX Hack +#define PAGE_SPACING_TWIPS 100 + +NS_IMETHODIMP +RootContentFrame::Reflow(nsIPresContext& aPresContext, + nsReflowMetrics& aDesiredSize, + const nsReflowState& aReflowState, + nsReflowStatus& aStatus) +{ + NS_FRAME_TRACE_REFLOW_IN("RootContentFrame::Reflow"); +#ifdef NS_DEBUG + PreReflowCheck(); +#endif + + aStatus = NS_FRAME_COMPLETE; + + // XXX Incremental reflow code doesn't handle page mode at all... + if (eReflowReason_Incremental == aReflowState.reason) { + // We don't expect the target of the reflow command to be the root + // content frame +#ifdef NS_DEBUG + nsIFrame* target; + aReflowState.reflowCommand->GetTarget(target); + NS_ASSERTION(target != this, "root content frame is reflow command target"); +#endif + + // Verify the next frame in the reflow chain is our child frame + nsIFrame* next; + aReflowState.reflowCommand->GetNext(next); + NS_ASSERTION(next == mFirstChild, "unexpected next reflow command frame"); + + nsSize maxSize(aReflowState.maxSize.width, NS_UNCONSTRAINEDSIZE); + nsReflowState kidReflowState(next, aReflowState, maxSize); + + // Dispatch the reflow command to our child frame. Allow it to be as high + // as it wants + mFirstChild->WillReflow(aPresContext); + aStatus = ReflowChild(mFirstChild, &aPresContext, aDesiredSize, kidReflowState); + + // Place and size the child. Make sure the child is at least as + // tall as our max size (the containing window) + if (aDesiredSize.height < aReflowState.maxSize.height) { + aDesiredSize.height = aReflowState.maxSize.height; + } + + nsRect rect(0, 0, aDesiredSize.width, aDesiredSize.height); + mFirstChild->SetRect(rect); + + } else { + nsReflowReason reflowReason = aReflowState.reason; + + // Do we have any children? + if (nsnull == mFirstChild) { + // No, create the first child frame + reflowReason = eReflowReason_Initial; + CreateFirstChild(&aPresContext); + } + + // Resize our frames + if (nsnull != mFirstChild) { + if (aPresContext.IsPaginated()) { + nscoord y = PAGE_SPACING_TWIPS; + nsReflowMetrics kidSize(aDesiredSize.maxElementSize); + + // Compute the size of each page and the x coordinate within + // ourselves that the pages will be placed at. + nsSize pageSize(aPresContext.GetPageWidth(), + aPresContext.GetPageHeight()); + nsIDeviceContext *dx = aPresContext.GetDeviceContext(); + float sbWidth, sbHeight; + dx->GetScrollBarDimensions(sbWidth, sbHeight); + PRInt32 extra = aReflowState.maxSize.width - PAGE_SPACING_TWIPS*2 - + pageSize.width - NSToCoordRound(sbWidth); + NS_RELEASE(dx); + + // Note: nscoord is an unsigned type so don't combine these + // two statements or the extra will be promoted to unsigned + // and the >0 won't work! + nscoord x = PAGE_SPACING_TWIPS; + if (extra > 0) { + x += extra / 2; + } + + // Tile the pages vertically + for (nsIFrame* kidFrame = mFirstChild; nsnull != kidFrame; ) { + // Reflow the page + nsReflowState kidReflowState(kidFrame, aReflowState, pageSize, + reflowReason); + nsReflowStatus status; + + // Place and size the page. If the page is narrower than our + // max width then center it horizontally + kidFrame->WillReflow(aPresContext); + kidFrame->MoveTo(x, y); + status = ReflowChild(kidFrame, &aPresContext, kidSize, + kidReflowState); + kidFrame->SetRect(nsRect(x, y, kidSize.width, kidSize.height)); + y += kidSize.height; + + // Leave a slight gap between the pages + y += PAGE_SPACING_TWIPS; + + // Is the page complete? + nsIFrame* kidNextInFlow; + + kidFrame->GetNextInFlow(kidNextInFlow); + if (NS_FRAME_IS_COMPLETE(status)) { + NS_ASSERTION(nsnull == kidNextInFlow, "bad child flow list"); + } else if (nsnull == kidNextInFlow) { + // The page isn't complete and it doesn't have a next-in-flow so + // create a continuing page + nsIStyleContext* kidSC; + kidFrame->GetStyleContext(&aPresContext, kidSC); + nsIFrame* continuingPage; + nsresult rv = kidFrame->CreateContinuingFrame(aPresContext, this, + kidSC, continuingPage); + NS_RELEASE(kidSC); + reflowReason = eReflowReason_Initial; + + // Add it to our child list +#ifdef NS_DEBUG + nsIFrame* kidNextSibling; + kidFrame->GetNextSibling(kidNextSibling); + NS_ASSERTION(nsnull == kidNextSibling, "unexpected sibling"); +#endif + kidFrame->SetNextSibling(continuingPage); + mChildCount++; + } + + // Get the next page + kidFrame->GetNextSibling(kidFrame); + } + + // Return our desired size + aDesiredSize.height = y; + if (aDesiredSize.height < aReflowState.maxSize.height) { + aDesiredSize.height = aReflowState.maxSize.height; + } + aDesiredSize.width = PAGE_SPACING_TWIPS*2 + pageSize.width; + if (aDesiredSize.width < aReflowState.maxSize.width) { + aDesiredSize.width = aReflowState.maxSize.width; + } + + } else { + // Allow the frame to be as wide as our max width, and as high + // as it wants to be. + nsSize maxSize(aReflowState.maxSize.width, NS_UNCONSTRAINEDSIZE); + nsReflowState kidReflowState(mFirstChild, aReflowState, maxSize, reflowReason); + + // Get the child's desired size. Our child's desired height is our + // desired size + mFirstChild->WillReflow(aPresContext); + aStatus = ReflowChild(mFirstChild, &aPresContext, aDesiredSize, kidReflowState); + NS_ASSERTION(NS_FRAME_IS_COMPLETE(aStatus), "bad status"); + + // Place and size the child. Make sure the child is at least as + // tall as our max size (the containing window) + if (aDesiredSize.height < aReflowState.maxSize.height) { + aDesiredSize.height = aReflowState.maxSize.height; + } + + // Place and size the child + nsRect rect(0, 0, aDesiredSize.width, aDesiredSize.height); + mFirstChild->SetRect(rect); + + // Do the necessary repainting + if (eReflowReason_Initial == reflowReason) { + // Repaint the visible area + Invalidate(nsRect(0, 0, aReflowState.maxSize.width, aReflowState.maxSize.height)); + } else if (eReflowReason_Resize == aReflowState.reason) { + // Repaint the entire frame + Invalidate(nsRect(0, 0, aReflowState.maxSize.width, aDesiredSize.height)); + } + } + } + else { + aDesiredSize.width = aReflowState.maxSize.width; + aDesiredSize.height = aReflowState.maxSize.height; + aDesiredSize.ascent = aDesiredSize.height; + aDesiredSize.descent = 0; + if (nsnull != aDesiredSize.maxElementSize) { + aDesiredSize.maxElementSize->width = 0; + aDesiredSize.maxElementSize->height = 0; + } + } + } + + // We are always a pseudo-frame; make sure our content offset is + // properly pushed upwards + nsContainerFrame* parent = (nsContainerFrame*) mGeometricParent; + parent->PropagateContentOffsets(this, mFirstContentOffset, + mLastContentOffset, mLastContentIsComplete); + +#ifdef NS_DEBUG + PostReflowCheck(aStatus); +#endif + NS_FRAME_TRACE_REFLOW_OUT("RootContentFrame::Reflow", aStatus); + return NS_OK; +} + +NS_IMETHODIMP +RootContentFrame::Paint(nsIPresContext& aPresContext, + nsIRenderingContext& aRenderingContext, + const nsRect& aDirtyRect) +{ + // If we're paginated then fill the dirty rect with white + if (aPresContext.IsPaginated()) { + // Cross hatching would be nicer... + aRenderingContext.SetColor(NS_RGB(255,255,255)); + aRenderingContext.FillRect(aDirtyRect); + } + + nsContainerFrame::Paint(aPresContext, aRenderingContext, aDirtyRect); + return NS_OK; +} + +NS_IMETHODIMP +RootContentFrame::HandleEvent(nsIPresContext& aPresContext, + nsGUIEvent* aEvent, + nsEventStatus& aEventStatus) +{ +#if 0 + mContent->HandleDOMEvent(aPresContext, (nsEvent*)aEvent, nsnull, DOM_EVENT_INIT, aEventStatus); +#else + nsContainerFrame::HandleEvent(aPresContext, aEvent, aEventStatus); +#endif + + if (aEventStatus != nsEventStatus_eConsumeNoDefault) { + switch (aEvent->message) { + case NS_MOUSE_MOVE: + case NS_MOUSE_ENTER: + { + nsIFrame* target = this; + nsIContent* mTargetContent = mContent; + PRInt32 cursor; + + GetCursorAndContentAt(aPresContext, aEvent->point, &target, &mTargetContent, cursor); + if (cursor == NS_STYLE_CURSOR_INHERIT) { + cursor = NS_STYLE_CURSOR_DEFAULT; + } + nsCursor c; + switch (cursor) { + default: + case NS_STYLE_CURSOR_DEFAULT: + c = eCursor_standard; + break; + case NS_STYLE_CURSOR_HAND: + c = eCursor_hyperlink; + break; + case NS_STYLE_CURSOR_IBEAM: + c = eCursor_select; + break; + } + nsIWidget* window; + target->GetWindow(window); + window->SetCursor(c); + NS_RELEASE(window); + + //If the content object under the cursor has changed, fire a mouseover/out + nsIEventStateManager *mStateManager; + nsIContent *mLastContent; + if (NS_OK == aPresContext.GetEventStateManager(&mStateManager)) { + mStateManager->GetLastMouseOverContent(&mLastContent); + if (mLastContent != mTargetContent) { + if (nsnull != mLastContent) { + //fire mouseout + nsEventStatus mStatus = nsEventStatus_eIgnore; + nsMouseEvent mEvent; + mEvent.eventStructType = NS_MOUSE_EVENT; + mEvent.message = NS_MOUSE_EXIT; + mLastContent->HandleDOMEvent(aPresContext, &mEvent, nsnull, DOM_EVENT_INIT, mStatus); + } + //fire mouseover + nsEventStatus mStatus = nsEventStatus_eIgnore; + nsMouseEvent mEvent; + mEvent.eventStructType = NS_MOUSE_EVENT; + mEvent.message = NS_MOUSE_ENTER; + mTargetContent->HandleDOMEvent(aPresContext, &mEvent, nsnull, DOM_EVENT_INIT, mStatus); + mStateManager->SetLastMouseOverContent(mTargetContent); + } + NS_RELEASE(mStateManager); + NS_IF_RELEASE(mLastContent); + } + } + break; + case NS_MOUSE_EXIT: + //Don't know if this is actually hooked up. + { + //Fire of mouseout to the last content object. + nsIEventStateManager *mStateManager; + nsIContent *mLastContent; + if (NS_OK == aPresContext.GetEventStateManager(&mStateManager)) { + mStateManager->GetLastMouseOverContent(&mLastContent); + if (nsnull != mLastContent) { + //fire mouseout + nsEventStatus mStatus = nsEventStatus_eIgnore; + nsMouseEvent mEvent; + mEvent.eventStructType = NS_MOUSE_EVENT; + mEvent.message = NS_MOUSE_EXIT; + mLastContent->HandleDOMEvent(aPresContext, &mEvent, nsnull, DOM_EVENT_INIT, mStatus); + mStateManager->SetLastMouseOverContent(nsnull); + + NS_RELEASE(mLastContent); + } + NS_RELEASE(mStateManager); + } + } + break; + case NS_MOUSE_LEFT_BUTTON_UP: + { + nsIEventStateManager *mStateManager; + if (NS_OK == aPresContext.GetEventStateManager(&mStateManager)) { + mStateManager->SetActiveLink(nsnull); + NS_RELEASE(mStateManager); + } + } + break; + } + } + + return NS_OK; +} diff --git a/mozilla/layout/generic/nsImageFrame.cpp b/mozilla/layout/generic/nsImageFrame.cpp new file mode 100644 index 00000000000..0c1a79435eb --- /dev/null +++ b/mozilla/layout/generic/nsImageFrame.cpp @@ -0,0 +1,928 @@ +/* -*- 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.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. + */ +#include "nsHTMLParts.h" +#include "nsHTMLImage.h" +#include "nsHTMLTagContent.h" +#include "nsString.h" +#include "nsLeafFrame.h" +#include "nsIPresContext.h" +#include "nsIRenderingContext.h" +#include "nsIFrameImageLoader.h" +#include "nsIPresShell.h" +#include "nsHTMLIIDs.h" +#include "nsIImage.h" +#include "nsIWidget.h" +#include "nsHTMLAtoms.h" +#include "nsIHTMLAttributes.h" +#include "nsIDocument.h" +#include "nsIHTMLDocument.h" +#include "nsIStyleContext.h" +#include "nsStyleConsts.h" +#include "nsIImageMap.h" +#include "nsILinkHandler.h" +#include "nsIURL.h" +#include "nsIView.h" +#include "nsIViewManager.h" +#include "nsCSSLayout.h" +#include "nsHTMLBase.h" +#include "prprf.h" +#include "nsISizeOfHandler.h" +#include "nsIFontMetrics.h" +#include "nsCSSRendering.h" +#include "nsIDOMHTMLImageElement.h" + +#define BROKEN_IMAGE_URL "resource:/res/html/broken-image.gif" + +#define XP_IS_SPACE(_ch) \ + (((_ch) == ' ') || ((_ch) == '\t') || ((_ch) == '\n')) + +// XXX image frame layout can be 100% decoupled from the content +// object; all it needs are attributes to work properly + +static NS_DEFINE_IID(kIHTMLDocumentIID, NS_IHTMLDOCUMENT_IID); + +#if 0 +#define nsHTMLImageSuper nsHTMLTagContent +class nsHTMLImage : public nsHTMLImageSuper, public nsIDOMHTMLImageElement { +public: + nsHTMLImage(nsIAtom* aTag); + + NS_DECL_ISUPPORTS + + NS_IMETHOD SizeOf(nsISizeOfHandler* aHandler) const; + NS_IMETHOD CreateFrame(nsIPresContext* aPresContext, + nsIFrame* aParentFrame, + nsIStyleContext* aStyleContext, + nsIFrame*& aResult); + + NS_IMETHOD SetAttribute(nsIAtom* aAttribute, const nsString& aValue, + PRBool aNotify); + NS_IMETHOD MapAttributesInto(nsIStyleContext* aContext, + nsIPresContext* aPresContext); + NS_IMETHOD AttributeToString(nsIAtom* aAttribute, + nsHTMLValue& aValue, + nsString& aResult) const; + + NS_FORWARD_IDOMNODE(nsHTMLImageSuper::) + NS_FORWARD_IDOMELEMENT(nsHTMLImageSuper::) + NS_FORWARD_IDOMHTMLELEMENT(nsHTMLImageSuper::) + + NS_DECL_IDOMHTMLIMAGEELEMENT + + NS_IMETHOD GetScriptObject(nsIScriptContext *aContext, void** aScriptObject); + +protected: + virtual ~nsHTMLImage(); + void SizeOfWithoutThis(nsISizeOfHandler* aHandler) const; + + void TriggerReflow(); +}; +#endif + +#define ImageFrameSuper nsLeafFrame +class ImageFrame : public ImageFrameSuper { +public: + ImageFrame(nsIContent* aContent, nsIFrame* aParentFrame); + + NS_IMETHOD DeleteFrame(nsIPresContext& aPresContext); + NS_IMETHOD SizeOf(nsISizeOfHandler* aHandler) const; + NS_IMETHOD Paint(nsIPresContext& aPresContext, + nsIRenderingContext& aRenderingContext, + const nsRect& aDirtyRect); + NS_METHOD HandleEvent(nsIPresContext& aPresContext, + nsGUIEvent* aEvent, + nsEventStatus& aEventStatus); + NS_IMETHOD GetCursorAndContentAt(nsIPresContext& aPresContext, + const nsPoint& aPoint, + nsIFrame** aFrame, + nsIContent** aContent, + PRInt32& aCursor); + NS_IMETHOD ContentChanged(nsIPresShell* aShell, + nsIPresContext* aPresContext, + nsIContent* aChild, + nsISupports* aSubContent); + +protected: + virtual ~ImageFrame(); + void SizeOfWithoutThis(nsISizeOfHandler* aHandler) const; + + virtual void GetDesiredSize(nsIPresContext* aPresContext, + const nsReflowState& aReflowState, + nsReflowMetrics& aDesiredSize); + + nsIImageMap* GetImageMap(); + + nsHTMLImageLoader mImageLoader; + nsIImageMap* mImageMap; + PRBool mSizeFrozen; + + void TriggerLink(nsIPresContext& aPresContext, + const nsString& aURLSpec, + const nsString& aTargetSpec, + PRBool aClick); + + PRBool IsServerImageMap(); + PRIntn GetSuppress(); + + nscoord MeasureString(nsIFontMetrics* aFontMetrics, + const PRUnichar* aString, + PRInt32 aLength, + nscoord aMaxWidth, + PRUint32& aMaxFit); + + void DisplayAltText(nsIPresContext& aPresContext, + nsIRenderingContext& aRenderingContext, + const nsString& aAltText, + const nsRect& aRect); +}; + +// Value's for mSuppress +#define SUPPRESS_UNSET 0 +#define DONT_SUPPRESS 1 +#define SUPPRESS 2 +#define DEFAULT_SUPPRESS 3 + +// Default alignment value (so we can tell an unset value from a set value) +#define ALIGN_UNSET PRUint8(-1) + +//---------------------------------------------------------------------- + +nsHTMLImageLoader::nsHTMLImageLoader() +{ + mImageLoader = nsnull; + mLoadImageFailed = PR_FALSE; + mLoadBrokenImageFailed = PR_FALSE; + mURLSpec = nsnull; + mBaseHREF = nsnull; +} + +nsHTMLImageLoader::~nsHTMLImageLoader() +{ + NS_IF_RELEASE(mImageLoader); + if (nsnull != mURLSpec) { + delete mURLSpec; + } + if (nsnull != mBaseHREF) { + delete mBaseHREF; + } +} + +void +nsHTMLImageLoader::SizeOf(nsISizeOfHandler* aHandler) const +{ + aHandler->Add(sizeof(*this)); + if (!aHandler->HaveSeen(mURLSpec)) { + mURLSpec->SizeOf(aHandler); + } + if (!aHandler->HaveSeen(mImageLoader)) { + mImageLoader->SizeOf(aHandler); + } +} + +nsIImage* +nsHTMLImageLoader::GetImage() +{ + nsIImage* image = nsnull; + if (nsnull != mImageLoader) { + mImageLoader->GetImage(image); + } + return image; +} + +nsresult +nsHTMLImageLoader::SetURL(const nsString& aURLSpec) +{ + if (nsnull != mURLSpec) { + delete mURLSpec; + } + mURLSpec = new nsString(aURLSpec); + if (nsnull == mURLSpec) { + return NS_ERROR_OUT_OF_MEMORY; + } + return NS_OK; +} + +nsresult +nsHTMLImageLoader::SetBaseHREF(const nsString& aBaseHREF) +{ + if (nsnull != mBaseHREF) { + delete mBaseHREF; + } + mBaseHREF = new nsString(aBaseHREF); + if (nsnull == mBaseHREF) { + return NS_ERROR_OUT_OF_MEMORY; + } + return NS_OK; +} + +nsresult +nsHTMLImageLoader::StartLoadImage(nsIPresContext* aPresContext, + nsIFrame* aForFrame, + PRBool aNeedSizeUpdate, + PRIntn& aLoadStatus) +{ + aLoadStatus = NS_IMAGE_LOAD_STATUS_NONE; + + // Get absolute url the first time through + nsresult rv; + nsAutoString src; + if (mLoadImageFailed || (nsnull == mURLSpec)) { + src.Append(BROKEN_IMAGE_URL); + } else { + nsAutoString baseURL; + if (nsnull != mBaseHREF) { + baseURL = *mBaseHREF; + } + + // Get documentURL + nsIPresShell* shell; + shell = aPresContext->GetShell(); + nsIDocument* doc = shell->GetDocument(); + nsIURL* docURL = doc->GetDocumentURL(); + + // Create an absolute URL + nsresult rv = NS_MakeAbsoluteURL(docURL, baseURL, *mURLSpec, src); + + // Release references + NS_RELEASE(shell); + NS_RELEASE(docURL); + NS_RELEASE(doc); + if (NS_OK != rv) { + return rv; + } + } + + if (nsnull == mImageLoader) { + // Start image loading. Note that we don't specify a background color + // so transparent images are always rendered using a transparency mask + rv = aPresContext->StartLoadImage(src, nsnull, aForFrame, aNeedSizeUpdate, + mImageLoader); + if (NS_OK != rv) { + return rv; + } + } + + // Examine current image load status + mImageLoader->GetImageLoadStatus(aLoadStatus); + if (0 != (aLoadStatus & NS_IMAGE_LOAD_STATUS_ERROR)) { + NS_RELEASE(mImageLoader); + if (mLoadImageFailed) { + // We are doomed. Loading the broken image has just failed. + mLoadBrokenImageFailed = PR_TRUE; + } + else { + // Try again, this time using the broke-image url + mLoadImageFailed = PR_TRUE; + return StartLoadImage(aPresContext, aForFrame, aNeedSizeUpdate, aLoadStatus); + } + } + return NS_OK; +} + +void +nsHTMLImageLoader::GetDesiredSize(nsIPresContext* aPresContext, + const nsReflowState& aReflowState, + nsReflowMetrics& aDesiredSize) +{ + nsSize styleSize; + PRIntn ss = nsCSSLayout::GetStyleSize(aPresContext, aReflowState, styleSize); + PRIntn loadStatus; + if (0 != ss) { + if (NS_SIZE_HAS_BOTH == ss) { + StartLoadImage(aPresContext, aReflowState.frame, PR_FALSE, loadStatus); + aDesiredSize.width = styleSize.width; + aDesiredSize.height = styleSize.height; + } + else { + // Preserve aspect ratio of image with unbound dimension. + StartLoadImage(aPresContext, aReflowState.frame, PR_TRUE, loadStatus); + if ((0 == (loadStatus & NS_IMAGE_LOAD_STATUS_SIZE_AVAILABLE)) || + (nsnull == mImageLoader)) { + // Provide a dummy size for now; later on when the image size + // shows up we will reflow to the new size. + aDesiredSize.width = 1; + aDesiredSize.height = 1; + } + else { + float p2t = aPresContext->GetPixelsToTwips(); + nsSize imageSize; + mImageLoader->GetSize(imageSize); + float imageWidth = imageSize.width * p2t; + float imageHeight = imageSize.height * p2t; + if (0.0f != imageHeight) { + if (0 != (ss & NS_SIZE_HAS_WIDTH)) { + // We have a width, and an auto height. Compute height + // from width. + aDesiredSize.width = styleSize.width; + aDesiredSize.height = + (nscoord)NSToIntRound(styleSize.width * imageHeight / imageWidth); + } + else { + // We have a height and an auto width. Compute width from + // height. + aDesiredSize.height = styleSize.height; + aDesiredSize.width = + (nscoord)NSToIntRound(styleSize.height * imageWidth / imageHeight); + } + } + else { + // Screwy image + aDesiredSize.width = 1; + aDesiredSize.height = 1; + } + } + } + } + else { + StartLoadImage(aPresContext, aReflowState.frame, PR_TRUE, loadStatus); + if ((0 == (loadStatus & NS_IMAGE_LOAD_STATUS_SIZE_AVAILABLE)) || + (nsnull == mImageLoader)) { + // Provide a dummy size for now; later on when the image size + // shows up we will reflow to the new size. + aDesiredSize.width = 1; + aDesiredSize.height = 1; + printf ("in image loader, dummy size of 1 returned\n"); + } else { + float p2t = aPresContext->GetPixelsToTwips(); + nsSize imageSize; + mImageLoader->GetSize(imageSize); + aDesiredSize.width = NSIntPixelsToTwips(imageSize.width, p2t); + aDesiredSize.height = NSIntPixelsToTwips(imageSize.height, p2t); + printf ("in image loader, real size of %d returned\n", aDesiredSize.width); + } + } +} + +//---------------------------------------------------------------------- + +nsresult +NS_NewImageFrame(nsIContent* aContent, + nsIFrame* aParentFrame, + nsIFrame*& aResult) +{ + ImageFrame* frame = new ImageFrame(aContent, aParentFrame); + if (nsnull == frame) { + return NS_ERROR_OUT_OF_MEMORY; + } + aResult = frame; + return NS_OK; +} + +ImageFrame::ImageFrame(nsIContent* aContent, nsIFrame* aParentFrame) + : nsLeafFrame(aContent, aParentFrame) +{ +} + +ImageFrame::~ImageFrame() +{ +} + +NS_METHOD +ImageFrame::DeleteFrame(nsIPresContext& aPresContext) +{ + NS_IF_RELEASE(mImageMap); + + // Release image loader first so that it's refcnt can go to zero + mImageLoader.DestroyLoader(); + + return nsLeafFrame::DeleteFrame(aPresContext); +} + +NS_IMETHODIMP +ImageFrame::SizeOf(nsISizeOfHandler* aHandler) const +{ + aHandler->Add(sizeof(*this)); + ImageFrame::SizeOfWithoutThis(aHandler); + return NS_OK; +} + +void +ImageFrame::SizeOfWithoutThis(nsISizeOfHandler* aHandler) const +{ + ImageFrameSuper::SizeOfWithoutThis(aHandler); + mImageLoader.SizeOf(aHandler); + if (!aHandler->HaveSeen(mImageMap)) { + mImageMap->SizeOf(aHandler); + } +} + +void +ImageFrame::GetDesiredSize(nsIPresContext* aPresContext, + const nsReflowState& aReflowState, + nsReflowMetrics& aDesiredSize) +{ + if (mSizeFrozen) { + aDesiredSize.width = mRect.width; + aDesiredSize.height = mRect.height; + } + else { + // XXX Don't create a view, because we want whatever is below the image + // to show through while the image is loading; Likewise for transparent + // images and broken images + // + // What we really want to do is to create a view, and indicate that the + // view has a transparent content area. Do this while it's loading, + // and then when it's fully loaded mark the view as opaque if the + // image is opaque. + // + // We can't use that approach yet, because currently the compositor doesn't + // support transparent views... +#if 0 + nsHTMLBase::CreateViewForFrame(aPresContext, this, mStyleContext, PR_TRUE); +#endif + + // Setup url before starting the image load + nsAutoString src, base; + if (NS_CONTENT_ATTR_HAS_VALUE == mContent->GetAttribute("SRC", src)) { + mImageLoader.SetURL(src); + if (NS_CONTENT_ATTR_HAS_VALUE == + mContent->GetAttribute(NS_HTML_BASE_HREF, base)) { + mImageLoader.SetBaseHREF(base); + } + } + mImageLoader.GetDesiredSize(aPresContext, aReflowState, aDesiredSize); + } +} + +// Computes the width of the specified string. aMaxWidth specifies the maximum +// width available. Once this limit is reached no more characters are measured. +// The number of characters that fit within the maximum width are returned in +// aMaxFit +nscoord +ImageFrame::MeasureString(nsIFontMetrics* aFontMetrics, + const PRUnichar* aString, + PRInt32 aLength, + nscoord aMaxWidth, + PRUint32& aMaxFit) +{ + nscoord totalWidth = 0; + nscoord spaceWidth; + aFontMetrics->GetWidth(' ', spaceWidth); + + aMaxFit = 0; + while (aLength > 0) { + // Find the next place we can line break + PRUint32 len = aLength; + PRBool trailingSpace = PR_FALSE; + for (PRInt32 i = 0; i < aLength; i++) { + if (XP_IS_SPACE(aString[i]) && (i > 0)) { + len = i; // don't include the space when measuring + trailingSpace = PR_TRUE; + break; + } + } + + // Measure this chunk of text, and see if it fits + nscoord width; + aFontMetrics->GetWidth(aString, len, width); + PRBool fits = (totalWidth + width) <= aMaxWidth; + + // If it fits on the line, or it's the first word we've processed then + // include it + if (fits || (0 == totalWidth)) { + // New piece fits + totalWidth += width; + + // If there's a trailing space then see if it fits as well + if (trailingSpace) { + if ((totalWidth + spaceWidth) <= aMaxWidth) { + totalWidth += spaceWidth; + } else { + // Space won't fit. Leave it at the end but don't include it in + // the width + fits = PR_FALSE; + } + + len++; + } + + aMaxFit += len; + aString += len; + aLength -= len; + } + + if (!fits) { + break; + } + } + + return totalWidth; +} + +// Formats the alt-text to fit within the specified rectangle. Breaks lines +// between words if a word would extend past the edge of the rectangle +void +ImageFrame::DisplayAltText(nsIPresContext& aPresContext, + nsIRenderingContext& aRenderingContext, + const nsString& aAltText, + const nsRect& aRect) +{ + // Clip so we don't render outside of the rect. + aRenderingContext.PushState(); + aRenderingContext.SetClipRect(aRect, nsClipCombine_kIntersect); + + const nsStyleColor* color = + (const nsStyleColor*)mStyleContext->GetStyleData(eStyleStruct_Color); + const nsStyleFont* font = + (const nsStyleFont*)mStyleContext->GetStyleData(eStyleStruct_Font); + + // Set font and color + aRenderingContext.SetColor(color->mColor); + aRenderingContext.SetFont(font->mFont); + + // Format the text to display within the formatting rect + nsIFontMetrics* fm = aRenderingContext.GetFontMetrics(); + + nscoord maxDescent, height; + fm->GetMaxDescent(maxDescent); + fm->GetHeight(height); + + // XXX It would be nice if there was a way to have the font metrics tell + // use where to break the text given a maximum width. At a minimum we need + // to be able to get the break character... + const PRUnichar* str = aAltText.GetUnicode(); + PRInt32 strLen = aAltText.Length(); + nscoord y = aRect.y; + while ((strLen > 0) && ((y + maxDescent) < aRect.YMost())) { + // Determine how much of the text to display on this line + PRUint32 maxFit; // number of characters that fit + nscoord width = MeasureString(fm, str, strLen, aRect.width, maxFit); + + // Display the text + aRenderingContext.DrawString(str, maxFit, aRect.x, y, 0); + + // Move to the next line + str += maxFit; + strLen -= maxFit; + y += height; + } + + NS_RELEASE(fm); + aRenderingContext.PopState(); +} + +struct nsRecessedBorder : public nsStyleSpacing { + nsRecessedBorder(nscoord aBorderWidth) + : nsStyleSpacing() + { + nsStyleCoord styleCoord(aBorderWidth); + + mBorder.SetLeft(styleCoord); + mBorder.SetTop(styleCoord); + mBorder.SetRight(styleCoord); + mBorder.SetBottom(styleCoord); + mBorderStyle[0] = NS_STYLE_BORDER_STYLE_INSET; + mBorderStyle[1] = NS_STYLE_BORDER_STYLE_INSET; + mBorderStyle[2] = NS_STYLE_BORDER_STYLE_INSET; + mBorderStyle[3] = NS_STYLE_BORDER_STYLE_INSET; + mBorderColor[0] = 0; + mBorderColor[1] = 0; + mBorderColor[2] = 0; + mBorderColor[3] = 0; + mHasCachedMargin = mHasCachedPadding = mHasCachedBorder = PR_FALSE; + } +}; + +NS_METHOD +ImageFrame::Paint(nsIPresContext& aPresContext, + nsIRenderingContext& aRenderingContext, + const nsRect& aDirtyRect) +{ + if ((0 == mRect.width) || (0 == mRect.height)) { + // Do not render when given a zero area. This avoids some useless + // scaling work while we wait for our image dimensions to arrive + // asynchronously. + return NS_OK; + } + + const nsStyleDisplay* disp = + (const nsStyleDisplay*)mStyleContext->GetStyleData(eStyleStruct_Display); + + if (disp->mVisible) { + // First paint background and borders + nsLeafFrame::Paint(aPresContext, aRenderingContext, aDirtyRect); + + nsIImage* image = mImageLoader.GetImage(); + if (nsnull == image) { + // No image yet. Draw the icon that indicates we're loading, and display + // the alt-text + nsAutoString altText; + if (NS_CONTENT_ATTR_HAS_VALUE == mContent->GetAttribute("ALT", altText)) { + // Display a recessed one-pixel border in the inner area + nsRect inner; + GetInnerArea(&aPresContext, inner); + + float p2t = aPresContext.GetPixelsToTwips(); + nsRecessedBorder recessedBorder(NSIntPixelsToTwips(1, p2t)); + nsCSSRendering::PaintBorder(aPresContext, aRenderingContext, this, inner, + inner, recessedBorder, 0); + inner.Deflate(NSIntPixelsToTwips(1, p2t), NSIntPixelsToTwips(1, p2t)); + + // Leave a 8 pixel left/right padding, and a 5 pixel top/bottom padding + inner.Deflate(NSIntPixelsToTwips(8, p2t), NSIntPixelsToTwips(5, p2t)); + + // If there's room, then display the alt-text + if (!inner.IsEmpty()) { + DisplayAltText(aPresContext, aRenderingContext, altText, inner); + } + } + return NS_OK; + } + + // Now render the image into our inner area (the area without the + // borders and padding) + nsRect inner; + GetInnerArea(&aPresContext, inner); + if (mImageLoader.GetLoadImageFailed()) { + float p2t = aPresContext.GetPixelsToTwips(); + inner.width = NSIntPixelsToTwips(image->GetWidth(), p2t); + inner.height = NSIntPixelsToTwips(image->GetHeight(), p2t); + } + aRenderingContext.DrawImage(image, inner); + + if (GetShowFrameBorders()) { + nsIImageMap* map = GetImageMap(); + if (nsnull != map) { + aRenderingContext.SetColor(NS_RGB(0, 0, 0)); + aRenderingContext.PushState(); + aRenderingContext.Translate(inner.x, inner.y); + map->Draw(aPresContext, aRenderingContext); + aRenderingContext.PopState(); + } + } + } + + return NS_OK; +} + +nsIImageMap* +ImageFrame::GetImageMap() +{ + if (nsnull == mImageMap) { + nsAutoString usemap; + mContent->GetAttribute("usemap", usemap); + if (0 == usemap.Length()) { + return nsnull; + } + + nsIDocument* doc = nsnull; + mContent->GetDocument(doc); + if (nsnull == doc) { + return nsnull; + } + + if (usemap.First() == '#') { + usemap.Cut(0, 1); + } + nsIHTMLDocument* hdoc; + nsresult rv = doc->QueryInterface(kIHTMLDocumentIID, (void**)&hdoc); + NS_RELEASE(doc); + if (NS_OK == rv) { + nsIImageMap* map; + rv = hdoc->GetImageMap(usemap, &map); + NS_RELEASE(hdoc); + if (NS_OK == rv) { + mImageMap = map; + } + } + } + NS_IF_ADDREF(mImageMap); + return mImageMap; +} + +void +ImageFrame::TriggerLink(nsIPresContext& aPresContext, + const nsString& aURLSpec, + const nsString& aTargetSpec, + PRBool aClick) +{ + nsILinkHandler* handler = nsnull; + aPresContext.GetLinkHandler(&handler); + if (nsnull != handler) { + if (aClick) { + handler->OnLinkClick(this, aURLSpec, aTargetSpec); + } + else { + handler->OnOverLink(this, aURLSpec, aTargetSpec); + } + } +} + +PRBool +ImageFrame::IsServerImageMap() +{ + nsAutoString ismap; + return NS_CONTENT_ATTR_HAS_VALUE == mContent->GetAttribute("ismap", ismap); +} + +PRIntn +ImageFrame::GetSuppress() +{ + nsAutoString s; + if (NS_CONTENT_ATTR_HAS_VALUE == mContent->GetAttribute("suppress", s)) { + if (s.EqualsIgnoreCase("true")) { + return SUPPRESS; + } else if (s.EqualsIgnoreCase("false")) { + return DONT_SUPPRESS; + } + } + return DEFAULT_SUPPRESS; +} + +// XXX what should clicks on transparent pixels do? +NS_METHOD +ImageFrame::HandleEvent(nsIPresContext& aPresContext, + nsGUIEvent* aEvent, + nsEventStatus& aEventStatus) +{ + nsIImageMap* map; + aEventStatus = nsEventStatus_eIgnore; + + switch (aEvent->message) { + case NS_MOUSE_LEFT_BUTTON_UP: + case NS_MOUSE_MOVE: + map = GetImageMap(); + if ((nsnull != map) || IsServerImageMap()) { + nsIURL* docURL = nsnull; + nsIDocument* doc = nsnull; + mContent->GetDocument(doc); + if (nsnull != doc) { + docURL = doc->GetDocumentURL(); + NS_RELEASE(doc); + } + + // Ask map if the x,y coordinates are in a clickable area + float t2p = aPresContext.GetTwipsToPixels(); + nsAutoString absURL, target, altText; + PRBool suppress; + if (nsnull != map) { + // Subtract out border and padding here so that we are looking + // at the right coordinates. Hit detection against area tags + // is done after the mouse wanders over the image, not over + // the image's borders. + nsRect inner; + GetInnerArea(&aPresContext, inner); + PRInt32 x = NSTwipsToIntPixels((aEvent->point.x - inner.x), t2p); + PRInt32 y = NSTwipsToIntPixels((aEvent->point.y - inner.y), t2p); + nsresult r = map->IsInside(x, y, docURL, absURL, target, altText, + &suppress); + NS_IF_RELEASE(docURL); + NS_RELEASE(map); + if (NS_OK == r) { + // We hit a clickable area. Time to go somewhere... + PRBool clicked = PR_FALSE; + if (aEvent->message == NS_MOUSE_LEFT_BUTTON_UP) { + aEventStatus = nsEventStatus_eConsumeNoDefault; + clicked = PR_TRUE; + } + TriggerLink(aPresContext, absURL, target, clicked); + } + } + else { + suppress = GetSuppress(); + nsAutoString baseURL; + mContent->GetAttribute(NS_HTML_BASE_HREF, baseURL); + nsAutoString src; + mContent->GetAttribute("src", src); + NS_MakeAbsoluteURL(docURL, baseURL, src, absURL); + + // Note: We don't subtract out the border/padding here to remain + // compatible with navigator. [ick] + PRInt32 x = NSTwipsToIntPixels(aEvent->point.x, t2p); + PRInt32 y = NSTwipsToIntPixels(aEvent->point.y, t2p); + char cbuf[50]; + PR_snprintf(cbuf, sizeof(cbuf), "?%d,%d", x, y); + absURL.Append(cbuf); + PRBool clicked = PR_FALSE; + if (aEvent->message == NS_MOUSE_LEFT_BUTTON_UP) { + aEventStatus = nsEventStatus_eConsumeNoDefault; + clicked = PR_TRUE; + } + TriggerLink(aPresContext, absURL, target, clicked); + } + break; + } + // FALL THROUGH + + default: + // Let default event handler deal with it + return nsLeafFrame::HandleEvent(aPresContext, aEvent, aEventStatus); + } + return NS_OK; +} + +NS_METHOD +ImageFrame::GetCursorAndContentAt(nsIPresContext& aPresContext, + const nsPoint& aPoint, + nsIFrame** aFrame, + nsIContent** aContent, + PRInt32& aCursor) +{ + // The default cursor is to have no cursor + aCursor = NS_STYLE_CURSOR_INHERIT; + *aContent = mContent; + + const nsStyleColor* styleColor = (const nsStyleColor*) + mStyleContext->GetStyleData(eStyleStruct_Color); + if (styleColor->mCursor != NS_STYLE_CURSOR_INHERIT) { + // If we have a particular cursor, use it + *aFrame = this; + aCursor = (PRInt32) styleColor->mCursor; + } + + nsIImageMap* map = GetImageMap(); + if (nsnull != map) { + nsRect inner; + GetInnerArea(&aPresContext, inner); + aCursor = NS_STYLE_CURSOR_DEFAULT; + float t2p = aPresContext.GetTwipsToPixels(); + PRInt32 x = NSTwipsToIntPixels((aPoint.x - inner.x), t2p); + PRInt32 y = NSTwipsToIntPixels((aPoint.y - inner.y), t2p); + if (NS_OK == map->IsInside(x, y)) { + aCursor = NS_STYLE_CURSOR_HAND; + } + NS_RELEASE(map); + } + + return NS_OK; +} + +NS_IMETHODIMP +ImageFrame::ContentChanged(nsIPresShell* aShell, + nsIPresContext* aPresContext, + nsIContent* aChild, + nsISupports* aSubContent) +{ + // See if the src attribute changed; if it did then trigger a redraw + // by firing up a new image load request. Otherwise let our base + // class handle the content-changed request. + nsAutoString oldSRC; + mImageLoader.GetURL(oldSRC); + + // Get src attribute's value and construct a new absolute url from it + nsAutoString newSRC; + if (NS_CONTENT_ATTR_HAS_VALUE == mContent->GetAttribute("SRC", newSRC)) { + if (!oldSRC.Equals(newSRC)) { + mSizeFrozen = PR_TRUE; + +#ifdef NS_DEBUG + char oldcbuf[100], newcbuf[100]; + oldSRC.ToCString(oldcbuf, sizeof(oldcbuf)); + newSRC.ToCString(newcbuf, sizeof(newcbuf)); + NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS, + ("ImageFrame::ContentChanged: new image source; old='%s' new='%s'", + oldcbuf, newcbuf)); +#endif + + // Get rid of old image loader and start a new image load going + mImageLoader.DestroyLoader(); + + // Fire up a new image load request + PRIntn loadStatus; + mImageLoader.SetURL(newSRC); + mImageLoader.StartLoadImage(aPresContext, this, PR_FALSE, loadStatus); + + NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS, + ("ImageFrame::ContentChanged: loadImage status=%x", + loadStatus)); + + // If the image is already ready then we need to trigger a + // redraw because the image loader won't. + if (loadStatus & NS_IMAGE_LOAD_STATUS_IMAGE_READY) { + // XXX Stuff this into a method on nsIPresShell/Context + nsRect bounds; + nsPoint offset; + nsIView* view; + GetOffsetFromView(offset, view); + nsIViewManager* vm; + view->GetViewManager(vm); + bounds.x = offset.x; + bounds.y = offset.y; + bounds.width = mRect.width; + bounds.height = mRect.height; + vm->UpdateView(view, bounds, 0); + NS_RELEASE(vm); + } + + return NS_OK; + } + } + + return ImageFrameSuper::ContentChanged(aShell, aPresContext, aChild, + aSubContent); +} diff --git a/mozilla/layout/generic/nsSpacerFrame.cpp b/mozilla/layout/generic/nsSpacerFrame.cpp new file mode 100644 index 00000000000..97a527a37cc --- /dev/null +++ b/mozilla/layout/generic/nsSpacerFrame.cpp @@ -0,0 +1,191 @@ +/* -*- 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.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. + */ +#include "nsHTMLParts.h" +#include "nsIHTMLContent.h" +#include "nsFrame.h" +#include "nsIInlineReflow.h" +#include "nsCSSLineLayout.h" +#include "nsHTMLIIDs.h" +#include "nsIPresContext.h" +#include "nsIPresShell.h" +#include "nsHTMLAtoms.h" +#include "nsUnitConversion.h" +#include "nsIStyleContext.h" +#include "nsStyleConsts.h" + +// Spacer type's +#define TYPE_WORD 0 // horizontal space +#define TYPE_LINE 1 // line-break + vertical space +#define TYPE_IMAGE 2 // acts like a sized image with nothing to see + +class SpacerFrame : public nsFrame, private nsIInlineReflow { +public: + SpacerFrame(nsIContent* aContent, nsIFrame* aParentFrame); + + // nsISupports + NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr); + + // nsIInlineReflow + NS_IMETHOD FindTextRuns(nsCSSLineLayout& aLineLayout, + nsIReflowCommand* aReflowCommand); + NS_IMETHOD InlineReflow(nsCSSLineLayout& aLineLayout, + nsReflowMetrics& aDesiredSize, + const nsReflowState& aReflowState); + + PRUint8 GetType(); + +protected: + virtual ~SpacerFrame(); +}; + +nsresult +NS_NewSpacerFrame(nsIContent* aContent, + nsIFrame* aParentFrame, + nsIFrame*& aResult) +{ + nsIFrame* frame = new SpacerFrame(aContent, aParentFrame); + if (nsnull == frame) { + return NS_ERROR_OUT_OF_MEMORY; + } + aResult = frame; + return NS_OK; +} + +SpacerFrame::SpacerFrame(nsIContent* aContent, nsIFrame* aParentFrame) + : nsFrame(aContent, aParentFrame) +{ +} + +SpacerFrame::~SpacerFrame() +{ +} + +NS_IMETHODIMP +SpacerFrame::QueryInterface(REFNSIID aIID, void** aInstancePtrResult) +{ + NS_PRECONDITION(nsnull != aInstancePtrResult, "null pointer"); + if (nsnull == aInstancePtrResult) { + return NS_ERROR_NULL_POINTER; + } + if (aIID.Equals(kIInlineReflowIID)) { + *aInstancePtrResult = (void*) ((nsIInlineReflow*)this); + return NS_OK; + } + return nsFrame::QueryInterface(aIID, aInstancePtrResult); +} + +NS_IMETHODIMP +SpacerFrame::InlineReflow(nsCSSLineLayout& aLineLayout, + nsReflowMetrics& aMetrics, + const nsReflowState& aReflowState) +{ + nsresult rv = NS_FRAME_COMPLETE; + + // By default, we have no area + aMetrics.width = 0; + aMetrics.height = 0; + aMetrics.ascent = 0; + aMetrics.descent = 0; + + nscoord width = 0; + nscoord height = 0; + PRUint8 type = GetType(); + nsresult ca; + nsIHTMLContent* hc = nsnull; + mContent->QueryInterface(kIHTMLContentIID, (void**) &hc); + if (nsnull != hc) { + if (type != TYPE_IMAGE) { + nsHTMLValue val; + ca = hc->GetAttribute(nsHTMLAtoms::size, val); + if (NS_CONTENT_ATTR_HAS_VALUE == ca) { + width = val.GetPixelValue(); + } + } else { + nsHTMLValue val; + ca = hc->GetAttribute(nsHTMLAtoms::width, val); + if (NS_CONTENT_ATTR_HAS_VALUE == ca) { + if (eHTMLUnit_Pixel == val.GetUnit()) { + width = val.GetPixelValue(); + } + } + ca = hc->GetAttribute(nsHTMLAtoms::height, val); + if (NS_CONTENT_ATTR_HAS_VALUE == ca) { + if (eHTMLUnit_Pixel == val.GetUnit()) { + height = val.GetPixelValue(); + } + } + } + NS_RELEASE(hc); + } + + float p2t = aLineLayout.mPresContext->GetPixelsToTwips(); + switch (type) { + case TYPE_WORD: + if (0 != width) { + aMetrics.width = NSIntPixelsToTwips(width, p2t); + } + break; + + case TYPE_LINE: + if (0 != width) { + rv = NS_INLINE_LINE_BREAK_AFTER(0); + aMetrics.height = NSIntPixelsToTwips(width, p2t); + aMetrics.ascent = aMetrics.height; + } + break; + + case TYPE_IMAGE: + aMetrics.width = NSIntPixelsToTwips(width, p2t); + aMetrics.height = NSIntPixelsToTwips(height, p2t); + aMetrics.ascent = aMetrics.height; + break; + } + + if (nsnull != aMetrics.maxElementSize) { + aMetrics.maxElementSize->width = aMetrics.width; + aMetrics.maxElementSize->height = aMetrics.height; + } + + return rv; +} + +NS_IMETHODIMP +SpacerFrame::FindTextRuns(nsCSSLineLayout& aLineLayout, + nsIReflowCommand* aReflowCommand) +{ + aLineLayout.EndTextRun(); + return NS_OK; +} + +PRUint8 +SpacerFrame::GetType() +{ + PRUint8 type = TYPE_WORD; + nsAutoString value; + if (NS_CONTENT_ATTR_HAS_VALUE == mContent->GetAttribute("type", value)) { + if (value.EqualsIgnoreCase("line") || + value.EqualsIgnoreCase("vert") || + value.EqualsIgnoreCase("vertical")) { + return TYPE_LINE; + } + if (value.EqualsIgnoreCase("block")) { + return TYPE_IMAGE; + } + } + return type; +} diff --git a/mozilla/layout/generic/nsTextFrame.cpp b/mozilla/layout/generic/nsTextFrame.cpp new file mode 100644 index 00000000000..f1272df7f49 --- /dev/null +++ b/mozilla/layout/generic/nsTextFrame.cpp @@ -0,0 +1,1558 @@ +/* -*- 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.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. + */ +#include "nsHTMLParts.h" +#include "nsCRT.h" +#include "nsSplittableFrame.h" +#include "nsIInlineReflow.h" +#include "nsCSSLineLayout.h" +#include "nsString.h" +#include "nsIPresContext.h" +#include "nsStyleConsts.h" +#include "nsIStyleContext.h" +#include "nsCoord.h" +#include "nsIFontMetrics.h" +#include "nsIRenderingContext.h" +#include "nsHTMLIIDs.h" +#include "nsIPresShell.h" +#include "nsIView.h" +#include "nsIViewManager.h" +#include "nsITimerCallback.h" +#include "nsITimer.h" +#include "prtime.h" +#include "nsVoidArray.h" +#include "prprf.h" +#include "nsIDOMText.h" +#include "nsIDocument.h" +#include "nsIDeviceContext.h" +#include "nsXIFConverter.h" +#include "nsITextContent.h" + +// Selection includes +#include "nsISelection.h" +#include "nsSelectionRange.h" + +#define CALC_DEBUG 0 +#define DO_SELECTION 0 + +static NS_DEFINE_IID(kIDOMTextIID, NS_IDOMTEXT_IID); + +#ifdef NS_DEBUG +#undef NOISY +#undef NOISY_BLINK +#else +#undef NOISY +#undef NOISY_BLINK +#endif + +// XXX TODO: +// 0. re-implement justified text +// 1. add in a rendering method that can render justified text +// 2. text renderer should negotiate with rendering context/font +// metrics to see what it can handle; for example, if the renderer can +// automatically deal with underlining, strikethrough, justification, +// etc, then the text renderer should let the rc do the work; +// otherwise there should be XP fallback code here. + +// XXX TODO: +// implement nsIFrame::Reflow + +// XXX Speedup ideas +// 1. justified text can use word width information during resize reflows +// 2. when we are doing an unconstrained reflow we know we are going to +// get reflowed again; collect up the word widths we are computing as we +// do this and then save them in the mWords; later on when we get reflowed +// again we can destroy them +// 3. when pulling up text get word width information from next-in-flow + +// XXX temporary +#define XP_IS_SPACE(_ch) \ + (((_ch) == ' ') || ((_ch) == '\t') || ((_ch) == '\n')) + +// XXX need more of this in nsIFontMetrics.GetWidth +#define CH_NBSP 160 + +// XXX use a PreTextFrame for pre-formatted text? + +static NS_DEFINE_IID(kITextContentIID, NS_ITEXT_CONTENT_IID); + +class TextFrame; + +class BlinkTimer : public nsITimerCallback { +public: + BlinkTimer(); + ~BlinkTimer(); + + NS_DECL_ISUPPORTS + + void AddFrame(TextFrame* aFrame); + + PRBool RemoveFrame(TextFrame* aFrame); + + PRInt32 FrameCount(); + + void Start(); + + void Stop(); + + virtual void Notify(nsITimer *timer); + + nsITimer* mTimer; + nsVoidArray mFrames; +}; + +class TextFrame : public nsSplittableFrame, private nsIInlineReflow { +public: + TextFrame(nsIContent* aContent, nsIFrame* aParentFrame); + + // nsISupports + NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr); + + // nsIFrame + NS_IMETHOD Paint(nsIPresContext& aPresContext, + nsIRenderingContext& aRenderingContext, + const nsRect& aDirtyRect); + + NS_IMETHOD GetCursorAndContentAt(nsIPresContext& aPresContext, + const nsPoint& aPoint, + nsIFrame** aFrame, + nsIContent** aContent, + PRInt32& aCursor); + + NS_IMETHOD ContentChanged(nsIPresShell* aShell, + nsIPresContext* aPresContext, + nsIContent* aChild, + nsISupports* aSubContent); + + NS_IMETHOD List(FILE* out, PRInt32 aIndent) const; + + NS_IMETHOD ListTag(FILE* out) const; + + virtual PRInt32 GetPosition(nsIPresContext& aCX, + nsGUIEvent* aEvent, + nsIFrame * aNewFrame, + PRUint32& aAcutalContentOffset); + + // nsIInlineReflow + NS_IMETHOD FindTextRuns(nsCSSLineLayout& aLineLayout, + nsIReflowCommand* aReflowCommand); + + NS_IMETHOD InlineReflow(nsCSSLineLayout& aLineLayout, + nsReflowMetrics& aMetrics, + const nsReflowState& aReflowState); + + // TextFrame methods + struct SelectionInfo { + PRInt32 mStartOffset; + PRInt32 mEndOffset; + PRBool mEmptySelection; + }; + + void ComputeSelectionInfo(nsIRenderingContext& aRenderingContext, + nsIDocument* aDocument, + PRInt32* aIndicies, PRInt32 aTextLength, + SelectionInfo& aResult); + + PRUnichar* PrepareUnicodeText(PRInt32* aIndicies, + PRUnichar* aBuffer, PRInt32 aBufSize, + PRInt32& aStrLen); + + char* PrepareAsciiText(PRInt32* aIndexes, + char* aBuffer, PRInt32 aBufSize, + PRInt32& aStrLen); + + void PaintUnicodeText(nsIPresContext& aPresContext, + nsIRenderingContext& aRenderingContext, + nscolor aTextColor, + nscolor aSelectionTextColor, + nscolor aSelectionBGColor, + nscoord dx, nscoord dy); + + void PaintAsciiText(nsIPresContext& aPresContext, + nsIRenderingContext& aRenderingContext, + nscolor aTextColor, + nscolor aSelectionTextColor, + nscolor aSelectionBGColor, + nscoord dx, nscoord dy); + + nsInlineReflowStatus ReflowPre(nsCSSLineLayout& aLineLayout, + nsReflowMetrics& aMetrics, + const nsReflowState& aReflowState, + const nsStyleFont& aFont, + PRInt32 aStartingOffset); + + nsInlineReflowStatus ReflowNormal(nsCSSLineLayout& aLineLayout, + nsReflowMetrics& aMetrics, + const nsReflowState& aReflowState, + const nsStyleFont& aFontStyle, + const nsStyleText& aTextStyle, + PRInt32 aStartingOffset); + +protected: + virtual ~TextFrame(); + + PRInt32 mContentOffset; + PRInt32 mContentLength; + + // XXX need a better packing + PRUint32 mFlags; + PRUint32 mColumn; +}; + +// Flag information used by rendering code. This information is +// computed by the ResizeReflow code. Remaining bits are used by the +// tab count. +#define TEXT_SKIP_LEADING_WS 0x01 +#define TEXT_HAS_MULTIBYTE 0x02 +#define TEXT_IS_PRE 0x04 +#define TEXT_BLINK_ON 0x08 +#define TEXT_ENDS_IN_WHITESPACE 0x10 + +#define TEXT_GET_TAB_COUNT(_mf) ((_mf) >> 5) +#define TEXT_SET_TAB_COUNT(_mf,_tabs) (_mf) = (_mf) | ((_tabs)<< 5) + +//---------------------------------------------------------------------- + +static PRBool gBlinkTextOff; +static BlinkTimer* gTextBlinker; +#ifdef NOISY_BLINK +static PRTime gLastTick; +#endif + +BlinkTimer::BlinkTimer() +{ + NS_INIT_REFCNT(); + mTimer = nsnull; +} + +BlinkTimer::~BlinkTimer() +{ + Stop(); +} + +void BlinkTimer::Start() +{ + nsresult rv = NS_NewTimer(&mTimer); + if (NS_OK == rv) { + mTimer->Init(this, 750); + } +} + +void BlinkTimer::Stop() +{ + if (nsnull != mTimer) { + mTimer->Cancel(); + NS_RELEASE(mTimer); + } +} + +static NS_DEFINE_IID(kITimerCallbackIID, NS_ITIMERCALLBACK_IID); +NS_IMPL_ISUPPORTS(BlinkTimer, kITimerCallbackIID); + +void BlinkTimer::AddFrame(TextFrame* aFrame) { + mFrames.AppendElement(aFrame); + if (1 == mFrames.Count()) { + Start(); + } +printf("%d blinking frames [add %p]\n", mFrames.Count(), aFrame); +} + +PRBool BlinkTimer::RemoveFrame(TextFrame* aFrame) { + PRBool rv = mFrames.RemoveElement(aFrame); + if (0 == mFrames.Count()) { + Stop(); + } +printf("%d blinking frames [remove %p] [%s]\n", + mFrames.Count(), aFrame, rv ? "true" : "FALSE"); + return rv; +} + +PRInt32 BlinkTimer::FrameCount() { + return mFrames.Count(); +} + +void BlinkTimer::Notify(nsITimer *timer) +{ + // Toggle blink state bit so that text code knows whether or not to + // render. All text code shares the same flag so that they all blink + // in unison. + gBlinkTextOff = PRBool(!gBlinkTextOff); + + // XXX hack to get auto-repeating timers; restart before doing + // expensive work so that time between ticks is more even + Stop(); + Start(); + +#ifdef NOISY_BLINK + PRTime now = PR_Now(); + char buf[50]; + PRTime delta; + LL_SUB(delta, now, gLastTick); + gLastTick = now; + PR_snprintf(buf, sizeof(buf), "%lldusec", delta); + printf("%s\n", buf); +#endif + + PRInt32 i, n = mFrames.Count(); + for (i = 0; i < n; i++) { + TextFrame* text = (TextFrame*) mFrames.ElementAt(i); + + // Determine damaged area and tell view manager to redraw it + nsPoint offset; + nsRect bounds; + text->GetRect(bounds); + nsIView* view; + text->GetOffsetFromView(offset, view); + nsIViewManager* vm; + view->GetViewManager(vm); + bounds.x = offset.x; + bounds.y = offset.y; + vm->UpdateView(view, bounds, 0); + NS_RELEASE(vm); + } +} + +//---------------------------------------------------------------------- + +nsresult +NS_NewTextFrame(nsIContent* aContent, nsIFrame* aParentFrame, + nsIFrame*& aResult) +{ + nsIFrame* frame = new TextFrame(aContent, aParentFrame); + if (nsnull == frame) { + return NS_ERROR_OUT_OF_MEMORY; + } + aResult = frame; + return NS_OK; +} + +TextFrame::TextFrame(nsIContent* aContent, nsIFrame* aParentFrame) + : nsSplittableFrame(aContent, aParentFrame) +{ + if (nsnull == gTextBlinker) { + // Create text timer the first time out + gTextBlinker = new BlinkTimer(); + } + NS_ADDREF(gTextBlinker); +} + +TextFrame::~TextFrame() +{ + if (0 != (mFlags & TEXT_BLINK_ON)) { + NS_ASSERTION(nsnull != gTextBlinker, "corrupted blinker"); + gTextBlinker->RemoveFrame(this); + } + if (0 == gTextBlinker->Release()) { + // Release text timer when the last text frame is gone + gTextBlinker = nsnull; + } +} + +NS_IMETHODIMP +TextFrame::QueryInterface(REFNSIID aIID, void** aInstancePtrResult) +{ + NS_PRECONDITION(nsnull != aInstancePtrResult, "null pointer"); + if (nsnull == aInstancePtrResult) { + return NS_ERROR_NULL_POINTER; + } + if (aIID.Equals(kIInlineReflowIID)) { + *aInstancePtrResult = (void*) ((nsIInlineReflow*)this); + return NS_OK; + } + return nsFrame::QueryInterface(aIID, aInstancePtrResult); +} + +NS_IMETHODIMP +TextFrame::GetCursorAndContentAt(nsIPresContext& aPresContext, + const nsPoint& aPoint, + nsIFrame** aFrame, + nsIContent** aContent, + PRInt32& aCursor) +{ + *aContent = mContent; + aCursor = NS_STYLE_CURSOR_IBEAM; + return NS_OK; +} + +NS_IMETHODIMP +TextFrame::ContentChanged(nsIPresShell* aShell, + nsIPresContext* aPresContext, + nsIContent* aChild, + nsISupports* aSubContent) +{ + // Generate a reflow command with this frame as the target frame + nsIReflowCommand* cmd; + nsresult result; + + result = NS_NewHTMLReflowCommand(&cmd, this, nsIReflowCommand::ContentChanged); + if (NS_OK == result) { + aShell->AppendReflowCommand(cmd); + NS_RELEASE(cmd); + } + + return result; +} + +NS_IMETHODIMP +TextFrame::Paint(nsIPresContext& aPresContext, + nsIRenderingContext& aRenderingContext, + const nsRect& aDirtyRect) +{ + if ((0 != (mFlags & TEXT_BLINK_ON)) && gBlinkTextOff) { + return NS_OK; + } + + const nsStyleDisplay* disp = + (const nsStyleDisplay*)mStyleContext->GetStyleData(eStyleStruct_Display); + + if (disp->mVisible) { + // Get style data + const nsStyleColor* color = + (const nsStyleColor*)mStyleContext->GetStyleData(eStyleStruct_Color); + const nsStyleFont* font = + (const nsStyleFont*)mStyleContext->GetStyleData(eStyleStruct_Font); + + // Set font and color + aRenderingContext.SetColor(color->mColor); + aRenderingContext.SetFont(font->mFont); + + // XXX Get these from style + nscolor selbg = NS_RGB(0, 0, 0); + nscolor selfg = NS_RGB(255, 255, 255); + + // Select rendering method and render + PRUint32 hints = 0; + aRenderingContext.GetHints(hints); + if ((TEXT_HAS_MULTIBYTE & mFlags) || + (0 == (hints & NS_RENDERING_HINT_FAST_8BIT_TEXT))) { + // Use PRUnichar rendering routine + PaintUnicodeText(aPresContext, aRenderingContext, + color->mColor, selfg, selbg, 0, 0); + } + else { + // Use char rendering routine + PaintAsciiText(aPresContext, aRenderingContext, + color->mColor, selfg, selbg, 0, 0); + } + } + + return NS_OK; +} + +/** + * To keep things efficient we depend on text content implementing + * our nsITextContent API + */ +static const PRUnichar* +GetText(nsIContent* aContent, PRInt32& aLengthResult) +{ + const PRUnichar* cp = nsnull; + nsITextContent* tc = nsnull; + aContent->QueryInterface(kITextContentIID, (void**) &tc); + if (nsnull != tc) { + tc->GetText(cp, aLengthResult); + NS_RELEASE(tc); + } + return cp; +} + +/** + * This method computes the starting and ending offsets of the + * selection for this frame. The results are placed into + * aResult. There are 5 cases that we represent with a starting offset + * (aResult.mStartOffset), ending offset (aResult.mEndOffset) and an + * empty selection flag (aResult.mEmptySelection): + * + * case 1: The selection completely misses this content/frame. In this + * case mStartOffset and mEndOffset will be set to aTextLength and + * mEmptySelection will be false. + * + * case 2: The selection begins somewhere before or at this frame and + * ends somewhere in this frame. In this case mStartOffset will be set + * to 0 and mEndOffset will be set to the end of the selection and + * mEmptySelection will be false. + * + * case 3: The selection begins somewhere in this frame and ends + * somewhere in this frame. In this case mStartOffset and mEndOffset + * are set accordingly and if they happen to be the same value then + * mEmptySelection is set to true (otherwise it is set to false). + * + * case 4: The selection begins somewhere in this frame and ends + * somewhere else. In this case mStartOffset is set to where the + * selection begins and mEndOffset is set to aTextLength and + * mEmptySelection is set to false. + * + * case 5: The selection covers the entire content/frame. In this case + * mStartOffset is set to zero and mEndOffset is set to aTextLength and + * mEmptySelection is set to false. + */ +void +TextFrame::ComputeSelectionInfo(nsIRenderingContext& aRenderingContext, + nsIDocument* aDocument, + PRInt32* aIndicies, PRInt32 aTextLength, + SelectionInfo& aResult) +{ + // Assume, for now, that the selection misses this section of + // content completely. + aResult.mStartOffset = aTextLength; + aResult.mEndOffset = aTextLength; + aResult.mEmptySelection = PR_FALSE; + + nsISelection * selection = aDocument->GetSelection(); + nsSelectionRange * range = selection->GetRange(); + nsSelectionPoint * startPnt = range->GetStartPoint(); + nsSelectionPoint * endPnt = range->GetEndPoint(); + nsIContent * startContent = startPnt->GetContent(); + nsIContent * endContent = endPnt->GetContent(); + PRInt32 startOffset = startPnt->GetOffset() - mContentOffset; + PRInt32 endOffset = endPnt->GetOffset() - mContentOffset; + + // Check for the case that requires up to 3 sections first. This + // case also handles the empty selection. + if ((mContent == startContent) && (mContent == endContent)) { + // Selection starts and ends in this content (but maybe not this + // frame) + if ((startOffset >= mContentLength) || (endOffset <= 0)) { + // Selection doesn't intersect this frame + } + else if (endOffset < mContentLength) { + // End of selection is in this frame + aResult.mEndOffset = aIndicies[endOffset] - mContentOffset; + if (startOffset > 0) { + // Beginning of selection is also in this frame (this is the 3 + // section case) + aResult.mStartOffset = aIndicies[startOffset] - mContentOffset; + } + else { + // This is a 2 section case + aResult.mStartOffset = 0; + } + if (startOffset == endOffset) { + aResult.mEmptySelection = PR_TRUE; + } + } else if (startOffset > 0) { + // This is a 2 section case + aResult.mStartOffset = aIndicies[startOffset] - mContentOffset; + } else { + // This is a 1 section case (where the entire section is + // selected) + aResult.mStartOffset = 0; + } + } + else if (aDocument->IsInRange(startContent, endContent, mContent)) { + if (mContent == startContent) { + // Selection starts (but does not end) in this content (but + // maybe not in this frame) + if (startOffset <= 0) { + // Selection starts before or at this frame + aResult.mStartOffset = 0; + } + else if (startOffset < mContentLength) { + // Selection starts somewhere in this frame + aResult.mStartOffset = aIndicies[startOffset] - mContentOffset; + } + else { + // Selection starts after this frame + } + } + else if (mContent == endContent) { + // Selection ends (but does not start) in this content (but + // maybe not in this frame) + if (endOffset <= 0) { + // Selection ends before this frame + } + else if (endOffset < mContentLength) { + // Selection ends in this frame + aResult.mStartOffset = 0; + aResult.mEndOffset = aIndicies[endOffset] - mContentOffset; + } + else { + // Selection ends after this frame (the entire frame is selected) + aResult.mStartOffset = 0; + } + } + else { + // Selection starts before this content and ends after this + // content therefore the entire frame is selected + aResult.mStartOffset = 0; + } + } + + NS_IF_RELEASE(startContent); + NS_IF_RELEASE(endContent); + NS_RELEASE(selection); +} + +#define XP_IS_SPACE_W XP_IS_SPACE + +/** + * Prepare the text in the content for rendering. If aIndexes is not nsnull + * then fill in aIndexes's with the mapping from the original input to + * the prepared output. + */ +PRUnichar* +TextFrame::PrepareUnicodeText(PRInt32* aIndexes, + PRUnichar* aBuffer, PRInt32 aBufSize, + PRInt32& aStrLen) +{ + PRUnichar* s = aBuffer; + PRUnichar* s0 = s; + + // Get text content + PRInt32 textLength; + const PRUnichar* cp = GetText(mContent, textLength); + if (0 == textLength) { + aBuffer[0] = 0; + aStrLen = 0; + return aBuffer; + } + cp += mContentOffset; + const PRUnichar* end = cp + mContentLength; + + // Skip leading space if necessary + PRInt32 mappingInx = 0; + PRInt32 strInx = mContentOffset; + if (0 != (mFlags & TEXT_SKIP_LEADING_WS)) { + while (cp < end) { + PRUnichar ch = *cp++; + if (!XP_IS_SPACE_W(ch)) { + cp--; + break; + } else { + if (nsnull != aIndexes) { + aIndexes[mappingInx++] = strInx; + } + } + } + } + + PRInt32 length = 0; + if (0 != (TEXT_IS_PRE & mFlags)) { + PRIntn tabs = TEXT_GET_TAB_COUNT(mFlags); + + // Make a copy of the text we are to render, translating tabs + // into whitespace. + PRInt32 maxLen = (end - cp) + 8*tabs; + if (maxLen > aBufSize) { + s0 = s = new PRUnichar[maxLen]; + } + + // Translate tabs into whitespace; translate other whitespace into + // spaces. + PRIntn col = (PRIntn) mColumn; + while (cp < end) { + PRUnichar ch = *cp++; + if (XP_IS_SPACE_W(ch)) { + if (ch == '\t') { + PRIntn spaces = 8 - (col & 7); + col += spaces; + while (--spaces >= 0) { + *s++ = ' '; + length++; + } + if (nsnull != aIndexes) { + aIndexes[mappingInx++] = strInx; + strInx++;/* XXX wrong? */ + } + continue; + } else { + *s++ = ' '; + length++; + if (nsnull != aIndexes) { + aIndexes[mappingInx++] = strInx; + strInx++; + } + } + } else if (ch == CH_NBSP) { + *s++ = ' '; + length++; + if (nsnull != aIndexes) { + aIndexes[mappingInx++] = strInx; + strInx++; + } + } else { + *s++ = ch; + length++; + if (nsnull != aIndexes) { + aIndexes[mappingInx++] = strInx; + strInx++; + } + } + col++; + } + } else { + // Make a copy of the text we are to render, compressing out + // whitespace; translating whitespace to literal spaces; + // eliminating trailing whitespace. + PRInt32 maxLen = end - cp; + if (maxLen > aBufSize) { + s0 = s = new PRUnichar[maxLen]; + } + + // Compress down the whitespace + while (cp < end) { + PRUnichar ch = *cp++; + if (XP_IS_SPACE_W(ch)) { + while (cp < end) { + ch = *cp++; + if (!XP_IS_SPACE_W(ch)) { + cp--; + break; + } else { + if (nsnull != aIndexes) { + aIndexes[mappingInx++] = strInx; + } + } + } + *s++ = ' '; + length++; + if (nsnull != aIndexes) { + aIndexes[mappingInx++] = strInx; + strInx++; + } + } else { + *s++ = ch; + length++; + if (nsnull != aIndexes) { + aIndexes[mappingInx++] = strInx; + strInx++; + } + } + } + } + + aStrLen = length; + return s0; +} + +/** + * Prepare the text in the content for rendering. If aIndexes is not nsnull + * then fill in aIndexes's with the mapping from the original input to + * the prepared output. + */ +char* +TextFrame::PrepareAsciiText(PRInt32* aIndexes, + char* aBuffer, PRInt32 aBufSize, + PRInt32& aStrLen) +{ + char* s = aBuffer; + char* s0 = s; + + // Get text content + PRInt32 textLength; + const PRUnichar* cp = GetText(mContent, textLength); + if (0 == textLength) { + aBuffer[0] = 0; + aStrLen = 0; + return aBuffer; + } + cp += mContentOffset; + const PRUnichar* end = cp + mContentLength; + + // Skip leading space if necessary + PRInt32 mappingInx = 0; + PRInt32 strInx = mContentOffset; + if (0 != (mFlags & TEXT_SKIP_LEADING_WS)) { + while (cp < end) { + char ch = char(*cp++); + if (!XP_IS_SPACE(ch)) { + cp--; + break; + } else { + if (nsnull != aIndexes) { + aIndexes[mappingInx++] = strInx; + } + } + } + } + + PRInt32 length = 0; + if (0 != (TEXT_IS_PRE & mFlags)) { + PRIntn tabs = TEXT_GET_TAB_COUNT(mFlags); + + // Make a copy of the text we are to render, translating tabs + // into whitespace. + PRInt32 maxLen = (end - cp) + 8*tabs; + if (maxLen > aBufSize) { + s0 = s = new char[maxLen]; + } + + // Translate tabs into whitespace; translate other whitespace into + // spaces. + PRIntn col = (PRIntn) mColumn; + while (cp < end) { + char ch = char(*cp++); + if (XP_IS_SPACE(ch)) { + if (ch == '\t') { + PRIntn spaces = 8 - (col & 7); + col += spaces; + while (--spaces >= 0) { + *s++ = ' '; + length++; + } + if (nsnull != aIndexes) { + aIndexes[mappingInx++] = strInx; + strInx++;/* XXX wrong? */ + } + continue; + } else { + *s++ = ' '; + length++; + if (nsnull != aIndexes) { + aIndexes[mappingInx++] = strInx; + strInx++; + } + } + } else if (ch == CH_NBSP) { + *s++ = ' '; + length++; + if (nsnull != aIndexes) { + aIndexes[mappingInx++] = strInx; + strInx++; + } + } else { + *s++ = ch; + length++; + if (nsnull != aIndexes) { + aIndexes[mappingInx++] = strInx; + strInx++; + } + } + col++; + } + } else { + // Make a copy of the text we are to render, compressing out + // whitespace; translating whitespace to literal spaces; + // eliminating trailing whitespace. + PRInt32 maxLen = end - cp; + if (maxLen > aBufSize) { + s0 = s = new char[maxLen]; + } + + // Compress down the whitespace + while (cp < end) { + char ch = char(*cp++); + if (XP_IS_SPACE(ch)) { + while (cp < end) { + ch = char(*cp++); + if (!XP_IS_SPACE(ch)) { + cp--; + break; + } else { + if (nsnull != aIndexes) { + aIndexes[mappingInx++] = strInx; + } + } + } + *s++ = ' '; + length++; + if (nsnull != aIndexes) { + aIndexes[mappingInx++] = strInx; + strInx++; + } + } else { + *s++ = ch; + length++; + if (nsnull != aIndexes) { + aIndexes[mappingInx++] = strInx; + strInx++; + } + } + } + } + + aStrLen = length; + return s0; +} + +// XXX This clearly needs to be done by the container, *somehow* +#define CURSOR_COLOR NS_RGB(0,0,255) +static void +RenderSelectionCursor(nsIRenderingContext& aRenderingContext, + nscoord dx, nscoord dy, nscoord aHeight, + nscolor aCursorColor) +{ + // Draw little blue triangle + aRenderingContext.SetColor(aCursorColor); + nsPoint pnts[4]; + nscoord ox = aHeight / 4; + nscoord oy = ox; + nscoord x0 = dx; + nscoord y0 = dy + aHeight; + pnts[0].x = x0 - ox; + pnts[0].y = y0; + pnts[1].x = x0; + pnts[1].y = y0 - oy; + pnts[2].x = x0 + ox; + pnts[2].y = y0; + pnts[3].x = x0 - ox; + pnts[3].y = y0; + + aRenderingContext.FillPolygon(pnts, 4); +} + +// XXX letter-spacing +// XXX word-spacing + +void +TextFrame::PaintUnicodeText(nsIPresContext& aPresContext, + nsIRenderingContext& aRenderingContext, + nscolor aTextColor, + nscolor aSelectionTextColor, + nscolor aSelectionBGColor, + nscoord dx, nscoord dy) +{ + nsIPresShell* shell = aPresContext.GetShell(); + nsIDocument* doc = shell->GetDocument(); + PRBool displaySelection; + displaySelection = doc->GetDisplaySelection(); + + PRInt32 textLength; + PRInt32 indicies[500]; + PRInt32* ip = indicies; + if (mContentLength > 500) { + ip = new PRInt32[mContentLength]; + } + PRUnichar buf[500]; + PRUnichar* text = PrepareUnicodeText(displaySelection ? ip : nsnull, + buf, 500, textLength); + if (0 != textLength) { + if (!displaySelection) { + // When there is no selection showing, use the fastest and + // simplest rendering approach + aRenderingContext.DrawString(text, textLength, dx, dy, mRect.width); + } + else { + SelectionInfo si; + ComputeSelectionInfo(aRenderingContext, doc, ip, textLength, si); + + nscoord textWidth; + nsIFontMetrics * fm = aRenderingContext.GetFontMetrics(); + if (si.mEmptySelection) { + aRenderingContext.DrawString(text, textLength, dx, dy, mRect.width); + fm->GetWidth(text, PRUint32(si.mStartOffset), textWidth); + RenderSelectionCursor(aRenderingContext, + dx + textWidth, dy, mRect.height, + CURSOR_COLOR); + } + else { + nscoord x = dx; + + if (0 != si.mStartOffset) { + // Render first (unselected) section + fm->GetWidth(text, PRUint32(si.mStartOffset), textWidth); + aRenderingContext.DrawString(text, si.mStartOffset, + x, dy, textWidth); + x += textWidth; + } + PRInt32 secondLen = si.mEndOffset - si.mStartOffset; + if (0 != secondLen) { + // Get the width of the second (selected) section + fm->GetWidth(text + si.mStartOffset, PRUint32(secondLen), textWidth); + + // Render second (selected) section + aRenderingContext.SetColor(aSelectionBGColor); + aRenderingContext.FillRect(x, dy, textWidth, mRect.height); + aRenderingContext.SetColor(aSelectionTextColor); + aRenderingContext.DrawString(text + si.mStartOffset, secondLen, + x, dy, textWidth); + aRenderingContext.SetColor(aTextColor); + x += textWidth; + } + if (textLength != si.mEndOffset) { + PRInt32 thirdLen = textLength - si.mEndOffset; + + // Render third (unselected) section + fm->GetWidth(text + si.mEndOffset, PRUint32(thirdLen), textWidth); + aRenderingContext.DrawString(text + si.mEndOffset, + thirdLen, x, dy, textWidth); + } + } + NS_RELEASE(fm); + } + } + + // Cleanup + if (text != buf) { + delete [] text; + } + if (ip != indicies) { + delete [] ip; + } + NS_RELEASE(shell); + NS_RELEASE(doc); +} + +void +TextFrame::PaintAsciiText(nsIPresContext& aPresContext, + nsIRenderingContext& aRenderingContext, + nscolor aTextColor, + nscolor aSelectionTextColor, + nscolor aSelectionBGColor, + nscoord dx, nscoord dy) +{ + nsIPresShell* shell = aPresContext.GetShell(); + nsIDocument* doc = shell->GetDocument(); + PRBool displaySelection; + displaySelection = doc->GetDisplaySelection(); + + PRInt32 textLength; + PRInt32 indicies[500]; + PRInt32* ip = indicies; + if (mContentLength > 500) { + ip = new PRInt32[mContentLength]; + } + char buf[500]; + char* text = PrepareAsciiText(displaySelection ? ip : nsnull, + buf, 500, textLength); + if (0 != textLength) { + if (!displaySelection) { + // When there is no selection showing, use the fastest and + // simplest rendering approach + aRenderingContext.DrawString(text, textLength, dx, dy, mRect.width); + } + else { + SelectionInfo si; + ComputeSelectionInfo(aRenderingContext, doc, ip, textLength, si); + + nscoord textWidth; + nsIFontMetrics * fm = aRenderingContext.GetFontMetrics(); + if (si.mEmptySelection) { + aRenderingContext.DrawString(text, textLength, dx, dy, mRect.width); + fm->GetWidth(text, PRUint32(si.mStartOffset), textWidth); + RenderSelectionCursor(aRenderingContext, + dx + textWidth, dy, mRect.height, + CURSOR_COLOR); + } + else { + nscoord x = dx; + + if (0 != si.mStartOffset) { + // Render first (unselected) section + fm->GetWidth(text, PRUint32(si.mStartOffset), textWidth); + aRenderingContext.DrawString(text, si.mStartOffset, + x, dy, textWidth); + x += textWidth; + } + PRInt32 secondLen = si.mEndOffset - si.mStartOffset; + if (0 != secondLen) { + // Get the width of the second (selected) section + fm->GetWidth(text + si.mStartOffset, PRUint32(secondLen), textWidth); + + // Render second (selected) section + aRenderingContext.SetColor(aSelectionBGColor); + aRenderingContext.FillRect(x, dy, textWidth, mRect.height); + aRenderingContext.SetColor(aSelectionTextColor); + aRenderingContext.DrawString(text + si.mStartOffset, secondLen, + x, dy, textWidth); + aRenderingContext.SetColor(aTextColor); + x += textWidth; + } + if (textLength != si.mEndOffset) { + PRInt32 thirdLen = textLength - si.mEndOffset; + + // Render third (unselected) section + fm->GetWidth(text + si.mEndOffset, PRUint32(thirdLen), textWidth); + aRenderingContext.DrawString(text + si.mEndOffset, + thirdLen, x, dy, textWidth); + } + } + NS_RELEASE(fm); + } + } + + // Cleanup + if (text != buf) { + delete [] text; + } + if (ip != indicies) { + delete [] ip; + } + NS_RELEASE(shell); + NS_RELEASE(doc); +} + +NS_IMETHODIMP +TextFrame::FindTextRuns(nsCSSLineLayout& aLineLayout, + nsIReflowCommand* aReflowCommand) +{ + if (nsnull == mPrevInFlow) { + aLineLayout.AddText(this); + } + return NS_OK; +} + +// XXX this is slow + +// XXX it doesn't work well when dragging the end of the selection +// (can't hit the first character) + +// XXX it doesn't do 1/2 the char width to make picking characters +// more sensible + +PRInt32 +TextFrame::GetPosition(nsIPresContext& aCX, + nsGUIEvent* aEvent, + nsIFrame* aNewFrame, + PRUint32& aAcutalContentOffset) +{ + // Get the rendered form of the text + PRInt32 textLength; + PRInt32 indicies[500]; + PRInt32* ip = indicies; + if (mContentLength > 500) { + ip = new PRInt32[mContentLength]; + } + PRUnichar buf[500]; + PRUnichar* text = PrepareUnicodeText(ip, buf, 500, textLength); + + // Find the font metrics for this text + nsIStyleContext* styleContext; + aNewFrame->GetStyleContext(&aCX, styleContext); + const nsStyleFont *font = (const nsStyleFont*) + styleContext->GetStyleData(eStyleStruct_Font); + NS_RELEASE(styleContext); + nsIFontMetrics* fm = aCX.GetMetricsFor(font->mFont); + + // XXX This algorithm could use some improvement + PRInt32 offset = mContentOffset + mContentLength; + PRInt32 i; + for (i=1;iGetWidth(text, i, textWidth); + if (textWidth >= aEvent->point.x) { + if (aEvent->message == NS_MOUSE_LEFT_BUTTON_DOWN) { + i--; + if (i < 0) { + i = 0; + } + } + offset = 0; + PRInt32 j; + for (j=0;jmContentOffset; + return offset; +} + +NS_IMETHODIMP +TextFrame::InlineReflow(nsCSSLineLayout& aLineLayout, + nsReflowMetrics& aMetrics, + const nsReflowState& aReflowState) +{ + NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS, + ("enter TextFrame::Reflow: aMaxSize=%d,%d", + aReflowState.maxSize.width, aReflowState.maxSize.height)); + + // Get starting offset into the content + PRInt32 startingOffset = 0; + if (nsnull != mPrevInFlow) { + TextFrame* prev = (TextFrame*) mPrevInFlow; + startingOffset = prev->mContentOffset + prev->mContentLength; + } + + const nsStyleFont* font = + (const nsStyleFont*)mStyleContext->GetStyleData(eStyleStruct_Font); + + // Initialize mFlags (without destroying the TEXT_BLINK_ON bit) bits + // that are filled in by the reflow routines. + mFlags &= TEXT_BLINK_ON; + if (font->mFont.decorations & NS_STYLE_TEXT_DECORATION_BLINK) { + if (0 == (mFlags & TEXT_BLINK_ON)) { + mFlags |= TEXT_BLINK_ON; + gTextBlinker->AddFrame(this); + } + } + + const nsStyleText* text = + (const nsStyleText*)mStyleContext->GetStyleData(eStyleStruct_Text); + + nsInlineReflowStatus rs; + if (NS_STYLE_WHITESPACE_PRE == text->mWhiteSpace) { + // Use a specialized routine for pre-formatted text + rs = ReflowPre(aLineLayout, aMetrics, aReflowState, + *font, startingOffset); + } else { + // Use normal wrapping routine for non-pre text (this includes + // text that is not wrapping) + rs = ReflowNormal(aLineLayout, aMetrics, aReflowState, + *font, *text, startingOffset); + } + + NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS, + ("exit TextFrame::Reflow: rv=%x width=%d", + rs, aMetrics.width)); + return rs; +} + +// Reflow normal text (stuff that doesn't have to deal with horizontal +// tabs). Normal text reflow may or may not wrap depending on the +// "whiteSpace" style property. +nsInlineReflowStatus +TextFrame::ReflowNormal(nsCSSLineLayout& aLineLayout, + nsReflowMetrics& aMetrics, + const nsReflowState& aReflowState, + const nsStyleFont& aFont, + const nsStyleText& aTextStyle, + PRInt32 aStartingOffset) +{ + PRInt32 textLength; + const PRUnichar* cp = GetText(mContent, textLength); + cp += aStartingOffset; + const PRUnichar* end = cp + textLength - aStartingOffset; + const PRUnichar* cpStart = cp; + mContentOffset = aStartingOffset; + + nsIFontMetrics* fm = aLineLayout.mPresContext->GetMetricsFor(aFont.mFont); + PRInt32 spaceWidth; + fm->GetWidth(' ', spaceWidth); + PRBool wrapping = PR_TRUE; + if (NS_STYLE_WHITESPACE_NORMAL != aTextStyle.mWhiteSpace) { + wrapping = PR_FALSE; + } + + // Set whitespace skip flag + PRBool skipWhitespace = PR_FALSE; + if (aLineLayout.GetSkipLeadingWhiteSpace()) { + skipWhitespace = PR_TRUE; + mFlags |= TEXT_SKIP_LEADING_WS; + } + + // Try to fit as much of the text as possible. Note that if we are + // at the left margin then the first word always fits. In addition, + // we compute the size of the largest word that we contain. If we + // end up containing nothing (because there isn't enough space for + // the first word) then we still compute the size of that first + // non-fitting word. + + // XXX XP_IS_SPACE must not return true for the unicode   character + // XXX what about &zwj and it's cousins? + nscoord x = 0; + nscoord maxWidth = aReflowState.maxSize.width; + nscoord maxWordWidth = 0; + const PRUnichar* lastWordEnd = cpStart; +// const PRUnichar* lastWordStart = cpStart; + PRBool hasMultibyte = PR_FALSE; + PRBool endsInWhitespace = PR_FALSE; + + while (cp < end) { + PRUnichar ch = *cp++; + PRBool isWhitespace; + nscoord width; + if (XP_IS_SPACE(ch)) { + // Compress whitespace down to a single whitespace + while (cp < end) { + ch = *cp; + if (XP_IS_SPACE(ch)) { + cp++; + continue; + } + break; + } + if (skipWhitespace) { +#if XXX_fix_me + aLineLayout->AtSpace(); +#endif + skipWhitespace = PR_FALSE; + continue; + } + width = spaceWidth; + isWhitespace = PR_TRUE; + } else { + // The character is not a space character. Find the end of the + // word and then measure it. + if (ch >= 128) { + hasMultibyte = PR_TRUE; + } + const PRUnichar* wordStart = cp - 1; +// lastWordStart = wordStart; + while (cp < end) { + ch = *cp; + if (ch >= 256) { + hasMultibyte = PR_TRUE; + } + if (!XP_IS_SPACE(ch)) { + cp++; + continue; + } + break; + } + fm->GetWidth(wordStart, PRUint32(cp - wordStart), width); + skipWhitespace = PR_FALSE; + isWhitespace = PR_FALSE; + } + + // Now that we have the end of the word or whitespace, see if it + // will fit. + if ((0 != x) && wrapping && (x + width > maxWidth)) { + // The word/whitespace will not fit. + cp = lastWordEnd; + break; + } + +#if XXX_fix_me + // Update break state in line reflow state + // XXX move this out of the loop! + if (isWhitespace) { + aLineLayout.AtSpace(); + } + else { + aLineLayout.AtWordStart(this, x); + } +#endif + + // The word fits. Add it to the run of text. + x += width; + if (width > maxWordWidth) { + maxWordWidth = width; + } + lastWordEnd = cp; + endsInWhitespace = isWhitespace; + } + + if (hasMultibyte) { + mFlags |= TEXT_HAS_MULTIBYTE; + } + if (endsInWhitespace) { + mFlags |= TEXT_ENDS_IN_WHITESPACE; + } + + if (0 == x) { + // Since we collapsed into nothingness (all our whitespace + // is ignored) leave the aState->mSkipLeadingWhiteSpace + // flag alone since it doesn't want leading whitespace + } + else { + aLineLayout.SetSkipLeadingWhiteSpace(endsInWhitespace); + } + + // XXX too much code here: some of it isn't needed + // Now we know our content length + mContentLength = lastWordEnd - cpStart; + if (0 == mContentLength) { + if (cp == end) { + // The entire chunk of text was whitespace that we skipped over. + aMetrics.width = 0; + aMetrics.height = 0; + aMetrics.ascent = 0; + aMetrics.descent = 0; + mContentLength = end - cpStart; + if (nsnull != aMetrics.maxElementSize) { + aMetrics.maxElementSize->width = 0; + aMetrics.maxElementSize->height = 0; + } + NS_RELEASE(fm); + return NS_FRAME_COMPLETE; + } + } + + // Set desired size to the computed size + aMetrics.width = x; + fm->GetHeight(aMetrics.height); + fm->GetMaxAscent(aMetrics.ascent); + fm->GetMaxDescent(aMetrics.descent); + if (!wrapping) { + maxWordWidth = x; + } + if (nsnull != aMetrics.maxElementSize) { + aMetrics.maxElementSize->width = maxWordWidth; + fm->GetHeight(aMetrics.maxElementSize->height); + } + NS_RELEASE(fm); + return (cp == end) ? NS_FRAME_COMPLETE : NS_FRAME_NOT_COMPLETE; +} + +nsInlineReflowStatus +TextFrame::ReflowPre(nsCSSLineLayout& aLineLayout, + nsReflowMetrics& aMetrics, + const nsReflowState& aReflowState, + const nsStyleFont& aFont, + PRInt32 aStartingOffset) +{ + nsInlineReflowStatus rs = NS_FRAME_COMPLETE; + + PRInt32 textLength; + const PRUnichar* cp = GetText(mContent, textLength); + cp += aStartingOffset; + const PRUnichar* cpStart = cp; + const PRUnichar* end = cp + textLength - aStartingOffset; + + mFlags |= TEXT_IS_PRE; + nsIFontMetrics* fm = aLineLayout.mPresContext->GetMetricsFor(aFont.mFont); + PRInt32 width = 0; + PRBool hasMultibyte = PR_FALSE; + PRUint16 tabs = 0; + PRUint16 col = aLineLayout.GetColumn(); + mColumn = col; + nscoord spaceWidth; + fm->GetWidth(' ', spaceWidth); + +// XXX change this to measure a line at a time + while (cp < end) { + PRUnichar ch = *cp++; + if (ch == '\n') { + rs = (cp == end) + ? NS_INLINE_LINE_BREAK_AFTER(NS_FRAME_COMPLETE) + : NS_INLINE_LINE_BREAK_AFTER(NS_FRAME_NOT_COMPLETE); + break; + } + if (ch == '\t') { + // Advance to next tab stop + PRIntn spaces = 8 - (col & 7); + width += spaces * spaceWidth; + col += spaces; + tabs++; + continue; + } + if (ch == CH_NBSP) { + width += spaceWidth; + col++; + continue; + } + if (ch < 256) { + nscoord charWidth; + fm->GetWidth(ch, charWidth); + width += charWidth; + } else { + nscoord charWidth; + fm->GetWidth(ch, charWidth); + width += charWidth; + hasMultibyte = PR_TRUE; + } + col++; + } + aLineLayout.SetColumn(col); + if (hasMultibyte) { + mFlags |= TEXT_HAS_MULTIBYTE; + } + TEXT_SET_TAB_COUNT(mFlags, tabs); + + mContentOffset = aStartingOffset; + mContentLength = cp - cpStart; + aMetrics.width = width; + fm->GetHeight(aMetrics.height); + fm->GetMaxAscent(aMetrics.ascent); + fm->GetMaxDescent(aMetrics.descent); + if (nsnull != aMetrics.maxElementSize) { + aMetrics.maxElementSize->width = aMetrics.width; + aMetrics.maxElementSize->height = aMetrics.height; + } + NS_RELEASE(fm); + + return rs; +} + +// XXX there is a copy of this in nsGenericDomDataNode.cpp +static void +ToCString(const PRUnichar* cp, PRInt32 aLen, nsString& aBuf) +{ + const PRUnichar* end = cp + aLen; + while (cp < end) { + PRUnichar ch = *cp++; + if (ch == '\r') { + aBuf.Append("\\r"); + } else if (ch == '\n') { + aBuf.Append("\\n"); + } else if (ch == '\t') { + aBuf.Append("\\t"); + } else if ((ch < ' ') || (ch >= 127)) { + aBuf.Append("\\0"); + aBuf.Append((PRInt32)ch, 8); + } else { + aBuf.Append(ch); + } + } +} + +NS_IMETHODIMP +TextFrame::ListTag(FILE* out) const +{ + PRInt32 contentIndex; + GetContentIndex(contentIndex); + fprintf(out, "Text(%d)@%p", contentIndex, this); + return NS_OK; +} + +NS_IMETHODIMP +TextFrame::List(FILE* out, PRInt32 aIndent) const +{ + PRInt32 i; + for (i = aIndent; --i >= 0; ) fputs(" ", out); + + // Output the tag + ListTag(out); + nsIView* view; + GetView(view); + if (nsnull != view) { + fprintf(out, " [view=%p]", view); + } + + // Output the first/last content offset and prev/next in flow info + // XXX inefficient (especially for really large strings) + PRInt32 textLength; + const PRUnichar* cp = GetText(mContent, textLength); + + PRBool isComplete = (mContentOffset + mContentLength) == textLength; + fprintf(out, "[%d,%d,%c] ", + mContentOffset, mContentOffset+mContentLength-1, + isComplete ? 'T':'F'); + if (nsnull != mPrevInFlow) { + fprintf(out, "prev-in-flow=%p ", mPrevInFlow); + } + if (nsnull != mNextInFlow) { + fprintf(out, "next-in-flow=%p ", mNextInFlow); + } + + // Output the rect and state + out << mRect; + if (0 != mState) { + fprintf(out, " [state=%08x]", mState); + } + + // Output the text + fputs("<\n", out); + aIndent++; + + for (i = aIndent; --i >= 0; ) fputs(" ", out); + fputs("\"", out); + nsAutoString tmp; + ToCString(cp, mContentLength, tmp); + fputs(tmp, out); + fputs("\"\n", out); + + aIndent--; + for (i = aIndent; --i >= 0; ) fputs(" ", out); + fputs(">\n", out); + + return NS_OK; +} diff --git a/mozilla/layout/html/base/src/nsBRFrame.cpp b/mozilla/layout/html/base/src/nsBRFrame.cpp new file mode 100644 index 00000000000..3b595cd87d1 --- /dev/null +++ b/mozilla/layout/html/base/src/nsBRFrame.cpp @@ -0,0 +1,141 @@ +/* -*- 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.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. + */ +#include "nsFrame.h" +#include "nsHTMLParts.h" +#include "nsHTMLIIDs.h" +#include "nsIPresContext.h" +#include "nsIInlineReflow.h" +#include "nsCSSLineLayout.h" +#include "nsStyleConsts.h" +#include "nsHTMLAtoms.h" +#include "nsIStyleContext.h" +#include "nsIFontMetrics.h" +#include "nsIRenderingContext.h" + +class BRFrame : public nsFrame, public nsIInlineReflow { +public: + BRFrame(nsIContent* aContent, nsIFrame* aParentFrame); + + // nsISupports + NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr); + + // nsIFrame + NS_IMETHOD Paint(nsIPresContext& aPresContext, + nsIRenderingContext& aRenderingContext, + const nsRect& aDirtyRect); + + // nsIInlineReflow + NS_IMETHOD FindTextRuns(nsCSSLineLayout& aLineLayout, + nsIReflowCommand* aReflowCommand); + NS_IMETHOD InlineReflow(nsCSSLineLayout& aLineLayout, + nsReflowMetrics& aDesiredSize, + const nsReflowState& aReflowState); + +protected: + virtual ~BRFrame(); +}; + +nsresult +NS_NewBRFrame(nsIContent* aContent, nsIFrame* aParentFrame, + nsIFrame*& aResult) +{ + nsIFrame* frame = new BRFrame(aContent, aParentFrame); + if (nsnull == frame) { + return NS_ERROR_OUT_OF_MEMORY; + } + aResult = frame; + return NS_OK; +} + +BRFrame::BRFrame(nsIContent* aContent, + nsIFrame* aParentFrame) + : nsFrame(aContent, aParentFrame) +{ +} + +BRFrame::~BRFrame() +{ +} + +NS_IMETHODIMP +BRFrame::QueryInterface(REFNSIID aIID, void** aInstancePtrResult) +{ + NS_PRECONDITION(nsnull != aInstancePtrResult, "null pointer"); + if (nsnull == aInstancePtrResult) { + return NS_ERROR_NULL_POINTER; + } + if (aIID.Equals(kIInlineReflowIID)) { + *aInstancePtrResult = (void*) ((nsIInlineReflow*)this); + return NS_OK; + } + return nsFrame::QueryInterface(aIID, aInstancePtrResult); +} + +NS_METHOD +BRFrame::Paint(nsIPresContext& aPresContext, + nsIRenderingContext& aRenderingContext, + const nsRect& aDirtyRect) +{ + if (nsIFrame::GetShowFrameBorders()) { + float p2t = aPresContext.GetPixelsToTwips(); + aRenderingContext.SetColor(NS_RGB(0, 255, 255)); + aRenderingContext.FillRect(0, 0, NSIntPixelsToTwips(5, p2t), mRect.height); + } + return NS_OK; +} + +NS_IMETHODIMP +BRFrame::FindTextRuns(nsCSSLineLayout& aLineLayout, + nsIReflowCommand* aReflowCommand) +{ + aLineLayout.EndTextRun(); + return NS_OK; +} + +NS_IMETHODIMP +BRFrame::InlineReflow(nsCSSLineLayout& aLineLayout, + nsReflowMetrics& aMetrics, + const nsReflowState& aReflowState) +{ + if (nsnull != aMetrics.maxElementSize) { + aMetrics.maxElementSize->width = 0; + aMetrics.maxElementSize->height = 0; + } + + // We have no width, but we're the height of the default font + const nsStyleFont* font = (const nsStyleFont*) + mStyleContext->GetStyleData(eStyleStruct_Font); + nsIFontMetrics* fm = aLineLayout.mPresContext->GetMetricsFor(font->mFont); + + fm->GetMaxAscent(aMetrics.ascent); + fm->GetMaxDescent(aMetrics.descent); + aMetrics.height = aMetrics.ascent + aMetrics.descent; + aMetrics.width = 0; + NS_RELEASE(fm); + + // Return our inline reflow status + const nsStyleDisplay* display = (const nsStyleDisplay*) + mStyleContext->GetStyleData(eStyleStruct_Display); + PRUint32 breakType = display->mBreakType; + if (NS_STYLE_CLEAR_NONE == breakType) { + breakType = NS_STYLE_CLEAR_LINE; + } + + return NS_INLINE_BREAK | NS_INLINE_BREAK_AFTER | + NS_INLINE_MAKE_BREAK_TYPE(breakType); +} diff --git a/mozilla/layout/html/base/src/nsHRFrame.cpp b/mozilla/layout/html/base/src/nsHRFrame.cpp new file mode 100644 index 00000000000..682a178c390 --- /dev/null +++ b/mozilla/layout/html/base/src/nsHRFrame.cpp @@ -0,0 +1,342 @@ +/* -*- 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.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. + */ +#include "nsHTMLParts.h" +#include "nsIHTMLContent.h" +#include "nsLeafFrame.h" +#include "nsIRenderingContext.h" +#include "nsGlobalVariables.h" +#include "nsIStyleContext.h" +#include "nsColor.h" +#include "nsIPresContext.h" +#include "nsIStyleContext.h" +#include "nsHTMLIIDs.h" +#include "nsHTMLAtoms.h" +#include "nsIFontMetrics.h" +#include "nsIHTMLAttributes.h" +#include "nsStyleConsts.h" +#include "nsCSSRendering.h" +#include "nsCSSLayout.h" +#include "nsIDOMHTMLHRElement.h" + +static NS_DEFINE_IID(kIDOMHTMLHRElementIID, NS_IDOMHTMLHRELEMENT_IID); + +// default hr thickness in pixels +#define DEFAULT_THICKNESS 3 + +class HRuleFrame : public nsLeafFrame { +public: + HRuleFrame(nsIContent* aContent, + nsIFrame* aParentFrame); + + NS_IMETHOD Reflow(nsIPresContext& aPresContext, + nsReflowMetrics& aDesiredSize, + const nsReflowState& aReflowState, + nsReflowStatus& aStatus); + NS_IMETHOD Paint(nsIPresContext& aPresContext, + nsIRenderingContext& aRenderingContext, + const nsRect& aDirtyRect); + +protected: + virtual ~HRuleFrame(); + + virtual void GetDesiredSize(nsIPresContext* aPresContext, + const nsReflowState& aReflowState, + nsReflowMetrics& aDesiredSize); + + PRBool GetNoShade(); + PRInt32 GetThickness(); + + nscoord mComputedWidth; +}; + +nsresult +NS_NewHRFrame(nsIContent* aContent, nsIFrame* aParentFrame, + nsIFrame*& aResult) +{ + nsIFrame* frame = new HRuleFrame(aContent, aParentFrame); + if (nsnull == frame) { + return NS_ERROR_OUT_OF_MEMORY; + } + aResult = frame; + return NS_OK; +} + +HRuleFrame::HRuleFrame(nsIContent* aContent, + nsIFrame* aParentFrame) + : nsLeafFrame(aContent, aParentFrame) +{ +} + +HRuleFrame::~HRuleFrame() +{ +} + +NS_METHOD +HRuleFrame::Paint(nsIPresContext& aPresContext, + nsIRenderingContext& aRenderingContext, + const nsRect& aDirtyRect) +{ + const nsStyleDisplay* disp = + (const nsStyleDisplay*)mStyleContext->GetStyleData(eStyleStruct_Display); + if (PR_FALSE == disp->mVisible) { + return NS_OK; + } + + float p2t = aPresContext.GetPixelsToTwips(); + nscoord thickness = NSIntPixelsToTwips(GetThickness(), p2t); + + // Get style data + const nsStyleSpacing* spacing = (const nsStyleSpacing*) + mStyleContext->GetStyleData(eStyleStruct_Spacing); + const nsStyleColor* color = (const nsStyleColor*) + mStyleContext->GetStyleData(eStyleStruct_Color); + nsMargin borderPadding; + spacing->CalcBorderPaddingFor(this, borderPadding); + nscoord x0 = borderPadding.left; + nscoord y0 = borderPadding.top; + nscoord width = mRect.width - + (borderPadding.left + borderPadding.right); + nscoord height = mRect.height - + (borderPadding.top + borderPadding.bottom); + + nscoord newWidth = mComputedWidth; + if (newWidth < width) { + // center or right align rule within the extra space + const nsStyleText* text = + (const nsStyleText*) mStyleContext->GetStyleData(eStyleStruct_Text); + switch (text->mTextAlign) { + case NS_STYLE_TEXT_ALIGN_RIGHT: + x0 += width - newWidth; + break; + + case NS_STYLE_TEXT_ALIGN_LEFT: + break; + + default: + case NS_STYLE_TEXT_ALIGN_CENTER: + x0 += (width - newWidth) / 2; + break; + } + } + width = newWidth; + + // Center hrule vertically within the available space + y0 += (height - thickness) / 2; + height = thickness; + + // To shade or not to shade, that is the question. Begin by collecting the + // three decision criteria: rendering to the printer or the display, is the + // "Beveled Lines" checkbox set in the page setup dialog, and does the tag + // have the NOSHADE attribute set. + PRBool printing = nsGlobalVariables::Instance()->GetPrinting(&aPresContext); + + PRBool bevel = nsGlobalVariables::Instance()->GetBeveledLines(); + + PRBool noShadeAttribute = GetNoShade(); + + // Now that we have the data to make the shading criteria, we next + // collect the decision criteria for rending in solid black: + // printing (which we already have) and the "Black Lines" setting in + // the page setup dialog + + PRBool blackLines = nsGlobalVariables::Instance()->GetBlackLines(); + nscolor colors[2]; + // Get the background color that applies to this HR + if (printing && blackLines) + { + colors[0] = NS_RGB(0,0,0); + colors[1] = colors[0]; + } + else + { + // XXX Get correct color by finding the first parent that actually + // specifies a color. + NS_Get3DColors(colors, color->mBackgroundColor); + } + + // Draw a "shadowed" box around the rule area + if (!noShadeAttribute && ((printing && bevel) || !printing)) { + // Lines render inclusively on the both the starting and ending + // coordinate, so reduce the end coordinates by one pixel. + nscoord x1 = nscoord(x0 + width - NSIntPixelsToTwips(1, p2t)); + nscoord y1 = nscoord(y0 + height - NSIntPixelsToTwips(1, p2t)); + + // Draw bottom and right lines + aRenderingContext.SetColor (colors[1]); + aRenderingContext.DrawLine (x1, y0, x1, y1); + aRenderingContext.DrawLine (x1, y1, x0, y1); + + // Draw top and left lines + aRenderingContext.SetColor (colors[0]); + aRenderingContext.DrawLine (x0, y1, x0, y0); + aRenderingContext.DrawLine (x0, y0, x1, y0); + } else { + // When a rule is not shaded, then we use a uniform color and + // draw half-circles on the end points. + aRenderingContext.SetColor (colors[0]); + nscoord diameter = height; + if ((diameter > width) || (diameter < NSIntPixelsToTwips(3, p2t))) { + // The half-circles on the ends of the rule aren't going to + // look right so don't bother drawing them. + aRenderingContext.FillRect(x0, y0, width, height); + } else { + aRenderingContext.FillArc(x0, y0, diameter, diameter, 90.0f, 270.0f); + aRenderingContext.FillArc(x0 + width - diameter, y0, + diameter, diameter, 270.0f, 180.0f); + aRenderingContext.FillRect(x0 + diameter/2, y0, + width - diameter, height); + } + } + return NS_OK; +} + +NS_IMETHODIMP +HRuleFrame::Reflow(nsIPresContext& aPresContext, + nsReflowMetrics& aDesiredSize, + const nsReflowState& aReflowState, + nsReflowStatus& aStatus) +{ + NS_PRECONDITION(mState & NS_FRAME_IN_REFLOW, "frame is not in reflow"); + + // XXX add in code to check for width/height being set via css + // and if set use them instead of calling GetDesiredSize. + + + GetDesiredSize(&aPresContext, aReflowState, aDesiredSize); + AddBordersAndPadding(&aPresContext, aDesiredSize); + + // HR's do not impact the max-element-size, unless a width is specified + // otherwise tables behave badly. This makes sense they are springy. + if (nsnull != aDesiredSize.maxElementSize) { + nscoord onePixel = NSIntPixelsToTwips(1, aPresContext.GetPixelsToTwips()); + nsSize size; + PRIntn ss = nsCSSLayout::GetStyleSize(&aPresContext, aReflowState, size); + if (NS_SIZE_HAS_WIDTH & ss) { + aDesiredSize.maxElementSize->width = size.width; + aDesiredSize.maxElementSize->height = onePixel; + } + else { + aDesiredSize.maxElementSize->width = onePixel; + aDesiredSize.maxElementSize->height = onePixel; + } + } + + aStatus = NS_FRAME_COMPLETE; + return NS_OK; +} + +void +HRuleFrame::GetDesiredSize(nsIPresContext* aPresContext, + const nsReflowState& aReflowState, + nsReflowMetrics& aDesiredSize) +{ + nsSize size; + PRIntn ss = nsCSSLayout::GetStyleSize(aPresContext, aReflowState, size); + if (NS_SIZE_HAS_WIDTH & ss) { + aDesiredSize.width = size.width; + } + else { + if (NS_UNCONSTRAINEDSIZE == aReflowState.maxSize.width) { + aDesiredSize.width = nscoord(aPresContext->GetPixelsToTwips()); + } + else { + aDesiredSize.width = aReflowState.maxSize.width; + } + } + mComputedWidth = aDesiredSize.width; + if (aReflowState.maxSize.width != NS_UNCONSTRAINEDSIZE) { + if (aDesiredSize.width < aReflowState.maxSize.width) { + aDesiredSize.width = aReflowState.maxSize.width; + } + } + + // XXX should we interpret css's height property as thickness? or as + // line-height? In the meantime, ignore it... + nscoord lineHeight; + + // Get the thickness of the rule (this is not css's height property) + float p2t = aPresContext->GetPixelsToTwips(); + nscoord thickness = NSIntPixelsToTwips(GetThickness(), p2t); + + // Compute height of "line" that hrule will layout within. Use the + // default font to do this. + lineHeight = thickness + NSIntPixelsToTwips(2, p2t); + const nsFont& defaultFont = aPresContext->GetDefaultFont(); + nsIFontMetrics* fm = aPresContext->GetMetricsFor(defaultFont); + nscoord defaultLineHeight; + fm->GetHeight(defaultLineHeight); + NS_RELEASE(fm); + if (lineHeight < defaultLineHeight) { + lineHeight = defaultLineHeight; + } + + aDesiredSize.height = lineHeight; + aDesiredSize.ascent = lineHeight; + aDesiredSize.descent = 0; +} + +PRInt32 +HRuleFrame::GetThickness() +{ + PRInt32 result = DEFAULT_THICKNESS; + + // See if the underlying content is an HR that also implements the + // nsIHTMLContent API. + // XXX the dependency on nsIHTMLContent is done to avoid reparsing + // the size value. + nsIDOMHTMLHRElement* hr = nsnull; + nsIHTMLContent* hc = nsnull; + mContent->QueryInterface(kIDOMHTMLHRElementIID, (void**) &hr); + mContent->QueryInterface(kIHTMLContentIID, (void**) &hc); + if ((nsnull != hr) && (nsnull != hc)) { + // Winner. Get the size attribute to determine how thick the HR + // should be. + nsHTMLValue value; + if (NS_CONTENT_ATTR_HAS_VALUE == + hc->GetAttribute(nsHTMLAtoms::size, value)) { + if (value.GetUnit() == eHTMLUnit_Pixel) { + PRInt32 pixels = value.GetPixelValue(); + switch (pixels) { + case 0: + case 1: + result = 1; + break; + default: + result = pixels + 1; + break; + } + } + } + } + NS_IF_RELEASE(hc); + NS_IF_RELEASE(hr); + return result; +} + +PRBool +HRuleFrame::GetNoShade() +{ + PRBool result = PR_FALSE; + nsIDOMHTMLHRElement* hr = nsnull; + mContent->QueryInterface(kIDOMHTMLHRElementIID, (void**) &hr); + if (nsnull != hr) { + hr->GetNoShade(&result); + NS_RELEASE(hr); + } + return result; +} diff --git a/mozilla/layout/html/base/src/nsHTMLFrame.cpp b/mozilla/layout/html/base/src/nsHTMLFrame.cpp new file mode 100644 index 00000000000..f460ff0c67f --- /dev/null +++ b/mozilla/layout/html/base/src/nsHTMLFrame.cpp @@ -0,0 +1,626 @@ +/* -*- 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.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. + */ +#include "nsHTMLParts.h" +#include "nsHTMLContainer.h" +#include "nsContainerFrame.h" +#include "nsIDocument.h" +#include "nsIReflowCommand.h" +#include "nsIPresContext.h" +#include "nsIStyleContext.h" +#include "nsViewsCID.h" +#include "nsIView.h" +#include "nsIViewManager.h" +#include "nsIContentDelegate.h" +#include "nsIWidget.h" +#include "nsHTMLIIDs.h" +#include "nsPageFrame.h" +#include "nsIRenderingContext.h" +#include "nsGUIEvent.h" +#include "nsDOMEvent.h" +#include "nsStyleConsts.h" +#include "nsIViewManager.h" +#include "nsHTMLAtoms.h" +#include "nsIEventStateManager.h" +#include "nsIDeviceContext.h" + +class RootFrame : public nsContainerFrame { +public: + RootFrame(nsIContent* aContent); + + NS_IMETHOD Reflow(nsIPresContext& aPresContext, + nsReflowMetrics& aDesiredSize, + const nsReflowState& aReflowState, + nsReflowStatus& aStatus); + + NS_IMETHOD HandleEvent(nsIPresContext& aPresContext, + nsGUIEvent* aEvent, + nsEventStatus& aEventStatus); +}; + +// Pseudo frame created by the root frame +class RootContentFrame : public nsContainerFrame { +public: + RootContentFrame(nsIContent* aContent, nsIFrame* aParent); + + NS_IMETHOD Reflow(nsIPresContext& aPresContext, + nsReflowMetrics& aDesiredSize, + const nsReflowState& aReflowState, + nsReflowStatus& aStatus); + + NS_IMETHOD Paint(nsIPresContext& aPresContext, + nsIRenderingContext& aRenderingContext, + const nsRect& aDirtyRect); + + NS_IMETHOD HandleEvent(nsIPresContext& aPresContext, + nsGUIEvent* aEvent, + nsEventStatus& aEventStatus); + +protected: + void CreateFirstChild(nsIPresContext* aPresContext); +}; + +//---------------------------------------------------------------------- + +nsresult +NS_NewHTMLFrame(nsIContent* aContent, nsIFrame* aParentFrame, + nsIFrame*& aResult) +{ + RootFrame* frame = new RootFrame(aContent); + if (nsnull == frame) { + return NS_ERROR_OUT_OF_MEMORY; + } + aResult = frame; + return NS_OK; +} + +RootFrame::RootFrame(nsIContent* aContent) + : nsContainerFrame(aContent, nsnull) +{ +} + +NS_IMETHODIMP +RootFrame::Reflow(nsIPresContext& aPresContext, + nsReflowMetrics& aDesiredSize, + const nsReflowState& aReflowState, + nsReflowStatus& aStatus) +{ + NS_FRAME_TRACE_REFLOW_IN("RootFrame::Reflow"); + +#ifdef NS_DEBUG + PreReflowCheck(); +#endif + + aStatus = NS_FRAME_COMPLETE; + + if (eReflowReason_Incremental == aReflowState.reason) { + // We don't expect the target of the reflow command to be the root frame +#ifdef NS_DEBUG + NS_ASSERTION(nsnull != aReflowState.reflowCommand, "no reflow command"); + + nsIFrame* target; + aReflowState.reflowCommand->GetTarget(target); + NS_ASSERTION(target != this, "root frame is reflow command target"); +#endif + + // Verify that the next frame in the reflow chain is our pseudo frame + nsIFrame* next; + aReflowState.reflowCommand->GetNext(next); + NS_ASSERTION(next == mFirstChild, "unexpected next reflow command frame"); + + } else { + // Do we have any children? + if (nsnull == mFirstChild) { + // No. Create a pseudo frame + NS_ASSERTION(eReflowReason_Initial == aReflowState.reason, "unexpected reflow reason"); + mFirstChild = new RootContentFrame(mContent, this); + mChildCount = 1; + nsIStyleContext* style = aPresContext.ResolvePseudoStyleContextFor(nsHTMLAtoms::rootContentPseudo, this); + mFirstChild->SetStyleContext(&aPresContext,style); + NS_RELEASE(style); + } + } + + // Reflow our pseudo frame. It will choose whetever height its child frame + // wants + if (nsnull != mFirstChild) { + nsReflowMetrics desiredSize(nsnull); + nsReflowState kidReflowState(mFirstChild, aReflowState, aReflowState.maxSize); + + mFirstChild->WillReflow(aPresContext); + aStatus = ReflowChild(mFirstChild, &aPresContext, desiredSize, kidReflowState); + + // Place and size the child + nsRect rect(0, 0, desiredSize.width, desiredSize.height); + mFirstChild->SetRect(rect); + mFirstChild->DidReflow(aPresContext, NS_FRAME_REFLOW_FINISHED); + + mLastContentOffset = ((RootContentFrame*)mFirstChild)->GetLastContentOffset(); + } + + // Return the max size as our desired size + aDesiredSize.width = aReflowState.maxSize.width; + aDesiredSize.height = aReflowState.maxSize.height; + aDesiredSize.ascent = aDesiredSize.height; + aDesiredSize.descent = 0; + +#ifdef NS_DEBUG + PostReflowCheck(aStatus); +#endif + NS_FRAME_TRACE_REFLOW_OUT("RootFrame::Reflow", aStatus); + return NS_OK; +} + +NS_IMETHODIMP +RootFrame::HandleEvent(nsIPresContext& aPresContext, + nsGUIEvent* aEvent, + nsEventStatus& aEventStatus) +{ + mContent->HandleDOMEvent(aPresContext, (nsEvent*)aEvent, nsnull, DOM_EVENT_INIT, aEventStatus); + + if (aEvent->message == NS_MOUSE_LEFT_BUTTON_UP || + aEvent->message == NS_MOUSE_MIDDLE_BUTTON_UP || + aEvent->message == NS_MOUSE_RIGHT_BUTTON_UP) { + nsFrame::HandleEvent(aPresContext, aEvent, aEventStatus); + } + +#if 0 + if (aEventStatus != nsEventStatus_eConsumeNoDefault) { + switch (aEvent->message) { + case NS_MOUSE_MOVE: + case NS_MOUSE_ENTER: + { + nsIFrame* target = this; + PRInt32 cursor; + + GetCursorAndContentAt(aPresContext, aEvent->point, &target, &mContent, cursor); + if (cursor == NS_STYLE_CURSOR_INHERIT) { + cursor = NS_STYLE_CURSOR_DEFAULT; + } + nsCursor c; + switch (cursor) { + default: + case NS_STYLE_CURSOR_DEFAULT: + c = eCursor_standard; + break; + case NS_STYLE_CURSOR_HAND: + c = eCursor_hyperlink; + break; + case NS_STYLE_CURSOR_IBEAM: + c = eCursor_select; + break; + } + nsIWidget* window; + target->GetWindow(window); + window->SetCursor(c); + NS_RELEASE(window); + } + break; + } + } +#endif + return NS_OK; +} + +//---------------------------------------------------------------------- + +RootContentFrame::RootContentFrame(nsIContent* aContent, nsIFrame* aParent) + : nsContainerFrame(aContent, aParent) +{ + // Create a view + nsIFrame* parent; + nsIView* view; + + GetParentWithView(parent); + NS_ASSERTION(parent, "GetParentWithView failed"); + nsIView* parView; + + parent->GetView(parView); + NS_ASSERTION(parView, "no parent with view"); + + // Create a view + static NS_DEFINE_IID(kViewCID, NS_VIEW_CID); + static NS_DEFINE_IID(kIViewIID, NS_IVIEW_IID); + + nsresult result = nsRepository::CreateInstance(kViewCID, + nsnull, + kIViewIID, + (void **)&view); + if (NS_OK == result) { + nsIView* rootView = parView; + nsIViewManager* viewManager; + rootView->GetViewManager(viewManager); + + // Initialize the view + NS_ASSERTION(nsnull != viewManager, "null view manager"); + + view->Init(viewManager, mRect, rootView); + viewManager->InsertChild(rootView, view, 0); + + NS_RELEASE(viewManager); + + // Remember our view + SetView(view); + } +} + +void +RootContentFrame::CreateFirstChild(nsIPresContext* aPresContext) +{ + // Are we paginated? + if (aPresContext->IsPaginated()) { + // Yes. Create the first page frame + mFirstChild = new nsPageFrame(mContent, this); + mChildCount = 1; + mLastContentOffset = mFirstContentOffset; + + } else { + // Create a frame for the body/frameset child + PRInt32 i, n; + mContent->ChildCount(n); + for (i = 0; i < n; i++) { + nsIContent* child; + mContent->ChildAt(i, child); + if (nsnull != child) { + nsIAtom* tag; + child->GetTag(tag); + if ((nsHTMLAtoms::body == tag) || (nsHTMLAtoms::frameset == tag)) { + // Create a frame + nsIContentDelegate* cd = child->GetDelegate(aPresContext); + if (nsnull != cd) { + nsIStyleContext* kidStyleContext = + aPresContext->ResolveStyleContextFor(child, this); + nsresult rv = cd->CreateFrame(aPresContext, child, this, + kidStyleContext, mFirstChild); + NS_RELEASE(kidStyleContext); + if (NS_OK == rv) { + mChildCount = 1; + mFirstContentOffset = i; + mLastContentOffset = i; + } + NS_RELEASE(cd); + } + } + NS_IF_RELEASE(tag); + NS_RELEASE(child); + } + } + } +} + +// XXX Hack +#define PAGE_SPACING_TWIPS 100 + +NS_IMETHODIMP +RootContentFrame::Reflow(nsIPresContext& aPresContext, + nsReflowMetrics& aDesiredSize, + const nsReflowState& aReflowState, + nsReflowStatus& aStatus) +{ + NS_FRAME_TRACE_REFLOW_IN("RootContentFrame::Reflow"); +#ifdef NS_DEBUG + PreReflowCheck(); +#endif + + aStatus = NS_FRAME_COMPLETE; + + // XXX Incremental reflow code doesn't handle page mode at all... + if (eReflowReason_Incremental == aReflowState.reason) { + // We don't expect the target of the reflow command to be the root + // content frame +#ifdef NS_DEBUG + nsIFrame* target; + aReflowState.reflowCommand->GetTarget(target); + NS_ASSERTION(target != this, "root content frame is reflow command target"); +#endif + + // Verify the next frame in the reflow chain is our child frame + nsIFrame* next; + aReflowState.reflowCommand->GetNext(next); + NS_ASSERTION(next == mFirstChild, "unexpected next reflow command frame"); + + nsSize maxSize(aReflowState.maxSize.width, NS_UNCONSTRAINEDSIZE); + nsReflowState kidReflowState(next, aReflowState, maxSize); + + // Dispatch the reflow command to our child frame. Allow it to be as high + // as it wants + mFirstChild->WillReflow(aPresContext); + aStatus = ReflowChild(mFirstChild, &aPresContext, aDesiredSize, kidReflowState); + + // Place and size the child. Make sure the child is at least as + // tall as our max size (the containing window) + if (aDesiredSize.height < aReflowState.maxSize.height) { + aDesiredSize.height = aReflowState.maxSize.height; + } + + nsRect rect(0, 0, aDesiredSize.width, aDesiredSize.height); + mFirstChild->SetRect(rect); + + } else { + nsReflowReason reflowReason = aReflowState.reason; + + // Do we have any children? + if (nsnull == mFirstChild) { + // No, create the first child frame + reflowReason = eReflowReason_Initial; + CreateFirstChild(&aPresContext); + } + + // Resize our frames + if (nsnull != mFirstChild) { + if (aPresContext.IsPaginated()) { + nscoord y = PAGE_SPACING_TWIPS; + nsReflowMetrics kidSize(aDesiredSize.maxElementSize); + + // Compute the size of each page and the x coordinate within + // ourselves that the pages will be placed at. + nsSize pageSize(aPresContext.GetPageWidth(), + aPresContext.GetPageHeight()); + nsIDeviceContext *dx = aPresContext.GetDeviceContext(); + float sbWidth, sbHeight; + dx->GetScrollBarDimensions(sbWidth, sbHeight); + PRInt32 extra = aReflowState.maxSize.width - PAGE_SPACING_TWIPS*2 - + pageSize.width - NSToCoordRound(sbWidth); + NS_RELEASE(dx); + + // Note: nscoord is an unsigned type so don't combine these + // two statements or the extra will be promoted to unsigned + // and the >0 won't work! + nscoord x = PAGE_SPACING_TWIPS; + if (extra > 0) { + x += extra / 2; + } + + // Tile the pages vertically + for (nsIFrame* kidFrame = mFirstChild; nsnull != kidFrame; ) { + // Reflow the page + nsReflowState kidReflowState(kidFrame, aReflowState, pageSize, + reflowReason); + nsReflowStatus status; + + // Place and size the page. If the page is narrower than our + // max width then center it horizontally + kidFrame->WillReflow(aPresContext); + kidFrame->MoveTo(x, y); + status = ReflowChild(kidFrame, &aPresContext, kidSize, + kidReflowState); + kidFrame->SetRect(nsRect(x, y, kidSize.width, kidSize.height)); + y += kidSize.height; + + // Leave a slight gap between the pages + y += PAGE_SPACING_TWIPS; + + // Is the page complete? + nsIFrame* kidNextInFlow; + + kidFrame->GetNextInFlow(kidNextInFlow); + if (NS_FRAME_IS_COMPLETE(status)) { + NS_ASSERTION(nsnull == kidNextInFlow, "bad child flow list"); + } else if (nsnull == kidNextInFlow) { + // The page isn't complete and it doesn't have a next-in-flow so + // create a continuing page + nsIStyleContext* kidSC; + kidFrame->GetStyleContext(&aPresContext, kidSC); + nsIFrame* continuingPage; + nsresult rv = kidFrame->CreateContinuingFrame(aPresContext, this, + kidSC, continuingPage); + NS_RELEASE(kidSC); + reflowReason = eReflowReason_Initial; + + // Add it to our child list +#ifdef NS_DEBUG + nsIFrame* kidNextSibling; + kidFrame->GetNextSibling(kidNextSibling); + NS_ASSERTION(nsnull == kidNextSibling, "unexpected sibling"); +#endif + kidFrame->SetNextSibling(continuingPage); + mChildCount++; + } + + // Get the next page + kidFrame->GetNextSibling(kidFrame); + } + + // Return our desired size + aDesiredSize.height = y; + if (aDesiredSize.height < aReflowState.maxSize.height) { + aDesiredSize.height = aReflowState.maxSize.height; + } + aDesiredSize.width = PAGE_SPACING_TWIPS*2 + pageSize.width; + if (aDesiredSize.width < aReflowState.maxSize.width) { + aDesiredSize.width = aReflowState.maxSize.width; + } + + } else { + // Allow the frame to be as wide as our max width, and as high + // as it wants to be. + nsSize maxSize(aReflowState.maxSize.width, NS_UNCONSTRAINEDSIZE); + nsReflowState kidReflowState(mFirstChild, aReflowState, maxSize, reflowReason); + + // Get the child's desired size. Our child's desired height is our + // desired size + mFirstChild->WillReflow(aPresContext); + aStatus = ReflowChild(mFirstChild, &aPresContext, aDesiredSize, kidReflowState); + NS_ASSERTION(NS_FRAME_IS_COMPLETE(aStatus), "bad status"); + + // Place and size the child. Make sure the child is at least as + // tall as our max size (the containing window) + if (aDesiredSize.height < aReflowState.maxSize.height) { + aDesiredSize.height = aReflowState.maxSize.height; + } + + // Place and size the child + nsRect rect(0, 0, aDesiredSize.width, aDesiredSize.height); + mFirstChild->SetRect(rect); + + // Do the necessary repainting + if (eReflowReason_Initial == reflowReason) { + // Repaint the visible area + Invalidate(nsRect(0, 0, aReflowState.maxSize.width, aReflowState.maxSize.height)); + } else if (eReflowReason_Resize == aReflowState.reason) { + // Repaint the entire frame + Invalidate(nsRect(0, 0, aReflowState.maxSize.width, aDesiredSize.height)); + } + } + } + else { + aDesiredSize.width = aReflowState.maxSize.width; + aDesiredSize.height = aReflowState.maxSize.height; + aDesiredSize.ascent = aDesiredSize.height; + aDesiredSize.descent = 0; + if (nsnull != aDesiredSize.maxElementSize) { + aDesiredSize.maxElementSize->width = 0; + aDesiredSize.maxElementSize->height = 0; + } + } + } + + // We are always a pseudo-frame; make sure our content offset is + // properly pushed upwards + nsContainerFrame* parent = (nsContainerFrame*) mGeometricParent; + parent->PropagateContentOffsets(this, mFirstContentOffset, + mLastContentOffset, mLastContentIsComplete); + +#ifdef NS_DEBUG + PostReflowCheck(aStatus); +#endif + NS_FRAME_TRACE_REFLOW_OUT("RootContentFrame::Reflow", aStatus); + return NS_OK; +} + +NS_IMETHODIMP +RootContentFrame::Paint(nsIPresContext& aPresContext, + nsIRenderingContext& aRenderingContext, + const nsRect& aDirtyRect) +{ + // If we're paginated then fill the dirty rect with white + if (aPresContext.IsPaginated()) { + // Cross hatching would be nicer... + aRenderingContext.SetColor(NS_RGB(255,255,255)); + aRenderingContext.FillRect(aDirtyRect); + } + + nsContainerFrame::Paint(aPresContext, aRenderingContext, aDirtyRect); + return NS_OK; +} + +NS_IMETHODIMP +RootContentFrame::HandleEvent(nsIPresContext& aPresContext, + nsGUIEvent* aEvent, + nsEventStatus& aEventStatus) +{ +#if 0 + mContent->HandleDOMEvent(aPresContext, (nsEvent*)aEvent, nsnull, DOM_EVENT_INIT, aEventStatus); +#else + nsContainerFrame::HandleEvent(aPresContext, aEvent, aEventStatus); +#endif + + if (aEventStatus != nsEventStatus_eConsumeNoDefault) { + switch (aEvent->message) { + case NS_MOUSE_MOVE: + case NS_MOUSE_ENTER: + { + nsIFrame* target = this; + nsIContent* mTargetContent = mContent; + PRInt32 cursor; + + GetCursorAndContentAt(aPresContext, aEvent->point, &target, &mTargetContent, cursor); + if (cursor == NS_STYLE_CURSOR_INHERIT) { + cursor = NS_STYLE_CURSOR_DEFAULT; + } + nsCursor c; + switch (cursor) { + default: + case NS_STYLE_CURSOR_DEFAULT: + c = eCursor_standard; + break; + case NS_STYLE_CURSOR_HAND: + c = eCursor_hyperlink; + break; + case NS_STYLE_CURSOR_IBEAM: + c = eCursor_select; + break; + } + nsIWidget* window; + target->GetWindow(window); + window->SetCursor(c); + NS_RELEASE(window); + + //If the content object under the cursor has changed, fire a mouseover/out + nsIEventStateManager *mStateManager; + nsIContent *mLastContent; + if (NS_OK == aPresContext.GetEventStateManager(&mStateManager)) { + mStateManager->GetLastMouseOverContent(&mLastContent); + if (mLastContent != mTargetContent) { + if (nsnull != mLastContent) { + //fire mouseout + nsEventStatus mStatus = nsEventStatus_eIgnore; + nsMouseEvent mEvent; + mEvent.eventStructType = NS_MOUSE_EVENT; + mEvent.message = NS_MOUSE_EXIT; + mLastContent->HandleDOMEvent(aPresContext, &mEvent, nsnull, DOM_EVENT_INIT, mStatus); + } + //fire mouseover + nsEventStatus mStatus = nsEventStatus_eIgnore; + nsMouseEvent mEvent; + mEvent.eventStructType = NS_MOUSE_EVENT; + mEvent.message = NS_MOUSE_ENTER; + mTargetContent->HandleDOMEvent(aPresContext, &mEvent, nsnull, DOM_EVENT_INIT, mStatus); + mStateManager->SetLastMouseOverContent(mTargetContent); + } + NS_RELEASE(mStateManager); + NS_IF_RELEASE(mLastContent); + } + } + break; + case NS_MOUSE_EXIT: + //Don't know if this is actually hooked up. + { + //Fire of mouseout to the last content object. + nsIEventStateManager *mStateManager; + nsIContent *mLastContent; + if (NS_OK == aPresContext.GetEventStateManager(&mStateManager)) { + mStateManager->GetLastMouseOverContent(&mLastContent); + if (nsnull != mLastContent) { + //fire mouseout + nsEventStatus mStatus = nsEventStatus_eIgnore; + nsMouseEvent mEvent; + mEvent.eventStructType = NS_MOUSE_EVENT; + mEvent.message = NS_MOUSE_EXIT; + mLastContent->HandleDOMEvent(aPresContext, &mEvent, nsnull, DOM_EVENT_INIT, mStatus); + mStateManager->SetLastMouseOverContent(nsnull); + + NS_RELEASE(mLastContent); + } + NS_RELEASE(mStateManager); + } + } + break; + case NS_MOUSE_LEFT_BUTTON_UP: + { + nsIEventStateManager *mStateManager; + if (NS_OK == aPresContext.GetEventStateManager(&mStateManager)) { + mStateManager->SetActiveLink(nsnull); + NS_RELEASE(mStateManager); + } + } + break; + } + } + + return NS_OK; +} diff --git a/mozilla/layout/html/base/src/nsImageFrame.cpp b/mozilla/layout/html/base/src/nsImageFrame.cpp new file mode 100644 index 00000000000..0c1a79435eb --- /dev/null +++ b/mozilla/layout/html/base/src/nsImageFrame.cpp @@ -0,0 +1,928 @@ +/* -*- 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.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. + */ +#include "nsHTMLParts.h" +#include "nsHTMLImage.h" +#include "nsHTMLTagContent.h" +#include "nsString.h" +#include "nsLeafFrame.h" +#include "nsIPresContext.h" +#include "nsIRenderingContext.h" +#include "nsIFrameImageLoader.h" +#include "nsIPresShell.h" +#include "nsHTMLIIDs.h" +#include "nsIImage.h" +#include "nsIWidget.h" +#include "nsHTMLAtoms.h" +#include "nsIHTMLAttributes.h" +#include "nsIDocument.h" +#include "nsIHTMLDocument.h" +#include "nsIStyleContext.h" +#include "nsStyleConsts.h" +#include "nsIImageMap.h" +#include "nsILinkHandler.h" +#include "nsIURL.h" +#include "nsIView.h" +#include "nsIViewManager.h" +#include "nsCSSLayout.h" +#include "nsHTMLBase.h" +#include "prprf.h" +#include "nsISizeOfHandler.h" +#include "nsIFontMetrics.h" +#include "nsCSSRendering.h" +#include "nsIDOMHTMLImageElement.h" + +#define BROKEN_IMAGE_URL "resource:/res/html/broken-image.gif" + +#define XP_IS_SPACE(_ch) \ + (((_ch) == ' ') || ((_ch) == '\t') || ((_ch) == '\n')) + +// XXX image frame layout can be 100% decoupled from the content +// object; all it needs are attributes to work properly + +static NS_DEFINE_IID(kIHTMLDocumentIID, NS_IHTMLDOCUMENT_IID); + +#if 0 +#define nsHTMLImageSuper nsHTMLTagContent +class nsHTMLImage : public nsHTMLImageSuper, public nsIDOMHTMLImageElement { +public: + nsHTMLImage(nsIAtom* aTag); + + NS_DECL_ISUPPORTS + + NS_IMETHOD SizeOf(nsISizeOfHandler* aHandler) const; + NS_IMETHOD CreateFrame(nsIPresContext* aPresContext, + nsIFrame* aParentFrame, + nsIStyleContext* aStyleContext, + nsIFrame*& aResult); + + NS_IMETHOD SetAttribute(nsIAtom* aAttribute, const nsString& aValue, + PRBool aNotify); + NS_IMETHOD MapAttributesInto(nsIStyleContext* aContext, + nsIPresContext* aPresContext); + NS_IMETHOD AttributeToString(nsIAtom* aAttribute, + nsHTMLValue& aValue, + nsString& aResult) const; + + NS_FORWARD_IDOMNODE(nsHTMLImageSuper::) + NS_FORWARD_IDOMELEMENT(nsHTMLImageSuper::) + NS_FORWARD_IDOMHTMLELEMENT(nsHTMLImageSuper::) + + NS_DECL_IDOMHTMLIMAGEELEMENT + + NS_IMETHOD GetScriptObject(nsIScriptContext *aContext, void** aScriptObject); + +protected: + virtual ~nsHTMLImage(); + void SizeOfWithoutThis(nsISizeOfHandler* aHandler) const; + + void TriggerReflow(); +}; +#endif + +#define ImageFrameSuper nsLeafFrame +class ImageFrame : public ImageFrameSuper { +public: + ImageFrame(nsIContent* aContent, nsIFrame* aParentFrame); + + NS_IMETHOD DeleteFrame(nsIPresContext& aPresContext); + NS_IMETHOD SizeOf(nsISizeOfHandler* aHandler) const; + NS_IMETHOD Paint(nsIPresContext& aPresContext, + nsIRenderingContext& aRenderingContext, + const nsRect& aDirtyRect); + NS_METHOD HandleEvent(nsIPresContext& aPresContext, + nsGUIEvent* aEvent, + nsEventStatus& aEventStatus); + NS_IMETHOD GetCursorAndContentAt(nsIPresContext& aPresContext, + const nsPoint& aPoint, + nsIFrame** aFrame, + nsIContent** aContent, + PRInt32& aCursor); + NS_IMETHOD ContentChanged(nsIPresShell* aShell, + nsIPresContext* aPresContext, + nsIContent* aChild, + nsISupports* aSubContent); + +protected: + virtual ~ImageFrame(); + void SizeOfWithoutThis(nsISizeOfHandler* aHandler) const; + + virtual void GetDesiredSize(nsIPresContext* aPresContext, + const nsReflowState& aReflowState, + nsReflowMetrics& aDesiredSize); + + nsIImageMap* GetImageMap(); + + nsHTMLImageLoader mImageLoader; + nsIImageMap* mImageMap; + PRBool mSizeFrozen; + + void TriggerLink(nsIPresContext& aPresContext, + const nsString& aURLSpec, + const nsString& aTargetSpec, + PRBool aClick); + + PRBool IsServerImageMap(); + PRIntn GetSuppress(); + + nscoord MeasureString(nsIFontMetrics* aFontMetrics, + const PRUnichar* aString, + PRInt32 aLength, + nscoord aMaxWidth, + PRUint32& aMaxFit); + + void DisplayAltText(nsIPresContext& aPresContext, + nsIRenderingContext& aRenderingContext, + const nsString& aAltText, + const nsRect& aRect); +}; + +// Value's for mSuppress +#define SUPPRESS_UNSET 0 +#define DONT_SUPPRESS 1 +#define SUPPRESS 2 +#define DEFAULT_SUPPRESS 3 + +// Default alignment value (so we can tell an unset value from a set value) +#define ALIGN_UNSET PRUint8(-1) + +//---------------------------------------------------------------------- + +nsHTMLImageLoader::nsHTMLImageLoader() +{ + mImageLoader = nsnull; + mLoadImageFailed = PR_FALSE; + mLoadBrokenImageFailed = PR_FALSE; + mURLSpec = nsnull; + mBaseHREF = nsnull; +} + +nsHTMLImageLoader::~nsHTMLImageLoader() +{ + NS_IF_RELEASE(mImageLoader); + if (nsnull != mURLSpec) { + delete mURLSpec; + } + if (nsnull != mBaseHREF) { + delete mBaseHREF; + } +} + +void +nsHTMLImageLoader::SizeOf(nsISizeOfHandler* aHandler) const +{ + aHandler->Add(sizeof(*this)); + if (!aHandler->HaveSeen(mURLSpec)) { + mURLSpec->SizeOf(aHandler); + } + if (!aHandler->HaveSeen(mImageLoader)) { + mImageLoader->SizeOf(aHandler); + } +} + +nsIImage* +nsHTMLImageLoader::GetImage() +{ + nsIImage* image = nsnull; + if (nsnull != mImageLoader) { + mImageLoader->GetImage(image); + } + return image; +} + +nsresult +nsHTMLImageLoader::SetURL(const nsString& aURLSpec) +{ + if (nsnull != mURLSpec) { + delete mURLSpec; + } + mURLSpec = new nsString(aURLSpec); + if (nsnull == mURLSpec) { + return NS_ERROR_OUT_OF_MEMORY; + } + return NS_OK; +} + +nsresult +nsHTMLImageLoader::SetBaseHREF(const nsString& aBaseHREF) +{ + if (nsnull != mBaseHREF) { + delete mBaseHREF; + } + mBaseHREF = new nsString(aBaseHREF); + if (nsnull == mBaseHREF) { + return NS_ERROR_OUT_OF_MEMORY; + } + return NS_OK; +} + +nsresult +nsHTMLImageLoader::StartLoadImage(nsIPresContext* aPresContext, + nsIFrame* aForFrame, + PRBool aNeedSizeUpdate, + PRIntn& aLoadStatus) +{ + aLoadStatus = NS_IMAGE_LOAD_STATUS_NONE; + + // Get absolute url the first time through + nsresult rv; + nsAutoString src; + if (mLoadImageFailed || (nsnull == mURLSpec)) { + src.Append(BROKEN_IMAGE_URL); + } else { + nsAutoString baseURL; + if (nsnull != mBaseHREF) { + baseURL = *mBaseHREF; + } + + // Get documentURL + nsIPresShell* shell; + shell = aPresContext->GetShell(); + nsIDocument* doc = shell->GetDocument(); + nsIURL* docURL = doc->GetDocumentURL(); + + // Create an absolute URL + nsresult rv = NS_MakeAbsoluteURL(docURL, baseURL, *mURLSpec, src); + + // Release references + NS_RELEASE(shell); + NS_RELEASE(docURL); + NS_RELEASE(doc); + if (NS_OK != rv) { + return rv; + } + } + + if (nsnull == mImageLoader) { + // Start image loading. Note that we don't specify a background color + // so transparent images are always rendered using a transparency mask + rv = aPresContext->StartLoadImage(src, nsnull, aForFrame, aNeedSizeUpdate, + mImageLoader); + if (NS_OK != rv) { + return rv; + } + } + + // Examine current image load status + mImageLoader->GetImageLoadStatus(aLoadStatus); + if (0 != (aLoadStatus & NS_IMAGE_LOAD_STATUS_ERROR)) { + NS_RELEASE(mImageLoader); + if (mLoadImageFailed) { + // We are doomed. Loading the broken image has just failed. + mLoadBrokenImageFailed = PR_TRUE; + } + else { + // Try again, this time using the broke-image url + mLoadImageFailed = PR_TRUE; + return StartLoadImage(aPresContext, aForFrame, aNeedSizeUpdate, aLoadStatus); + } + } + return NS_OK; +} + +void +nsHTMLImageLoader::GetDesiredSize(nsIPresContext* aPresContext, + const nsReflowState& aReflowState, + nsReflowMetrics& aDesiredSize) +{ + nsSize styleSize; + PRIntn ss = nsCSSLayout::GetStyleSize(aPresContext, aReflowState, styleSize); + PRIntn loadStatus; + if (0 != ss) { + if (NS_SIZE_HAS_BOTH == ss) { + StartLoadImage(aPresContext, aReflowState.frame, PR_FALSE, loadStatus); + aDesiredSize.width = styleSize.width; + aDesiredSize.height = styleSize.height; + } + else { + // Preserve aspect ratio of image with unbound dimension. + StartLoadImage(aPresContext, aReflowState.frame, PR_TRUE, loadStatus); + if ((0 == (loadStatus & NS_IMAGE_LOAD_STATUS_SIZE_AVAILABLE)) || + (nsnull == mImageLoader)) { + // Provide a dummy size for now; later on when the image size + // shows up we will reflow to the new size. + aDesiredSize.width = 1; + aDesiredSize.height = 1; + } + else { + float p2t = aPresContext->GetPixelsToTwips(); + nsSize imageSize; + mImageLoader->GetSize(imageSize); + float imageWidth = imageSize.width * p2t; + float imageHeight = imageSize.height * p2t; + if (0.0f != imageHeight) { + if (0 != (ss & NS_SIZE_HAS_WIDTH)) { + // We have a width, and an auto height. Compute height + // from width. + aDesiredSize.width = styleSize.width; + aDesiredSize.height = + (nscoord)NSToIntRound(styleSize.width * imageHeight / imageWidth); + } + else { + // We have a height and an auto width. Compute width from + // height. + aDesiredSize.height = styleSize.height; + aDesiredSize.width = + (nscoord)NSToIntRound(styleSize.height * imageWidth / imageHeight); + } + } + else { + // Screwy image + aDesiredSize.width = 1; + aDesiredSize.height = 1; + } + } + } + } + else { + StartLoadImage(aPresContext, aReflowState.frame, PR_TRUE, loadStatus); + if ((0 == (loadStatus & NS_IMAGE_LOAD_STATUS_SIZE_AVAILABLE)) || + (nsnull == mImageLoader)) { + // Provide a dummy size for now; later on when the image size + // shows up we will reflow to the new size. + aDesiredSize.width = 1; + aDesiredSize.height = 1; + printf ("in image loader, dummy size of 1 returned\n"); + } else { + float p2t = aPresContext->GetPixelsToTwips(); + nsSize imageSize; + mImageLoader->GetSize(imageSize); + aDesiredSize.width = NSIntPixelsToTwips(imageSize.width, p2t); + aDesiredSize.height = NSIntPixelsToTwips(imageSize.height, p2t); + printf ("in image loader, real size of %d returned\n", aDesiredSize.width); + } + } +} + +//---------------------------------------------------------------------- + +nsresult +NS_NewImageFrame(nsIContent* aContent, + nsIFrame* aParentFrame, + nsIFrame*& aResult) +{ + ImageFrame* frame = new ImageFrame(aContent, aParentFrame); + if (nsnull == frame) { + return NS_ERROR_OUT_OF_MEMORY; + } + aResult = frame; + return NS_OK; +} + +ImageFrame::ImageFrame(nsIContent* aContent, nsIFrame* aParentFrame) + : nsLeafFrame(aContent, aParentFrame) +{ +} + +ImageFrame::~ImageFrame() +{ +} + +NS_METHOD +ImageFrame::DeleteFrame(nsIPresContext& aPresContext) +{ + NS_IF_RELEASE(mImageMap); + + // Release image loader first so that it's refcnt can go to zero + mImageLoader.DestroyLoader(); + + return nsLeafFrame::DeleteFrame(aPresContext); +} + +NS_IMETHODIMP +ImageFrame::SizeOf(nsISizeOfHandler* aHandler) const +{ + aHandler->Add(sizeof(*this)); + ImageFrame::SizeOfWithoutThis(aHandler); + return NS_OK; +} + +void +ImageFrame::SizeOfWithoutThis(nsISizeOfHandler* aHandler) const +{ + ImageFrameSuper::SizeOfWithoutThis(aHandler); + mImageLoader.SizeOf(aHandler); + if (!aHandler->HaveSeen(mImageMap)) { + mImageMap->SizeOf(aHandler); + } +} + +void +ImageFrame::GetDesiredSize(nsIPresContext* aPresContext, + const nsReflowState& aReflowState, + nsReflowMetrics& aDesiredSize) +{ + if (mSizeFrozen) { + aDesiredSize.width = mRect.width; + aDesiredSize.height = mRect.height; + } + else { + // XXX Don't create a view, because we want whatever is below the image + // to show through while the image is loading; Likewise for transparent + // images and broken images + // + // What we really want to do is to create a view, and indicate that the + // view has a transparent content area. Do this while it's loading, + // and then when it's fully loaded mark the view as opaque if the + // image is opaque. + // + // We can't use that approach yet, because currently the compositor doesn't + // support transparent views... +#if 0 + nsHTMLBase::CreateViewForFrame(aPresContext, this, mStyleContext, PR_TRUE); +#endif + + // Setup url before starting the image load + nsAutoString src, base; + if (NS_CONTENT_ATTR_HAS_VALUE == mContent->GetAttribute("SRC", src)) { + mImageLoader.SetURL(src); + if (NS_CONTENT_ATTR_HAS_VALUE == + mContent->GetAttribute(NS_HTML_BASE_HREF, base)) { + mImageLoader.SetBaseHREF(base); + } + } + mImageLoader.GetDesiredSize(aPresContext, aReflowState, aDesiredSize); + } +} + +// Computes the width of the specified string. aMaxWidth specifies the maximum +// width available. Once this limit is reached no more characters are measured. +// The number of characters that fit within the maximum width are returned in +// aMaxFit +nscoord +ImageFrame::MeasureString(nsIFontMetrics* aFontMetrics, + const PRUnichar* aString, + PRInt32 aLength, + nscoord aMaxWidth, + PRUint32& aMaxFit) +{ + nscoord totalWidth = 0; + nscoord spaceWidth; + aFontMetrics->GetWidth(' ', spaceWidth); + + aMaxFit = 0; + while (aLength > 0) { + // Find the next place we can line break + PRUint32 len = aLength; + PRBool trailingSpace = PR_FALSE; + for (PRInt32 i = 0; i < aLength; i++) { + if (XP_IS_SPACE(aString[i]) && (i > 0)) { + len = i; // don't include the space when measuring + trailingSpace = PR_TRUE; + break; + } + } + + // Measure this chunk of text, and see if it fits + nscoord width; + aFontMetrics->GetWidth(aString, len, width); + PRBool fits = (totalWidth + width) <= aMaxWidth; + + // If it fits on the line, or it's the first word we've processed then + // include it + if (fits || (0 == totalWidth)) { + // New piece fits + totalWidth += width; + + // If there's a trailing space then see if it fits as well + if (trailingSpace) { + if ((totalWidth + spaceWidth) <= aMaxWidth) { + totalWidth += spaceWidth; + } else { + // Space won't fit. Leave it at the end but don't include it in + // the width + fits = PR_FALSE; + } + + len++; + } + + aMaxFit += len; + aString += len; + aLength -= len; + } + + if (!fits) { + break; + } + } + + return totalWidth; +} + +// Formats the alt-text to fit within the specified rectangle. Breaks lines +// between words if a word would extend past the edge of the rectangle +void +ImageFrame::DisplayAltText(nsIPresContext& aPresContext, + nsIRenderingContext& aRenderingContext, + const nsString& aAltText, + const nsRect& aRect) +{ + // Clip so we don't render outside of the rect. + aRenderingContext.PushState(); + aRenderingContext.SetClipRect(aRect, nsClipCombine_kIntersect); + + const nsStyleColor* color = + (const nsStyleColor*)mStyleContext->GetStyleData(eStyleStruct_Color); + const nsStyleFont* font = + (const nsStyleFont*)mStyleContext->GetStyleData(eStyleStruct_Font); + + // Set font and color + aRenderingContext.SetColor(color->mColor); + aRenderingContext.SetFont(font->mFont); + + // Format the text to display within the formatting rect + nsIFontMetrics* fm = aRenderingContext.GetFontMetrics(); + + nscoord maxDescent, height; + fm->GetMaxDescent(maxDescent); + fm->GetHeight(height); + + // XXX It would be nice if there was a way to have the font metrics tell + // use where to break the text given a maximum width. At a minimum we need + // to be able to get the break character... + const PRUnichar* str = aAltText.GetUnicode(); + PRInt32 strLen = aAltText.Length(); + nscoord y = aRect.y; + while ((strLen > 0) && ((y + maxDescent) < aRect.YMost())) { + // Determine how much of the text to display on this line + PRUint32 maxFit; // number of characters that fit + nscoord width = MeasureString(fm, str, strLen, aRect.width, maxFit); + + // Display the text + aRenderingContext.DrawString(str, maxFit, aRect.x, y, 0); + + // Move to the next line + str += maxFit; + strLen -= maxFit; + y += height; + } + + NS_RELEASE(fm); + aRenderingContext.PopState(); +} + +struct nsRecessedBorder : public nsStyleSpacing { + nsRecessedBorder(nscoord aBorderWidth) + : nsStyleSpacing() + { + nsStyleCoord styleCoord(aBorderWidth); + + mBorder.SetLeft(styleCoord); + mBorder.SetTop(styleCoord); + mBorder.SetRight(styleCoord); + mBorder.SetBottom(styleCoord); + mBorderStyle[0] = NS_STYLE_BORDER_STYLE_INSET; + mBorderStyle[1] = NS_STYLE_BORDER_STYLE_INSET; + mBorderStyle[2] = NS_STYLE_BORDER_STYLE_INSET; + mBorderStyle[3] = NS_STYLE_BORDER_STYLE_INSET; + mBorderColor[0] = 0; + mBorderColor[1] = 0; + mBorderColor[2] = 0; + mBorderColor[3] = 0; + mHasCachedMargin = mHasCachedPadding = mHasCachedBorder = PR_FALSE; + } +}; + +NS_METHOD +ImageFrame::Paint(nsIPresContext& aPresContext, + nsIRenderingContext& aRenderingContext, + const nsRect& aDirtyRect) +{ + if ((0 == mRect.width) || (0 == mRect.height)) { + // Do not render when given a zero area. This avoids some useless + // scaling work while we wait for our image dimensions to arrive + // asynchronously. + return NS_OK; + } + + const nsStyleDisplay* disp = + (const nsStyleDisplay*)mStyleContext->GetStyleData(eStyleStruct_Display); + + if (disp->mVisible) { + // First paint background and borders + nsLeafFrame::Paint(aPresContext, aRenderingContext, aDirtyRect); + + nsIImage* image = mImageLoader.GetImage(); + if (nsnull == image) { + // No image yet. Draw the icon that indicates we're loading, and display + // the alt-text + nsAutoString altText; + if (NS_CONTENT_ATTR_HAS_VALUE == mContent->GetAttribute("ALT", altText)) { + // Display a recessed one-pixel border in the inner area + nsRect inner; + GetInnerArea(&aPresContext, inner); + + float p2t = aPresContext.GetPixelsToTwips(); + nsRecessedBorder recessedBorder(NSIntPixelsToTwips(1, p2t)); + nsCSSRendering::PaintBorder(aPresContext, aRenderingContext, this, inner, + inner, recessedBorder, 0); + inner.Deflate(NSIntPixelsToTwips(1, p2t), NSIntPixelsToTwips(1, p2t)); + + // Leave a 8 pixel left/right padding, and a 5 pixel top/bottom padding + inner.Deflate(NSIntPixelsToTwips(8, p2t), NSIntPixelsToTwips(5, p2t)); + + // If there's room, then display the alt-text + if (!inner.IsEmpty()) { + DisplayAltText(aPresContext, aRenderingContext, altText, inner); + } + } + return NS_OK; + } + + // Now render the image into our inner area (the area without the + // borders and padding) + nsRect inner; + GetInnerArea(&aPresContext, inner); + if (mImageLoader.GetLoadImageFailed()) { + float p2t = aPresContext.GetPixelsToTwips(); + inner.width = NSIntPixelsToTwips(image->GetWidth(), p2t); + inner.height = NSIntPixelsToTwips(image->GetHeight(), p2t); + } + aRenderingContext.DrawImage(image, inner); + + if (GetShowFrameBorders()) { + nsIImageMap* map = GetImageMap(); + if (nsnull != map) { + aRenderingContext.SetColor(NS_RGB(0, 0, 0)); + aRenderingContext.PushState(); + aRenderingContext.Translate(inner.x, inner.y); + map->Draw(aPresContext, aRenderingContext); + aRenderingContext.PopState(); + } + } + } + + return NS_OK; +} + +nsIImageMap* +ImageFrame::GetImageMap() +{ + if (nsnull == mImageMap) { + nsAutoString usemap; + mContent->GetAttribute("usemap", usemap); + if (0 == usemap.Length()) { + return nsnull; + } + + nsIDocument* doc = nsnull; + mContent->GetDocument(doc); + if (nsnull == doc) { + return nsnull; + } + + if (usemap.First() == '#') { + usemap.Cut(0, 1); + } + nsIHTMLDocument* hdoc; + nsresult rv = doc->QueryInterface(kIHTMLDocumentIID, (void**)&hdoc); + NS_RELEASE(doc); + if (NS_OK == rv) { + nsIImageMap* map; + rv = hdoc->GetImageMap(usemap, &map); + NS_RELEASE(hdoc); + if (NS_OK == rv) { + mImageMap = map; + } + } + } + NS_IF_ADDREF(mImageMap); + return mImageMap; +} + +void +ImageFrame::TriggerLink(nsIPresContext& aPresContext, + const nsString& aURLSpec, + const nsString& aTargetSpec, + PRBool aClick) +{ + nsILinkHandler* handler = nsnull; + aPresContext.GetLinkHandler(&handler); + if (nsnull != handler) { + if (aClick) { + handler->OnLinkClick(this, aURLSpec, aTargetSpec); + } + else { + handler->OnOverLink(this, aURLSpec, aTargetSpec); + } + } +} + +PRBool +ImageFrame::IsServerImageMap() +{ + nsAutoString ismap; + return NS_CONTENT_ATTR_HAS_VALUE == mContent->GetAttribute("ismap", ismap); +} + +PRIntn +ImageFrame::GetSuppress() +{ + nsAutoString s; + if (NS_CONTENT_ATTR_HAS_VALUE == mContent->GetAttribute("suppress", s)) { + if (s.EqualsIgnoreCase("true")) { + return SUPPRESS; + } else if (s.EqualsIgnoreCase("false")) { + return DONT_SUPPRESS; + } + } + return DEFAULT_SUPPRESS; +} + +// XXX what should clicks on transparent pixels do? +NS_METHOD +ImageFrame::HandleEvent(nsIPresContext& aPresContext, + nsGUIEvent* aEvent, + nsEventStatus& aEventStatus) +{ + nsIImageMap* map; + aEventStatus = nsEventStatus_eIgnore; + + switch (aEvent->message) { + case NS_MOUSE_LEFT_BUTTON_UP: + case NS_MOUSE_MOVE: + map = GetImageMap(); + if ((nsnull != map) || IsServerImageMap()) { + nsIURL* docURL = nsnull; + nsIDocument* doc = nsnull; + mContent->GetDocument(doc); + if (nsnull != doc) { + docURL = doc->GetDocumentURL(); + NS_RELEASE(doc); + } + + // Ask map if the x,y coordinates are in a clickable area + float t2p = aPresContext.GetTwipsToPixels(); + nsAutoString absURL, target, altText; + PRBool suppress; + if (nsnull != map) { + // Subtract out border and padding here so that we are looking + // at the right coordinates. Hit detection against area tags + // is done after the mouse wanders over the image, not over + // the image's borders. + nsRect inner; + GetInnerArea(&aPresContext, inner); + PRInt32 x = NSTwipsToIntPixels((aEvent->point.x - inner.x), t2p); + PRInt32 y = NSTwipsToIntPixels((aEvent->point.y - inner.y), t2p); + nsresult r = map->IsInside(x, y, docURL, absURL, target, altText, + &suppress); + NS_IF_RELEASE(docURL); + NS_RELEASE(map); + if (NS_OK == r) { + // We hit a clickable area. Time to go somewhere... + PRBool clicked = PR_FALSE; + if (aEvent->message == NS_MOUSE_LEFT_BUTTON_UP) { + aEventStatus = nsEventStatus_eConsumeNoDefault; + clicked = PR_TRUE; + } + TriggerLink(aPresContext, absURL, target, clicked); + } + } + else { + suppress = GetSuppress(); + nsAutoString baseURL; + mContent->GetAttribute(NS_HTML_BASE_HREF, baseURL); + nsAutoString src; + mContent->GetAttribute("src", src); + NS_MakeAbsoluteURL(docURL, baseURL, src, absURL); + + // Note: We don't subtract out the border/padding here to remain + // compatible with navigator. [ick] + PRInt32 x = NSTwipsToIntPixels(aEvent->point.x, t2p); + PRInt32 y = NSTwipsToIntPixels(aEvent->point.y, t2p); + char cbuf[50]; + PR_snprintf(cbuf, sizeof(cbuf), "?%d,%d", x, y); + absURL.Append(cbuf); + PRBool clicked = PR_FALSE; + if (aEvent->message == NS_MOUSE_LEFT_BUTTON_UP) { + aEventStatus = nsEventStatus_eConsumeNoDefault; + clicked = PR_TRUE; + } + TriggerLink(aPresContext, absURL, target, clicked); + } + break; + } + // FALL THROUGH + + default: + // Let default event handler deal with it + return nsLeafFrame::HandleEvent(aPresContext, aEvent, aEventStatus); + } + return NS_OK; +} + +NS_METHOD +ImageFrame::GetCursorAndContentAt(nsIPresContext& aPresContext, + const nsPoint& aPoint, + nsIFrame** aFrame, + nsIContent** aContent, + PRInt32& aCursor) +{ + // The default cursor is to have no cursor + aCursor = NS_STYLE_CURSOR_INHERIT; + *aContent = mContent; + + const nsStyleColor* styleColor = (const nsStyleColor*) + mStyleContext->GetStyleData(eStyleStruct_Color); + if (styleColor->mCursor != NS_STYLE_CURSOR_INHERIT) { + // If we have a particular cursor, use it + *aFrame = this; + aCursor = (PRInt32) styleColor->mCursor; + } + + nsIImageMap* map = GetImageMap(); + if (nsnull != map) { + nsRect inner; + GetInnerArea(&aPresContext, inner); + aCursor = NS_STYLE_CURSOR_DEFAULT; + float t2p = aPresContext.GetTwipsToPixels(); + PRInt32 x = NSTwipsToIntPixels((aPoint.x - inner.x), t2p); + PRInt32 y = NSTwipsToIntPixels((aPoint.y - inner.y), t2p); + if (NS_OK == map->IsInside(x, y)) { + aCursor = NS_STYLE_CURSOR_HAND; + } + NS_RELEASE(map); + } + + return NS_OK; +} + +NS_IMETHODIMP +ImageFrame::ContentChanged(nsIPresShell* aShell, + nsIPresContext* aPresContext, + nsIContent* aChild, + nsISupports* aSubContent) +{ + // See if the src attribute changed; if it did then trigger a redraw + // by firing up a new image load request. Otherwise let our base + // class handle the content-changed request. + nsAutoString oldSRC; + mImageLoader.GetURL(oldSRC); + + // Get src attribute's value and construct a new absolute url from it + nsAutoString newSRC; + if (NS_CONTENT_ATTR_HAS_VALUE == mContent->GetAttribute("SRC", newSRC)) { + if (!oldSRC.Equals(newSRC)) { + mSizeFrozen = PR_TRUE; + +#ifdef NS_DEBUG + char oldcbuf[100], newcbuf[100]; + oldSRC.ToCString(oldcbuf, sizeof(oldcbuf)); + newSRC.ToCString(newcbuf, sizeof(newcbuf)); + NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS, + ("ImageFrame::ContentChanged: new image source; old='%s' new='%s'", + oldcbuf, newcbuf)); +#endif + + // Get rid of old image loader and start a new image load going + mImageLoader.DestroyLoader(); + + // Fire up a new image load request + PRIntn loadStatus; + mImageLoader.SetURL(newSRC); + mImageLoader.StartLoadImage(aPresContext, this, PR_FALSE, loadStatus); + + NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS, + ("ImageFrame::ContentChanged: loadImage status=%x", + loadStatus)); + + // If the image is already ready then we need to trigger a + // redraw because the image loader won't. + if (loadStatus & NS_IMAGE_LOAD_STATUS_IMAGE_READY) { + // XXX Stuff this into a method on nsIPresShell/Context + nsRect bounds; + nsPoint offset; + nsIView* view; + GetOffsetFromView(offset, view); + nsIViewManager* vm; + view->GetViewManager(vm); + bounds.x = offset.x; + bounds.y = offset.y; + bounds.width = mRect.width; + bounds.height = mRect.height; + vm->UpdateView(view, bounds, 0); + NS_RELEASE(vm); + } + + return NS_OK; + } + } + + return ImageFrameSuper::ContentChanged(aShell, aPresContext, aChild, + aSubContent); +} diff --git a/mozilla/layout/html/base/src/nsSpacerFrame.cpp b/mozilla/layout/html/base/src/nsSpacerFrame.cpp new file mode 100644 index 00000000000..97a527a37cc --- /dev/null +++ b/mozilla/layout/html/base/src/nsSpacerFrame.cpp @@ -0,0 +1,191 @@ +/* -*- 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.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. + */ +#include "nsHTMLParts.h" +#include "nsIHTMLContent.h" +#include "nsFrame.h" +#include "nsIInlineReflow.h" +#include "nsCSSLineLayout.h" +#include "nsHTMLIIDs.h" +#include "nsIPresContext.h" +#include "nsIPresShell.h" +#include "nsHTMLAtoms.h" +#include "nsUnitConversion.h" +#include "nsIStyleContext.h" +#include "nsStyleConsts.h" + +// Spacer type's +#define TYPE_WORD 0 // horizontal space +#define TYPE_LINE 1 // line-break + vertical space +#define TYPE_IMAGE 2 // acts like a sized image with nothing to see + +class SpacerFrame : public nsFrame, private nsIInlineReflow { +public: + SpacerFrame(nsIContent* aContent, nsIFrame* aParentFrame); + + // nsISupports + NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr); + + // nsIInlineReflow + NS_IMETHOD FindTextRuns(nsCSSLineLayout& aLineLayout, + nsIReflowCommand* aReflowCommand); + NS_IMETHOD InlineReflow(nsCSSLineLayout& aLineLayout, + nsReflowMetrics& aDesiredSize, + const nsReflowState& aReflowState); + + PRUint8 GetType(); + +protected: + virtual ~SpacerFrame(); +}; + +nsresult +NS_NewSpacerFrame(nsIContent* aContent, + nsIFrame* aParentFrame, + nsIFrame*& aResult) +{ + nsIFrame* frame = new SpacerFrame(aContent, aParentFrame); + if (nsnull == frame) { + return NS_ERROR_OUT_OF_MEMORY; + } + aResult = frame; + return NS_OK; +} + +SpacerFrame::SpacerFrame(nsIContent* aContent, nsIFrame* aParentFrame) + : nsFrame(aContent, aParentFrame) +{ +} + +SpacerFrame::~SpacerFrame() +{ +} + +NS_IMETHODIMP +SpacerFrame::QueryInterface(REFNSIID aIID, void** aInstancePtrResult) +{ + NS_PRECONDITION(nsnull != aInstancePtrResult, "null pointer"); + if (nsnull == aInstancePtrResult) { + return NS_ERROR_NULL_POINTER; + } + if (aIID.Equals(kIInlineReflowIID)) { + *aInstancePtrResult = (void*) ((nsIInlineReflow*)this); + return NS_OK; + } + return nsFrame::QueryInterface(aIID, aInstancePtrResult); +} + +NS_IMETHODIMP +SpacerFrame::InlineReflow(nsCSSLineLayout& aLineLayout, + nsReflowMetrics& aMetrics, + const nsReflowState& aReflowState) +{ + nsresult rv = NS_FRAME_COMPLETE; + + // By default, we have no area + aMetrics.width = 0; + aMetrics.height = 0; + aMetrics.ascent = 0; + aMetrics.descent = 0; + + nscoord width = 0; + nscoord height = 0; + PRUint8 type = GetType(); + nsresult ca; + nsIHTMLContent* hc = nsnull; + mContent->QueryInterface(kIHTMLContentIID, (void**) &hc); + if (nsnull != hc) { + if (type != TYPE_IMAGE) { + nsHTMLValue val; + ca = hc->GetAttribute(nsHTMLAtoms::size, val); + if (NS_CONTENT_ATTR_HAS_VALUE == ca) { + width = val.GetPixelValue(); + } + } else { + nsHTMLValue val; + ca = hc->GetAttribute(nsHTMLAtoms::width, val); + if (NS_CONTENT_ATTR_HAS_VALUE == ca) { + if (eHTMLUnit_Pixel == val.GetUnit()) { + width = val.GetPixelValue(); + } + } + ca = hc->GetAttribute(nsHTMLAtoms::height, val); + if (NS_CONTENT_ATTR_HAS_VALUE == ca) { + if (eHTMLUnit_Pixel == val.GetUnit()) { + height = val.GetPixelValue(); + } + } + } + NS_RELEASE(hc); + } + + float p2t = aLineLayout.mPresContext->GetPixelsToTwips(); + switch (type) { + case TYPE_WORD: + if (0 != width) { + aMetrics.width = NSIntPixelsToTwips(width, p2t); + } + break; + + case TYPE_LINE: + if (0 != width) { + rv = NS_INLINE_LINE_BREAK_AFTER(0); + aMetrics.height = NSIntPixelsToTwips(width, p2t); + aMetrics.ascent = aMetrics.height; + } + break; + + case TYPE_IMAGE: + aMetrics.width = NSIntPixelsToTwips(width, p2t); + aMetrics.height = NSIntPixelsToTwips(height, p2t); + aMetrics.ascent = aMetrics.height; + break; + } + + if (nsnull != aMetrics.maxElementSize) { + aMetrics.maxElementSize->width = aMetrics.width; + aMetrics.maxElementSize->height = aMetrics.height; + } + + return rv; +} + +NS_IMETHODIMP +SpacerFrame::FindTextRuns(nsCSSLineLayout& aLineLayout, + nsIReflowCommand* aReflowCommand) +{ + aLineLayout.EndTextRun(); + return NS_OK; +} + +PRUint8 +SpacerFrame::GetType() +{ + PRUint8 type = TYPE_WORD; + nsAutoString value; + if (NS_CONTENT_ATTR_HAS_VALUE == mContent->GetAttribute("type", value)) { + if (value.EqualsIgnoreCase("line") || + value.EqualsIgnoreCase("vert") || + value.EqualsIgnoreCase("vertical")) { + return TYPE_LINE; + } + if (value.EqualsIgnoreCase("block")) { + return TYPE_IMAGE; + } + } + return type; +} diff --git a/mozilla/layout/html/base/src/nsTextFrame.cpp b/mozilla/layout/html/base/src/nsTextFrame.cpp new file mode 100644 index 00000000000..f1272df7f49 --- /dev/null +++ b/mozilla/layout/html/base/src/nsTextFrame.cpp @@ -0,0 +1,1558 @@ +/* -*- 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.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. + */ +#include "nsHTMLParts.h" +#include "nsCRT.h" +#include "nsSplittableFrame.h" +#include "nsIInlineReflow.h" +#include "nsCSSLineLayout.h" +#include "nsString.h" +#include "nsIPresContext.h" +#include "nsStyleConsts.h" +#include "nsIStyleContext.h" +#include "nsCoord.h" +#include "nsIFontMetrics.h" +#include "nsIRenderingContext.h" +#include "nsHTMLIIDs.h" +#include "nsIPresShell.h" +#include "nsIView.h" +#include "nsIViewManager.h" +#include "nsITimerCallback.h" +#include "nsITimer.h" +#include "prtime.h" +#include "nsVoidArray.h" +#include "prprf.h" +#include "nsIDOMText.h" +#include "nsIDocument.h" +#include "nsIDeviceContext.h" +#include "nsXIFConverter.h" +#include "nsITextContent.h" + +// Selection includes +#include "nsISelection.h" +#include "nsSelectionRange.h" + +#define CALC_DEBUG 0 +#define DO_SELECTION 0 + +static NS_DEFINE_IID(kIDOMTextIID, NS_IDOMTEXT_IID); + +#ifdef NS_DEBUG +#undef NOISY +#undef NOISY_BLINK +#else +#undef NOISY +#undef NOISY_BLINK +#endif + +// XXX TODO: +// 0. re-implement justified text +// 1. add in a rendering method that can render justified text +// 2. text renderer should negotiate with rendering context/font +// metrics to see what it can handle; for example, if the renderer can +// automatically deal with underlining, strikethrough, justification, +// etc, then the text renderer should let the rc do the work; +// otherwise there should be XP fallback code here. + +// XXX TODO: +// implement nsIFrame::Reflow + +// XXX Speedup ideas +// 1. justified text can use word width information during resize reflows +// 2. when we are doing an unconstrained reflow we know we are going to +// get reflowed again; collect up the word widths we are computing as we +// do this and then save them in the mWords; later on when we get reflowed +// again we can destroy them +// 3. when pulling up text get word width information from next-in-flow + +// XXX temporary +#define XP_IS_SPACE(_ch) \ + (((_ch) == ' ') || ((_ch) == '\t') || ((_ch) == '\n')) + +// XXX need more of this in nsIFontMetrics.GetWidth +#define CH_NBSP 160 + +// XXX use a PreTextFrame for pre-formatted text? + +static NS_DEFINE_IID(kITextContentIID, NS_ITEXT_CONTENT_IID); + +class TextFrame; + +class BlinkTimer : public nsITimerCallback { +public: + BlinkTimer(); + ~BlinkTimer(); + + NS_DECL_ISUPPORTS + + void AddFrame(TextFrame* aFrame); + + PRBool RemoveFrame(TextFrame* aFrame); + + PRInt32 FrameCount(); + + void Start(); + + void Stop(); + + virtual void Notify(nsITimer *timer); + + nsITimer* mTimer; + nsVoidArray mFrames; +}; + +class TextFrame : public nsSplittableFrame, private nsIInlineReflow { +public: + TextFrame(nsIContent* aContent, nsIFrame* aParentFrame); + + // nsISupports + NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr); + + // nsIFrame + NS_IMETHOD Paint(nsIPresContext& aPresContext, + nsIRenderingContext& aRenderingContext, + const nsRect& aDirtyRect); + + NS_IMETHOD GetCursorAndContentAt(nsIPresContext& aPresContext, + const nsPoint& aPoint, + nsIFrame** aFrame, + nsIContent** aContent, + PRInt32& aCursor); + + NS_IMETHOD ContentChanged(nsIPresShell* aShell, + nsIPresContext* aPresContext, + nsIContent* aChild, + nsISupports* aSubContent); + + NS_IMETHOD List(FILE* out, PRInt32 aIndent) const; + + NS_IMETHOD ListTag(FILE* out) const; + + virtual PRInt32 GetPosition(nsIPresContext& aCX, + nsGUIEvent* aEvent, + nsIFrame * aNewFrame, + PRUint32& aAcutalContentOffset); + + // nsIInlineReflow + NS_IMETHOD FindTextRuns(nsCSSLineLayout& aLineLayout, + nsIReflowCommand* aReflowCommand); + + NS_IMETHOD InlineReflow(nsCSSLineLayout& aLineLayout, + nsReflowMetrics& aMetrics, + const nsReflowState& aReflowState); + + // TextFrame methods + struct SelectionInfo { + PRInt32 mStartOffset; + PRInt32 mEndOffset; + PRBool mEmptySelection; + }; + + void ComputeSelectionInfo(nsIRenderingContext& aRenderingContext, + nsIDocument* aDocument, + PRInt32* aIndicies, PRInt32 aTextLength, + SelectionInfo& aResult); + + PRUnichar* PrepareUnicodeText(PRInt32* aIndicies, + PRUnichar* aBuffer, PRInt32 aBufSize, + PRInt32& aStrLen); + + char* PrepareAsciiText(PRInt32* aIndexes, + char* aBuffer, PRInt32 aBufSize, + PRInt32& aStrLen); + + void PaintUnicodeText(nsIPresContext& aPresContext, + nsIRenderingContext& aRenderingContext, + nscolor aTextColor, + nscolor aSelectionTextColor, + nscolor aSelectionBGColor, + nscoord dx, nscoord dy); + + void PaintAsciiText(nsIPresContext& aPresContext, + nsIRenderingContext& aRenderingContext, + nscolor aTextColor, + nscolor aSelectionTextColor, + nscolor aSelectionBGColor, + nscoord dx, nscoord dy); + + nsInlineReflowStatus ReflowPre(nsCSSLineLayout& aLineLayout, + nsReflowMetrics& aMetrics, + const nsReflowState& aReflowState, + const nsStyleFont& aFont, + PRInt32 aStartingOffset); + + nsInlineReflowStatus ReflowNormal(nsCSSLineLayout& aLineLayout, + nsReflowMetrics& aMetrics, + const nsReflowState& aReflowState, + const nsStyleFont& aFontStyle, + const nsStyleText& aTextStyle, + PRInt32 aStartingOffset); + +protected: + virtual ~TextFrame(); + + PRInt32 mContentOffset; + PRInt32 mContentLength; + + // XXX need a better packing + PRUint32 mFlags; + PRUint32 mColumn; +}; + +// Flag information used by rendering code. This information is +// computed by the ResizeReflow code. Remaining bits are used by the +// tab count. +#define TEXT_SKIP_LEADING_WS 0x01 +#define TEXT_HAS_MULTIBYTE 0x02 +#define TEXT_IS_PRE 0x04 +#define TEXT_BLINK_ON 0x08 +#define TEXT_ENDS_IN_WHITESPACE 0x10 + +#define TEXT_GET_TAB_COUNT(_mf) ((_mf) >> 5) +#define TEXT_SET_TAB_COUNT(_mf,_tabs) (_mf) = (_mf) | ((_tabs)<< 5) + +//---------------------------------------------------------------------- + +static PRBool gBlinkTextOff; +static BlinkTimer* gTextBlinker; +#ifdef NOISY_BLINK +static PRTime gLastTick; +#endif + +BlinkTimer::BlinkTimer() +{ + NS_INIT_REFCNT(); + mTimer = nsnull; +} + +BlinkTimer::~BlinkTimer() +{ + Stop(); +} + +void BlinkTimer::Start() +{ + nsresult rv = NS_NewTimer(&mTimer); + if (NS_OK == rv) { + mTimer->Init(this, 750); + } +} + +void BlinkTimer::Stop() +{ + if (nsnull != mTimer) { + mTimer->Cancel(); + NS_RELEASE(mTimer); + } +} + +static NS_DEFINE_IID(kITimerCallbackIID, NS_ITIMERCALLBACK_IID); +NS_IMPL_ISUPPORTS(BlinkTimer, kITimerCallbackIID); + +void BlinkTimer::AddFrame(TextFrame* aFrame) { + mFrames.AppendElement(aFrame); + if (1 == mFrames.Count()) { + Start(); + } +printf("%d blinking frames [add %p]\n", mFrames.Count(), aFrame); +} + +PRBool BlinkTimer::RemoveFrame(TextFrame* aFrame) { + PRBool rv = mFrames.RemoveElement(aFrame); + if (0 == mFrames.Count()) { + Stop(); + } +printf("%d blinking frames [remove %p] [%s]\n", + mFrames.Count(), aFrame, rv ? "true" : "FALSE"); + return rv; +} + +PRInt32 BlinkTimer::FrameCount() { + return mFrames.Count(); +} + +void BlinkTimer::Notify(nsITimer *timer) +{ + // Toggle blink state bit so that text code knows whether or not to + // render. All text code shares the same flag so that they all blink + // in unison. + gBlinkTextOff = PRBool(!gBlinkTextOff); + + // XXX hack to get auto-repeating timers; restart before doing + // expensive work so that time between ticks is more even + Stop(); + Start(); + +#ifdef NOISY_BLINK + PRTime now = PR_Now(); + char buf[50]; + PRTime delta; + LL_SUB(delta, now, gLastTick); + gLastTick = now; + PR_snprintf(buf, sizeof(buf), "%lldusec", delta); + printf("%s\n", buf); +#endif + + PRInt32 i, n = mFrames.Count(); + for (i = 0; i < n; i++) { + TextFrame* text = (TextFrame*) mFrames.ElementAt(i); + + // Determine damaged area and tell view manager to redraw it + nsPoint offset; + nsRect bounds; + text->GetRect(bounds); + nsIView* view; + text->GetOffsetFromView(offset, view); + nsIViewManager* vm; + view->GetViewManager(vm); + bounds.x = offset.x; + bounds.y = offset.y; + vm->UpdateView(view, bounds, 0); + NS_RELEASE(vm); + } +} + +//---------------------------------------------------------------------- + +nsresult +NS_NewTextFrame(nsIContent* aContent, nsIFrame* aParentFrame, + nsIFrame*& aResult) +{ + nsIFrame* frame = new TextFrame(aContent, aParentFrame); + if (nsnull == frame) { + return NS_ERROR_OUT_OF_MEMORY; + } + aResult = frame; + return NS_OK; +} + +TextFrame::TextFrame(nsIContent* aContent, nsIFrame* aParentFrame) + : nsSplittableFrame(aContent, aParentFrame) +{ + if (nsnull == gTextBlinker) { + // Create text timer the first time out + gTextBlinker = new BlinkTimer(); + } + NS_ADDREF(gTextBlinker); +} + +TextFrame::~TextFrame() +{ + if (0 != (mFlags & TEXT_BLINK_ON)) { + NS_ASSERTION(nsnull != gTextBlinker, "corrupted blinker"); + gTextBlinker->RemoveFrame(this); + } + if (0 == gTextBlinker->Release()) { + // Release text timer when the last text frame is gone + gTextBlinker = nsnull; + } +} + +NS_IMETHODIMP +TextFrame::QueryInterface(REFNSIID aIID, void** aInstancePtrResult) +{ + NS_PRECONDITION(nsnull != aInstancePtrResult, "null pointer"); + if (nsnull == aInstancePtrResult) { + return NS_ERROR_NULL_POINTER; + } + if (aIID.Equals(kIInlineReflowIID)) { + *aInstancePtrResult = (void*) ((nsIInlineReflow*)this); + return NS_OK; + } + return nsFrame::QueryInterface(aIID, aInstancePtrResult); +} + +NS_IMETHODIMP +TextFrame::GetCursorAndContentAt(nsIPresContext& aPresContext, + const nsPoint& aPoint, + nsIFrame** aFrame, + nsIContent** aContent, + PRInt32& aCursor) +{ + *aContent = mContent; + aCursor = NS_STYLE_CURSOR_IBEAM; + return NS_OK; +} + +NS_IMETHODIMP +TextFrame::ContentChanged(nsIPresShell* aShell, + nsIPresContext* aPresContext, + nsIContent* aChild, + nsISupports* aSubContent) +{ + // Generate a reflow command with this frame as the target frame + nsIReflowCommand* cmd; + nsresult result; + + result = NS_NewHTMLReflowCommand(&cmd, this, nsIReflowCommand::ContentChanged); + if (NS_OK == result) { + aShell->AppendReflowCommand(cmd); + NS_RELEASE(cmd); + } + + return result; +} + +NS_IMETHODIMP +TextFrame::Paint(nsIPresContext& aPresContext, + nsIRenderingContext& aRenderingContext, + const nsRect& aDirtyRect) +{ + if ((0 != (mFlags & TEXT_BLINK_ON)) && gBlinkTextOff) { + return NS_OK; + } + + const nsStyleDisplay* disp = + (const nsStyleDisplay*)mStyleContext->GetStyleData(eStyleStruct_Display); + + if (disp->mVisible) { + // Get style data + const nsStyleColor* color = + (const nsStyleColor*)mStyleContext->GetStyleData(eStyleStruct_Color); + const nsStyleFont* font = + (const nsStyleFont*)mStyleContext->GetStyleData(eStyleStruct_Font); + + // Set font and color + aRenderingContext.SetColor(color->mColor); + aRenderingContext.SetFont(font->mFont); + + // XXX Get these from style + nscolor selbg = NS_RGB(0, 0, 0); + nscolor selfg = NS_RGB(255, 255, 255); + + // Select rendering method and render + PRUint32 hints = 0; + aRenderingContext.GetHints(hints); + if ((TEXT_HAS_MULTIBYTE & mFlags) || + (0 == (hints & NS_RENDERING_HINT_FAST_8BIT_TEXT))) { + // Use PRUnichar rendering routine + PaintUnicodeText(aPresContext, aRenderingContext, + color->mColor, selfg, selbg, 0, 0); + } + else { + // Use char rendering routine + PaintAsciiText(aPresContext, aRenderingContext, + color->mColor, selfg, selbg, 0, 0); + } + } + + return NS_OK; +} + +/** + * To keep things efficient we depend on text content implementing + * our nsITextContent API + */ +static const PRUnichar* +GetText(nsIContent* aContent, PRInt32& aLengthResult) +{ + const PRUnichar* cp = nsnull; + nsITextContent* tc = nsnull; + aContent->QueryInterface(kITextContentIID, (void**) &tc); + if (nsnull != tc) { + tc->GetText(cp, aLengthResult); + NS_RELEASE(tc); + } + return cp; +} + +/** + * This method computes the starting and ending offsets of the + * selection for this frame. The results are placed into + * aResult. There are 5 cases that we represent with a starting offset + * (aResult.mStartOffset), ending offset (aResult.mEndOffset) and an + * empty selection flag (aResult.mEmptySelection): + * + * case 1: The selection completely misses this content/frame. In this + * case mStartOffset and mEndOffset will be set to aTextLength and + * mEmptySelection will be false. + * + * case 2: The selection begins somewhere before or at this frame and + * ends somewhere in this frame. In this case mStartOffset will be set + * to 0 and mEndOffset will be set to the end of the selection and + * mEmptySelection will be false. + * + * case 3: The selection begins somewhere in this frame and ends + * somewhere in this frame. In this case mStartOffset and mEndOffset + * are set accordingly and if they happen to be the same value then + * mEmptySelection is set to true (otherwise it is set to false). + * + * case 4: The selection begins somewhere in this frame and ends + * somewhere else. In this case mStartOffset is set to where the + * selection begins and mEndOffset is set to aTextLength and + * mEmptySelection is set to false. + * + * case 5: The selection covers the entire content/frame. In this case + * mStartOffset is set to zero and mEndOffset is set to aTextLength and + * mEmptySelection is set to false. + */ +void +TextFrame::ComputeSelectionInfo(nsIRenderingContext& aRenderingContext, + nsIDocument* aDocument, + PRInt32* aIndicies, PRInt32 aTextLength, + SelectionInfo& aResult) +{ + // Assume, for now, that the selection misses this section of + // content completely. + aResult.mStartOffset = aTextLength; + aResult.mEndOffset = aTextLength; + aResult.mEmptySelection = PR_FALSE; + + nsISelection * selection = aDocument->GetSelection(); + nsSelectionRange * range = selection->GetRange(); + nsSelectionPoint * startPnt = range->GetStartPoint(); + nsSelectionPoint * endPnt = range->GetEndPoint(); + nsIContent * startContent = startPnt->GetContent(); + nsIContent * endContent = endPnt->GetContent(); + PRInt32 startOffset = startPnt->GetOffset() - mContentOffset; + PRInt32 endOffset = endPnt->GetOffset() - mContentOffset; + + // Check for the case that requires up to 3 sections first. This + // case also handles the empty selection. + if ((mContent == startContent) && (mContent == endContent)) { + // Selection starts and ends in this content (but maybe not this + // frame) + if ((startOffset >= mContentLength) || (endOffset <= 0)) { + // Selection doesn't intersect this frame + } + else if (endOffset < mContentLength) { + // End of selection is in this frame + aResult.mEndOffset = aIndicies[endOffset] - mContentOffset; + if (startOffset > 0) { + // Beginning of selection is also in this frame (this is the 3 + // section case) + aResult.mStartOffset = aIndicies[startOffset] - mContentOffset; + } + else { + // This is a 2 section case + aResult.mStartOffset = 0; + } + if (startOffset == endOffset) { + aResult.mEmptySelection = PR_TRUE; + } + } else if (startOffset > 0) { + // This is a 2 section case + aResult.mStartOffset = aIndicies[startOffset] - mContentOffset; + } else { + // This is a 1 section case (where the entire section is + // selected) + aResult.mStartOffset = 0; + } + } + else if (aDocument->IsInRange(startContent, endContent, mContent)) { + if (mContent == startContent) { + // Selection starts (but does not end) in this content (but + // maybe not in this frame) + if (startOffset <= 0) { + // Selection starts before or at this frame + aResult.mStartOffset = 0; + } + else if (startOffset < mContentLength) { + // Selection starts somewhere in this frame + aResult.mStartOffset = aIndicies[startOffset] - mContentOffset; + } + else { + // Selection starts after this frame + } + } + else if (mContent == endContent) { + // Selection ends (but does not start) in this content (but + // maybe not in this frame) + if (endOffset <= 0) { + // Selection ends before this frame + } + else if (endOffset < mContentLength) { + // Selection ends in this frame + aResult.mStartOffset = 0; + aResult.mEndOffset = aIndicies[endOffset] - mContentOffset; + } + else { + // Selection ends after this frame (the entire frame is selected) + aResult.mStartOffset = 0; + } + } + else { + // Selection starts before this content and ends after this + // content therefore the entire frame is selected + aResult.mStartOffset = 0; + } + } + + NS_IF_RELEASE(startContent); + NS_IF_RELEASE(endContent); + NS_RELEASE(selection); +} + +#define XP_IS_SPACE_W XP_IS_SPACE + +/** + * Prepare the text in the content for rendering. If aIndexes is not nsnull + * then fill in aIndexes's with the mapping from the original input to + * the prepared output. + */ +PRUnichar* +TextFrame::PrepareUnicodeText(PRInt32* aIndexes, + PRUnichar* aBuffer, PRInt32 aBufSize, + PRInt32& aStrLen) +{ + PRUnichar* s = aBuffer; + PRUnichar* s0 = s; + + // Get text content + PRInt32 textLength; + const PRUnichar* cp = GetText(mContent, textLength); + if (0 == textLength) { + aBuffer[0] = 0; + aStrLen = 0; + return aBuffer; + } + cp += mContentOffset; + const PRUnichar* end = cp + mContentLength; + + // Skip leading space if necessary + PRInt32 mappingInx = 0; + PRInt32 strInx = mContentOffset; + if (0 != (mFlags & TEXT_SKIP_LEADING_WS)) { + while (cp < end) { + PRUnichar ch = *cp++; + if (!XP_IS_SPACE_W(ch)) { + cp--; + break; + } else { + if (nsnull != aIndexes) { + aIndexes[mappingInx++] = strInx; + } + } + } + } + + PRInt32 length = 0; + if (0 != (TEXT_IS_PRE & mFlags)) { + PRIntn tabs = TEXT_GET_TAB_COUNT(mFlags); + + // Make a copy of the text we are to render, translating tabs + // into whitespace. + PRInt32 maxLen = (end - cp) + 8*tabs; + if (maxLen > aBufSize) { + s0 = s = new PRUnichar[maxLen]; + } + + // Translate tabs into whitespace; translate other whitespace into + // spaces. + PRIntn col = (PRIntn) mColumn; + while (cp < end) { + PRUnichar ch = *cp++; + if (XP_IS_SPACE_W(ch)) { + if (ch == '\t') { + PRIntn spaces = 8 - (col & 7); + col += spaces; + while (--spaces >= 0) { + *s++ = ' '; + length++; + } + if (nsnull != aIndexes) { + aIndexes[mappingInx++] = strInx; + strInx++;/* XXX wrong? */ + } + continue; + } else { + *s++ = ' '; + length++; + if (nsnull != aIndexes) { + aIndexes[mappingInx++] = strInx; + strInx++; + } + } + } else if (ch == CH_NBSP) { + *s++ = ' '; + length++; + if (nsnull != aIndexes) { + aIndexes[mappingInx++] = strInx; + strInx++; + } + } else { + *s++ = ch; + length++; + if (nsnull != aIndexes) { + aIndexes[mappingInx++] = strInx; + strInx++; + } + } + col++; + } + } else { + // Make a copy of the text we are to render, compressing out + // whitespace; translating whitespace to literal spaces; + // eliminating trailing whitespace. + PRInt32 maxLen = end - cp; + if (maxLen > aBufSize) { + s0 = s = new PRUnichar[maxLen]; + } + + // Compress down the whitespace + while (cp < end) { + PRUnichar ch = *cp++; + if (XP_IS_SPACE_W(ch)) { + while (cp < end) { + ch = *cp++; + if (!XP_IS_SPACE_W(ch)) { + cp--; + break; + } else { + if (nsnull != aIndexes) { + aIndexes[mappingInx++] = strInx; + } + } + } + *s++ = ' '; + length++; + if (nsnull != aIndexes) { + aIndexes[mappingInx++] = strInx; + strInx++; + } + } else { + *s++ = ch; + length++; + if (nsnull != aIndexes) { + aIndexes[mappingInx++] = strInx; + strInx++; + } + } + } + } + + aStrLen = length; + return s0; +} + +/** + * Prepare the text in the content for rendering. If aIndexes is not nsnull + * then fill in aIndexes's with the mapping from the original input to + * the prepared output. + */ +char* +TextFrame::PrepareAsciiText(PRInt32* aIndexes, + char* aBuffer, PRInt32 aBufSize, + PRInt32& aStrLen) +{ + char* s = aBuffer; + char* s0 = s; + + // Get text content + PRInt32 textLength; + const PRUnichar* cp = GetText(mContent, textLength); + if (0 == textLength) { + aBuffer[0] = 0; + aStrLen = 0; + return aBuffer; + } + cp += mContentOffset; + const PRUnichar* end = cp + mContentLength; + + // Skip leading space if necessary + PRInt32 mappingInx = 0; + PRInt32 strInx = mContentOffset; + if (0 != (mFlags & TEXT_SKIP_LEADING_WS)) { + while (cp < end) { + char ch = char(*cp++); + if (!XP_IS_SPACE(ch)) { + cp--; + break; + } else { + if (nsnull != aIndexes) { + aIndexes[mappingInx++] = strInx; + } + } + } + } + + PRInt32 length = 0; + if (0 != (TEXT_IS_PRE & mFlags)) { + PRIntn tabs = TEXT_GET_TAB_COUNT(mFlags); + + // Make a copy of the text we are to render, translating tabs + // into whitespace. + PRInt32 maxLen = (end - cp) + 8*tabs; + if (maxLen > aBufSize) { + s0 = s = new char[maxLen]; + } + + // Translate tabs into whitespace; translate other whitespace into + // spaces. + PRIntn col = (PRIntn) mColumn; + while (cp < end) { + char ch = char(*cp++); + if (XP_IS_SPACE(ch)) { + if (ch == '\t') { + PRIntn spaces = 8 - (col & 7); + col += spaces; + while (--spaces >= 0) { + *s++ = ' '; + length++; + } + if (nsnull != aIndexes) { + aIndexes[mappingInx++] = strInx; + strInx++;/* XXX wrong? */ + } + continue; + } else { + *s++ = ' '; + length++; + if (nsnull != aIndexes) { + aIndexes[mappingInx++] = strInx; + strInx++; + } + } + } else if (ch == CH_NBSP) { + *s++ = ' '; + length++; + if (nsnull != aIndexes) { + aIndexes[mappingInx++] = strInx; + strInx++; + } + } else { + *s++ = ch; + length++; + if (nsnull != aIndexes) { + aIndexes[mappingInx++] = strInx; + strInx++; + } + } + col++; + } + } else { + // Make a copy of the text we are to render, compressing out + // whitespace; translating whitespace to literal spaces; + // eliminating trailing whitespace. + PRInt32 maxLen = end - cp; + if (maxLen > aBufSize) { + s0 = s = new char[maxLen]; + } + + // Compress down the whitespace + while (cp < end) { + char ch = char(*cp++); + if (XP_IS_SPACE(ch)) { + while (cp < end) { + ch = char(*cp++); + if (!XP_IS_SPACE(ch)) { + cp--; + break; + } else { + if (nsnull != aIndexes) { + aIndexes[mappingInx++] = strInx; + } + } + } + *s++ = ' '; + length++; + if (nsnull != aIndexes) { + aIndexes[mappingInx++] = strInx; + strInx++; + } + } else { + *s++ = ch; + length++; + if (nsnull != aIndexes) { + aIndexes[mappingInx++] = strInx; + strInx++; + } + } + } + } + + aStrLen = length; + return s0; +} + +// XXX This clearly needs to be done by the container, *somehow* +#define CURSOR_COLOR NS_RGB(0,0,255) +static void +RenderSelectionCursor(nsIRenderingContext& aRenderingContext, + nscoord dx, nscoord dy, nscoord aHeight, + nscolor aCursorColor) +{ + // Draw little blue triangle + aRenderingContext.SetColor(aCursorColor); + nsPoint pnts[4]; + nscoord ox = aHeight / 4; + nscoord oy = ox; + nscoord x0 = dx; + nscoord y0 = dy + aHeight; + pnts[0].x = x0 - ox; + pnts[0].y = y0; + pnts[1].x = x0; + pnts[1].y = y0 - oy; + pnts[2].x = x0 + ox; + pnts[2].y = y0; + pnts[3].x = x0 - ox; + pnts[3].y = y0; + + aRenderingContext.FillPolygon(pnts, 4); +} + +// XXX letter-spacing +// XXX word-spacing + +void +TextFrame::PaintUnicodeText(nsIPresContext& aPresContext, + nsIRenderingContext& aRenderingContext, + nscolor aTextColor, + nscolor aSelectionTextColor, + nscolor aSelectionBGColor, + nscoord dx, nscoord dy) +{ + nsIPresShell* shell = aPresContext.GetShell(); + nsIDocument* doc = shell->GetDocument(); + PRBool displaySelection; + displaySelection = doc->GetDisplaySelection(); + + PRInt32 textLength; + PRInt32 indicies[500]; + PRInt32* ip = indicies; + if (mContentLength > 500) { + ip = new PRInt32[mContentLength]; + } + PRUnichar buf[500]; + PRUnichar* text = PrepareUnicodeText(displaySelection ? ip : nsnull, + buf, 500, textLength); + if (0 != textLength) { + if (!displaySelection) { + // When there is no selection showing, use the fastest and + // simplest rendering approach + aRenderingContext.DrawString(text, textLength, dx, dy, mRect.width); + } + else { + SelectionInfo si; + ComputeSelectionInfo(aRenderingContext, doc, ip, textLength, si); + + nscoord textWidth; + nsIFontMetrics * fm = aRenderingContext.GetFontMetrics(); + if (si.mEmptySelection) { + aRenderingContext.DrawString(text, textLength, dx, dy, mRect.width); + fm->GetWidth(text, PRUint32(si.mStartOffset), textWidth); + RenderSelectionCursor(aRenderingContext, + dx + textWidth, dy, mRect.height, + CURSOR_COLOR); + } + else { + nscoord x = dx; + + if (0 != si.mStartOffset) { + // Render first (unselected) section + fm->GetWidth(text, PRUint32(si.mStartOffset), textWidth); + aRenderingContext.DrawString(text, si.mStartOffset, + x, dy, textWidth); + x += textWidth; + } + PRInt32 secondLen = si.mEndOffset - si.mStartOffset; + if (0 != secondLen) { + // Get the width of the second (selected) section + fm->GetWidth(text + si.mStartOffset, PRUint32(secondLen), textWidth); + + // Render second (selected) section + aRenderingContext.SetColor(aSelectionBGColor); + aRenderingContext.FillRect(x, dy, textWidth, mRect.height); + aRenderingContext.SetColor(aSelectionTextColor); + aRenderingContext.DrawString(text + si.mStartOffset, secondLen, + x, dy, textWidth); + aRenderingContext.SetColor(aTextColor); + x += textWidth; + } + if (textLength != si.mEndOffset) { + PRInt32 thirdLen = textLength - si.mEndOffset; + + // Render third (unselected) section + fm->GetWidth(text + si.mEndOffset, PRUint32(thirdLen), textWidth); + aRenderingContext.DrawString(text + si.mEndOffset, + thirdLen, x, dy, textWidth); + } + } + NS_RELEASE(fm); + } + } + + // Cleanup + if (text != buf) { + delete [] text; + } + if (ip != indicies) { + delete [] ip; + } + NS_RELEASE(shell); + NS_RELEASE(doc); +} + +void +TextFrame::PaintAsciiText(nsIPresContext& aPresContext, + nsIRenderingContext& aRenderingContext, + nscolor aTextColor, + nscolor aSelectionTextColor, + nscolor aSelectionBGColor, + nscoord dx, nscoord dy) +{ + nsIPresShell* shell = aPresContext.GetShell(); + nsIDocument* doc = shell->GetDocument(); + PRBool displaySelection; + displaySelection = doc->GetDisplaySelection(); + + PRInt32 textLength; + PRInt32 indicies[500]; + PRInt32* ip = indicies; + if (mContentLength > 500) { + ip = new PRInt32[mContentLength]; + } + char buf[500]; + char* text = PrepareAsciiText(displaySelection ? ip : nsnull, + buf, 500, textLength); + if (0 != textLength) { + if (!displaySelection) { + // When there is no selection showing, use the fastest and + // simplest rendering approach + aRenderingContext.DrawString(text, textLength, dx, dy, mRect.width); + } + else { + SelectionInfo si; + ComputeSelectionInfo(aRenderingContext, doc, ip, textLength, si); + + nscoord textWidth; + nsIFontMetrics * fm = aRenderingContext.GetFontMetrics(); + if (si.mEmptySelection) { + aRenderingContext.DrawString(text, textLength, dx, dy, mRect.width); + fm->GetWidth(text, PRUint32(si.mStartOffset), textWidth); + RenderSelectionCursor(aRenderingContext, + dx + textWidth, dy, mRect.height, + CURSOR_COLOR); + } + else { + nscoord x = dx; + + if (0 != si.mStartOffset) { + // Render first (unselected) section + fm->GetWidth(text, PRUint32(si.mStartOffset), textWidth); + aRenderingContext.DrawString(text, si.mStartOffset, + x, dy, textWidth); + x += textWidth; + } + PRInt32 secondLen = si.mEndOffset - si.mStartOffset; + if (0 != secondLen) { + // Get the width of the second (selected) section + fm->GetWidth(text + si.mStartOffset, PRUint32(secondLen), textWidth); + + // Render second (selected) section + aRenderingContext.SetColor(aSelectionBGColor); + aRenderingContext.FillRect(x, dy, textWidth, mRect.height); + aRenderingContext.SetColor(aSelectionTextColor); + aRenderingContext.DrawString(text + si.mStartOffset, secondLen, + x, dy, textWidth); + aRenderingContext.SetColor(aTextColor); + x += textWidth; + } + if (textLength != si.mEndOffset) { + PRInt32 thirdLen = textLength - si.mEndOffset; + + // Render third (unselected) section + fm->GetWidth(text + si.mEndOffset, PRUint32(thirdLen), textWidth); + aRenderingContext.DrawString(text + si.mEndOffset, + thirdLen, x, dy, textWidth); + } + } + NS_RELEASE(fm); + } + } + + // Cleanup + if (text != buf) { + delete [] text; + } + if (ip != indicies) { + delete [] ip; + } + NS_RELEASE(shell); + NS_RELEASE(doc); +} + +NS_IMETHODIMP +TextFrame::FindTextRuns(nsCSSLineLayout& aLineLayout, + nsIReflowCommand* aReflowCommand) +{ + if (nsnull == mPrevInFlow) { + aLineLayout.AddText(this); + } + return NS_OK; +} + +// XXX this is slow + +// XXX it doesn't work well when dragging the end of the selection +// (can't hit the first character) + +// XXX it doesn't do 1/2 the char width to make picking characters +// more sensible + +PRInt32 +TextFrame::GetPosition(nsIPresContext& aCX, + nsGUIEvent* aEvent, + nsIFrame* aNewFrame, + PRUint32& aAcutalContentOffset) +{ + // Get the rendered form of the text + PRInt32 textLength; + PRInt32 indicies[500]; + PRInt32* ip = indicies; + if (mContentLength > 500) { + ip = new PRInt32[mContentLength]; + } + PRUnichar buf[500]; + PRUnichar* text = PrepareUnicodeText(ip, buf, 500, textLength); + + // Find the font metrics for this text + nsIStyleContext* styleContext; + aNewFrame->GetStyleContext(&aCX, styleContext); + const nsStyleFont *font = (const nsStyleFont*) + styleContext->GetStyleData(eStyleStruct_Font); + NS_RELEASE(styleContext); + nsIFontMetrics* fm = aCX.GetMetricsFor(font->mFont); + + // XXX This algorithm could use some improvement + PRInt32 offset = mContentOffset + mContentLength; + PRInt32 i; + for (i=1;iGetWidth(text, i, textWidth); + if (textWidth >= aEvent->point.x) { + if (aEvent->message == NS_MOUSE_LEFT_BUTTON_DOWN) { + i--; + if (i < 0) { + i = 0; + } + } + offset = 0; + PRInt32 j; + for (j=0;jmContentOffset; + return offset; +} + +NS_IMETHODIMP +TextFrame::InlineReflow(nsCSSLineLayout& aLineLayout, + nsReflowMetrics& aMetrics, + const nsReflowState& aReflowState) +{ + NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS, + ("enter TextFrame::Reflow: aMaxSize=%d,%d", + aReflowState.maxSize.width, aReflowState.maxSize.height)); + + // Get starting offset into the content + PRInt32 startingOffset = 0; + if (nsnull != mPrevInFlow) { + TextFrame* prev = (TextFrame*) mPrevInFlow; + startingOffset = prev->mContentOffset + prev->mContentLength; + } + + const nsStyleFont* font = + (const nsStyleFont*)mStyleContext->GetStyleData(eStyleStruct_Font); + + // Initialize mFlags (without destroying the TEXT_BLINK_ON bit) bits + // that are filled in by the reflow routines. + mFlags &= TEXT_BLINK_ON; + if (font->mFont.decorations & NS_STYLE_TEXT_DECORATION_BLINK) { + if (0 == (mFlags & TEXT_BLINK_ON)) { + mFlags |= TEXT_BLINK_ON; + gTextBlinker->AddFrame(this); + } + } + + const nsStyleText* text = + (const nsStyleText*)mStyleContext->GetStyleData(eStyleStruct_Text); + + nsInlineReflowStatus rs; + if (NS_STYLE_WHITESPACE_PRE == text->mWhiteSpace) { + // Use a specialized routine for pre-formatted text + rs = ReflowPre(aLineLayout, aMetrics, aReflowState, + *font, startingOffset); + } else { + // Use normal wrapping routine for non-pre text (this includes + // text that is not wrapping) + rs = ReflowNormal(aLineLayout, aMetrics, aReflowState, + *font, *text, startingOffset); + } + + NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS, + ("exit TextFrame::Reflow: rv=%x width=%d", + rs, aMetrics.width)); + return rs; +} + +// Reflow normal text (stuff that doesn't have to deal with horizontal +// tabs). Normal text reflow may or may not wrap depending on the +// "whiteSpace" style property. +nsInlineReflowStatus +TextFrame::ReflowNormal(nsCSSLineLayout& aLineLayout, + nsReflowMetrics& aMetrics, + const nsReflowState& aReflowState, + const nsStyleFont& aFont, + const nsStyleText& aTextStyle, + PRInt32 aStartingOffset) +{ + PRInt32 textLength; + const PRUnichar* cp = GetText(mContent, textLength); + cp += aStartingOffset; + const PRUnichar* end = cp + textLength - aStartingOffset; + const PRUnichar* cpStart = cp; + mContentOffset = aStartingOffset; + + nsIFontMetrics* fm = aLineLayout.mPresContext->GetMetricsFor(aFont.mFont); + PRInt32 spaceWidth; + fm->GetWidth(' ', spaceWidth); + PRBool wrapping = PR_TRUE; + if (NS_STYLE_WHITESPACE_NORMAL != aTextStyle.mWhiteSpace) { + wrapping = PR_FALSE; + } + + // Set whitespace skip flag + PRBool skipWhitespace = PR_FALSE; + if (aLineLayout.GetSkipLeadingWhiteSpace()) { + skipWhitespace = PR_TRUE; + mFlags |= TEXT_SKIP_LEADING_WS; + } + + // Try to fit as much of the text as possible. Note that if we are + // at the left margin then the first word always fits. In addition, + // we compute the size of the largest word that we contain. If we + // end up containing nothing (because there isn't enough space for + // the first word) then we still compute the size of that first + // non-fitting word. + + // XXX XP_IS_SPACE must not return true for the unicode   character + // XXX what about &zwj and it's cousins? + nscoord x = 0; + nscoord maxWidth = aReflowState.maxSize.width; + nscoord maxWordWidth = 0; + const PRUnichar* lastWordEnd = cpStart; +// const PRUnichar* lastWordStart = cpStart; + PRBool hasMultibyte = PR_FALSE; + PRBool endsInWhitespace = PR_FALSE; + + while (cp < end) { + PRUnichar ch = *cp++; + PRBool isWhitespace; + nscoord width; + if (XP_IS_SPACE(ch)) { + // Compress whitespace down to a single whitespace + while (cp < end) { + ch = *cp; + if (XP_IS_SPACE(ch)) { + cp++; + continue; + } + break; + } + if (skipWhitespace) { +#if XXX_fix_me + aLineLayout->AtSpace(); +#endif + skipWhitespace = PR_FALSE; + continue; + } + width = spaceWidth; + isWhitespace = PR_TRUE; + } else { + // The character is not a space character. Find the end of the + // word and then measure it. + if (ch >= 128) { + hasMultibyte = PR_TRUE; + } + const PRUnichar* wordStart = cp - 1; +// lastWordStart = wordStart; + while (cp < end) { + ch = *cp; + if (ch >= 256) { + hasMultibyte = PR_TRUE; + } + if (!XP_IS_SPACE(ch)) { + cp++; + continue; + } + break; + } + fm->GetWidth(wordStart, PRUint32(cp - wordStart), width); + skipWhitespace = PR_FALSE; + isWhitespace = PR_FALSE; + } + + // Now that we have the end of the word or whitespace, see if it + // will fit. + if ((0 != x) && wrapping && (x + width > maxWidth)) { + // The word/whitespace will not fit. + cp = lastWordEnd; + break; + } + +#if XXX_fix_me + // Update break state in line reflow state + // XXX move this out of the loop! + if (isWhitespace) { + aLineLayout.AtSpace(); + } + else { + aLineLayout.AtWordStart(this, x); + } +#endif + + // The word fits. Add it to the run of text. + x += width; + if (width > maxWordWidth) { + maxWordWidth = width; + } + lastWordEnd = cp; + endsInWhitespace = isWhitespace; + } + + if (hasMultibyte) { + mFlags |= TEXT_HAS_MULTIBYTE; + } + if (endsInWhitespace) { + mFlags |= TEXT_ENDS_IN_WHITESPACE; + } + + if (0 == x) { + // Since we collapsed into nothingness (all our whitespace + // is ignored) leave the aState->mSkipLeadingWhiteSpace + // flag alone since it doesn't want leading whitespace + } + else { + aLineLayout.SetSkipLeadingWhiteSpace(endsInWhitespace); + } + + // XXX too much code here: some of it isn't needed + // Now we know our content length + mContentLength = lastWordEnd - cpStart; + if (0 == mContentLength) { + if (cp == end) { + // The entire chunk of text was whitespace that we skipped over. + aMetrics.width = 0; + aMetrics.height = 0; + aMetrics.ascent = 0; + aMetrics.descent = 0; + mContentLength = end - cpStart; + if (nsnull != aMetrics.maxElementSize) { + aMetrics.maxElementSize->width = 0; + aMetrics.maxElementSize->height = 0; + } + NS_RELEASE(fm); + return NS_FRAME_COMPLETE; + } + } + + // Set desired size to the computed size + aMetrics.width = x; + fm->GetHeight(aMetrics.height); + fm->GetMaxAscent(aMetrics.ascent); + fm->GetMaxDescent(aMetrics.descent); + if (!wrapping) { + maxWordWidth = x; + } + if (nsnull != aMetrics.maxElementSize) { + aMetrics.maxElementSize->width = maxWordWidth; + fm->GetHeight(aMetrics.maxElementSize->height); + } + NS_RELEASE(fm); + return (cp == end) ? NS_FRAME_COMPLETE : NS_FRAME_NOT_COMPLETE; +} + +nsInlineReflowStatus +TextFrame::ReflowPre(nsCSSLineLayout& aLineLayout, + nsReflowMetrics& aMetrics, + const nsReflowState& aReflowState, + const nsStyleFont& aFont, + PRInt32 aStartingOffset) +{ + nsInlineReflowStatus rs = NS_FRAME_COMPLETE; + + PRInt32 textLength; + const PRUnichar* cp = GetText(mContent, textLength); + cp += aStartingOffset; + const PRUnichar* cpStart = cp; + const PRUnichar* end = cp + textLength - aStartingOffset; + + mFlags |= TEXT_IS_PRE; + nsIFontMetrics* fm = aLineLayout.mPresContext->GetMetricsFor(aFont.mFont); + PRInt32 width = 0; + PRBool hasMultibyte = PR_FALSE; + PRUint16 tabs = 0; + PRUint16 col = aLineLayout.GetColumn(); + mColumn = col; + nscoord spaceWidth; + fm->GetWidth(' ', spaceWidth); + +// XXX change this to measure a line at a time + while (cp < end) { + PRUnichar ch = *cp++; + if (ch == '\n') { + rs = (cp == end) + ? NS_INLINE_LINE_BREAK_AFTER(NS_FRAME_COMPLETE) + : NS_INLINE_LINE_BREAK_AFTER(NS_FRAME_NOT_COMPLETE); + break; + } + if (ch == '\t') { + // Advance to next tab stop + PRIntn spaces = 8 - (col & 7); + width += spaces * spaceWidth; + col += spaces; + tabs++; + continue; + } + if (ch == CH_NBSP) { + width += spaceWidth; + col++; + continue; + } + if (ch < 256) { + nscoord charWidth; + fm->GetWidth(ch, charWidth); + width += charWidth; + } else { + nscoord charWidth; + fm->GetWidth(ch, charWidth); + width += charWidth; + hasMultibyte = PR_TRUE; + } + col++; + } + aLineLayout.SetColumn(col); + if (hasMultibyte) { + mFlags |= TEXT_HAS_MULTIBYTE; + } + TEXT_SET_TAB_COUNT(mFlags, tabs); + + mContentOffset = aStartingOffset; + mContentLength = cp - cpStart; + aMetrics.width = width; + fm->GetHeight(aMetrics.height); + fm->GetMaxAscent(aMetrics.ascent); + fm->GetMaxDescent(aMetrics.descent); + if (nsnull != aMetrics.maxElementSize) { + aMetrics.maxElementSize->width = aMetrics.width; + aMetrics.maxElementSize->height = aMetrics.height; + } + NS_RELEASE(fm); + + return rs; +} + +// XXX there is a copy of this in nsGenericDomDataNode.cpp +static void +ToCString(const PRUnichar* cp, PRInt32 aLen, nsString& aBuf) +{ + const PRUnichar* end = cp + aLen; + while (cp < end) { + PRUnichar ch = *cp++; + if (ch == '\r') { + aBuf.Append("\\r"); + } else if (ch == '\n') { + aBuf.Append("\\n"); + } else if (ch == '\t') { + aBuf.Append("\\t"); + } else if ((ch < ' ') || (ch >= 127)) { + aBuf.Append("\\0"); + aBuf.Append((PRInt32)ch, 8); + } else { + aBuf.Append(ch); + } + } +} + +NS_IMETHODIMP +TextFrame::ListTag(FILE* out) const +{ + PRInt32 contentIndex; + GetContentIndex(contentIndex); + fprintf(out, "Text(%d)@%p", contentIndex, this); + return NS_OK; +} + +NS_IMETHODIMP +TextFrame::List(FILE* out, PRInt32 aIndent) const +{ + PRInt32 i; + for (i = aIndent; --i >= 0; ) fputs(" ", out); + + // Output the tag + ListTag(out); + nsIView* view; + GetView(view); + if (nsnull != view) { + fprintf(out, " [view=%p]", view); + } + + // Output the first/last content offset and prev/next in flow info + // XXX inefficient (especially for really large strings) + PRInt32 textLength; + const PRUnichar* cp = GetText(mContent, textLength); + + PRBool isComplete = (mContentOffset + mContentLength) == textLength; + fprintf(out, "[%d,%d,%c] ", + mContentOffset, mContentOffset+mContentLength-1, + isComplete ? 'T':'F'); + if (nsnull != mPrevInFlow) { + fprintf(out, "prev-in-flow=%p ", mPrevInFlow); + } + if (nsnull != mNextInFlow) { + fprintf(out, "next-in-flow=%p ", mNextInFlow); + } + + // Output the rect and state + out << mRect; + if (0 != mState) { + fprintf(out, " [state=%08x]", mState); + } + + // Output the text + fputs("<\n", out); + aIndent++; + + for (i = aIndent; --i >= 0; ) fputs(" ", out); + fputs("\"", out); + nsAutoString tmp; + ToCString(cp, mContentLength, tmp); + fputs(tmp, out); + fputs("\"\n", out); + + aIndent--; + for (i = aIndent; --i >= 0; ) fputs(" ", out); + fputs(">\n", out); + + return NS_OK; +} diff --git a/mozilla/layout/html/base/src/nsWBRFrame.cpp b/mozilla/layout/html/base/src/nsWBRFrame.cpp new file mode 100644 index 00000000000..20bb6a09ca1 --- /dev/null +++ b/mozilla/layout/html/base/src/nsWBRFrame.cpp @@ -0,0 +1,35 @@ +/* -*- 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.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. + */ +#include "nsHTMLParts.h" +#include "nsHTMLTagContent.h" +#include "nsFrame.h" +#include "nsHTMLIIDs.h" + +nsresult +NS_NewWBRFrame(nsIContent* aContent, + nsIFrame* aParentFrame, + nsIFrame*& aResult) +{ + nsIFrame* frame = nsnull; + nsresult rv = nsFrame::NewFrame(&frame, aContent, aParentFrame); + if (NS_OK != rv) { + return rv; + } + aResult = frame; + return NS_OK; +}