gecko-dev/lib/layout/laysel.c
1998-03-28 02:44:41 +00:00

7133 lines
198 KiB
C

/* -*- Mode: C++; tab-width: 8; 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 "xp.h"
#include "layout.h"
#include "laylayer.h"
#include "libi18n.h"
#include "edt.h"
#include "layers.h"
#include <ctype.h> /* For lo_CharacterClassOf */
#ifndef XP_TRACE
# define XP_TRACE(X) fprintf X
#endif
#ifdef TEST_16BIT
#define XP_WIN16
#endif /* TEST_16BIT */
#ifdef XP_WIN16
#define SIZE_LIMIT 32000
#endif /* XP_WIN16 */
#ifdef PROFILE
#pragma profile on
#endif
#ifdef DEBUG
void lo_ValidatePosition(MWContext *context, LO_Position* position);
void lo_ValidateSelection(MWContext *context, LO_Selection* selection);
void lo_ValidatePosition2(MWContext *context, LO_Element* element, int32 position);
void lo_ValidateSelection2(MWContext *context, LO_Element* elementB, int32 positionB, LO_Element* elementE, int32 positionE);
#define LO_ASSERT_POSITION(context, p) lo_ValidatePosition(context, p)
#define LO_ASSERT_SELECTION(context, s) lo_ValidateSelection(context, s)
#define LO_ASSERT_POSITION2(context, e, p) lo_ValidatePosition2(context, e, p)
#define LO_ASSERT_SELECTION2(context, eb, pb, ee, pe) lo_ValidateSelection2(context, eb, pb, ee, pe)
#else
#define LO_ASSERT_POSITION(context, p) {}
#define LO_ASSERT_SELECTION(context, s) {}
#define LO_ASSERT_POSITION2(context, e, p) {}
#define LO_ASSERT_SELECTION2(context, eb, pb, ee, pe) {}
#endif
static void
lo_bump_position(MWContext *context, lo_DocState *state,
LO_Element *sel, int32 pos,
LO_Element **new_sel, int32 *new_pos, Bool forward);
/*
* Utility functions to convert between insert point and selection end.
* Returns FALSE if the conversion can't be done.
* (Only happens when the insert point is off the beginning of the document, or the
* selection end is off the end of the document.) In those cases, the input position is
* returned unchanged.
*
*/
Bool lo_ConvertInsertPointToSelectionEnd(MWContext* context, lo_DocState * state,
LO_Element** element, int32* position);
Bool lo_ConvertSelectionEndToInsertPoint(MWContext* context, lo_DocState * state,
LO_Element** element, int32* position);
void lo_HighlightSelect(MWContext *context, lo_DocState *state,
LO_Element *start, int32 start_pos, LO_Element *end, int32 end_pos,
Bool on);
void lo_SetSelect(MWContext *context, lo_DocState *state,
LO_Element *start, int32 start_pos, LO_Element *end, int32 end_pos,
Bool on);
void
lo_StartNewSelection(MWContext *context, lo_DocState * state,
LO_Element* eptr, int32 position);
void
lo_ExtendSelectionToPosition2(MWContext *context, lo_TopState* top_state, lo_DocState *state, LO_Element* eptr, int32 position);
/* Makes sure *eptr points to an editable element. If *eptr doesn't, searches towards
* the end of the document.
* If no editiable element is found, the function
* returns FALSE.
*/
Bool
lo_EnsureEditableSearchNext(MWContext *context, lo_DocState *state, LO_Element** eptr);
Bool
lo_EnsureEditableSearchNext2(MWContext *context, lo_DocState *state, LO_Element** eptr, int32* ePositionPtr);
/* Same as lo_EnsureEditableSearchNext, except searches towards the start of document.
*/
Bool
lo_EnsureEditableSearchPrev(MWContext *context, lo_DocState *state, LO_Element** eptr);
Bool
lo_EnsureEditableSearchPrev2(MWContext *context, lo_DocState *state, LO_Element** eptr, int32* ePositionPtr);
Bool
lo_EnsureEditableSearch(MWContext *context, lo_DocState* state, LO_Position* p, Bool forward);
Bool
lo_EnsureEditableSearch2(MWContext *context, lo_DocState* state, LO_Element** eptr, int32* ePositionPtr, Bool forward);
Bool
lo_IsValidEditableInsertPoint2(MWContext *context, lo_DocState* state, LO_Element* eptr, int32 ePositionPtr);
/* Normalizes the selection, if editing is turned on. Returns TRUE if the selection
* is non empty.
*/
Bool lo_NormalizeSelection(MWContext *context);
/*
* Return FALSE if the selection point can't be normalized.
* (i.e. if it's at the end of the document.)
*/
Bool
lo_NormalizeSelectionPoint(MWContext *context, lo_DocState *state, LO_Element** pEptr, int32* pPosition);
void
lo_NormalizeSelectionEnd(MWContext *context, lo_DocState *state, LO_Element** pEptr, int32* pPosition);
int32
lo_GetTextAttrMask(LO_Element* eptr);
LO_AnchorData*
lo_GetAnchorData(LO_Element* eptr);
LO_Element*
lo_GetNeighbor(LO_Element* element, Bool forward);
int32
lo_GetElementEdge(LO_Element* element, Bool forward);
int32
lo_GetMaximumInsertPointPosition(LO_Element* eptr);
int32
lo_GetLastCharEndPosition(LO_Element* eptr);
int32
lo_GetLastCharBeginPosition(LO_Element* eptr);
int32
lo_IncrementPosition(LO_Element* eptr, int32 position);
int32
lo_DecrementPosition(LO_Element* eptr, int32 position);
Bool
lo_IsEndOfParagraph2(MWContext* context, LO_Element* element, int32 position);
Bool
lo_FindDocumentEdge(MWContext* context, lo_DocState *state, LO_Position* edge, Bool select, Bool forward);
Bool
lo_IsEdgeOfDocument2(MWContext* context, lo_DocState *state, LO_Element* element, int32 position, Bool forward);
Bool
lo_IsEdgeOfDocument(MWContext* context, lo_DocState *state, LO_Position* where, Bool forward);
void
lo_SetInsertPoint(MWContext *context, lo_TopState *top_state, LO_Element* eptr, int32 position, CL_Layer *layer);
/*
* Implements the UI policy for a click on an element. Returns TRUE if the result is an insertion point.
*/
Bool lo_ProcessClick(MWContext *context, lo_TopState *top_state, lo_DocState *state, LO_HitResult* result, Bool requireCaret, CL_Layer *layer);
Bool lo_ProcessDoubleClick(MWContext *context, lo_TopState *top_state, lo_DocState *state, LO_HitResult* result, CL_Layer *layer);
Bool lo_ProcessAnchorClick(MWContext *context, lo_TopState *top_state, lo_DocState *state, LO_HitResult* result);
Bool
lo_SelectAnchor(MWContext* context, lo_DocState *state, LO_Element* eptr);
void
lo_SetSelection(MWContext *context, LO_Selection* selection, Bool extendingStart);
void
lo_FullSetSelection(MWContext *context, lo_DocState * state,
LO_Element* start, int32 start_pos,
LO_Element* end, int32 end_pos, Bool extendStart);
/* Get various selection parts, expressed as insert points. */
void
lo_GetAnchorPoint(MWContext *context, lo_DocState * state, LO_Element** pElement, int32* pPosition);
void
lo_GetExtensionPoint(MWContext *context, lo_DocState * state, LO_Element** pElement, int32* pPosition);
Bool lo_FindBestPositionInTable(MWContext *context, lo_DocState* state, LO_TableStruct* pTable, int32 iDesiredX,
int32 iDesiredY, Bool bForward, int32* pRetX, int32 *pRetY );
Bool lo_FindClosestUpDown_Cell(MWContext *pContext, lo_DocState* state,
LO_CellStruct* cell, int32 x, int32 y,
Bool bForward, int32* ret_x, int32* ret_y);
LO_Element *
lo_search_element_list_WideMatch(MWContext *context, LO_Element *eptr, LO_Element* eEndPtr, int32 x, int32 y,
Bool bForward);
Bool lo_EditableElement( int iType )
{
if( iType == LO_TEXT
|| iType == LO_TEXTBLOCK
|| iType == LO_IMAGE
|| iType == LO_HRULE
|| iType == LO_LINEFEED
|| iType == LO_FORM_ELE )
{
return TRUE;
}
else
{
return FALSE;
}
}
PRIVATE
int32
lo_ElementToCharOffset2(MWContext *context, lo_DocState *state,
LO_Element *element, int32 x, int32* returnCharStart, int32* returnCharEnd)
{
LO_TextStruct *text_ele;
LO_TextInfo text_info;
int32 orig_len;
int32 xpos;
int32 cpos;
int32 start, end;
int16 charset;
int32 startX;
if ((element == NULL)||(element->type != LO_TEXT))
{
return(-1);
}
text_ele = (LO_TextStruct *)element;
if ((text_ele->text == NULL)||(text_ele->text_len <= 0))
{
return -1;
}
startX = text_ele->x + text_ele->x_offset;
xpos = x - startX;
if (xpos < 0)
{
return -1;
}
if (xpos >= text_ele->width && returnCharStart == NULL)
{
return lo_GetLastCharEndPosition(element);
}
start = 0;
end = lo_GetLastCharEndPosition(element);
/* Make a guess as to where to start searching, assuming characters are
* all roughly the same width.
*/
if ( text_ele->width > 0 )
{
cpos = xpos * text_ele->text_len / text_ele->width;
}
else
{
cpos = 0;
}
if (cpos > end)
{
cpos = end;
}
orig_len = text_ele->text_len;
charset = text_ele->text_attr->charset;
if (INTL_CharSetType(charset) == SINGLEBYTE)
{
while (start < end)
{
XP_ASSERT(cpos >= 0);
text_ele->text_len = (intn) cpos + 1;
FE_GetTextInfo(context, text_ele, &text_info);
if (xpos > text_info.max_width)
{
start = cpos + 1;
}
else
{
end = cpos;
}
cpos = (start + end) / 2;
}
/* Find character width by trickery */
text_ele->text_len = (intn) cpos;
FE_GetTextInfo(context, text_ele, &text_info);
if ( returnCharStart != NULL ) *returnCharStart = startX + text_info.max_width;
text_ele->text_len = (intn) cpos+1;
FE_GetTextInfo(context, text_ele, &text_info);
if ( returnCharEnd != NULL ) *returnCharEnd = startX + text_info.max_width;
}
else
{ /* slow multibyte finding, I feel sad we can't use the
beautiful single byte algorithm above
*/
int32 prev_xpos, last_xpos, last_pos;
char *tptr;
PA_LOCK(tptr, char *, text_ele->text);
cpos = 0;
prev_xpos = 0;
last_xpos = text_ele->width ;
last_pos = end ;
while ((start <= end) && (cpos <= end))
{
text_ele->text_len = (intn) cpos ;
FE_GetTextInfo(context, text_ele, &text_info);
if (xpos > text_info.max_width)
{
prev_xpos = text_info.max_width ;
start = text_ele->text_len;
}
else /* since it goes char by char, finding ends here */
{
last_xpos = text_info.max_width ;
end = cpos;
break ;
}
/* go to next char */
cpos = start +
(int32)INTL_CharLen(charset,
(unsigned char *)(tptr + start));
}
cpos = start ;
if (cpos > last_pos)
cpos = last_pos ;
PA_UNLOCK(text_ele->text);
if ( returnCharStart != NULL ) *returnCharStart = startX + prev_xpos;
if ( returnCharEnd != NULL) *returnCharEnd = startX + last_xpos;
}
text_ele->text_len = (intn) orig_len;
return(cpos);
}
/*
* For clients that don't care about the bounds of what they hit.
*/
PRIVATE
int32
lo_ElementToCharOffset(MWContext *context, lo_DocState *state,
LO_Element *element, int32 x)
{
return lo_ElementToCharOffset2(context, state, element, x, NULL, NULL);
}
void
LO_StartSelection(MWContext *context, int32 x, int32 y, CL_Layer *layer)
{
#if 0
int32 doc_id;
lo_TopState *top_state;
lo_DocState *state;
LO_Element *eptr;
int32 position;
int32 ret_x, ret_y;
/*
* Get the unique document ID, and retreive this
* documents layout state.
*/
doc_id = XP_DOCID(context);
top_state = lo_FetchTopState(doc_id);
if ((top_state == NULL)||(top_state->doc_state == NULL))
{
return;
}
state = top_state->doc_state;
LO_HighlightSelection(context, FALSE);
state->selection_start = NULL;
state->selection_start_pos = 0;
state->selection_end = NULL;
state->selection_end_pos = 0;
position = 0;
eptr = lo_XYToDocumentElement(context, state, x, y, FALSE, TRUE, TRUE,
&ret_x, &ret_y);
if (eptr != NULL)
{
position = lo_ElementToCharOffset(context, state, eptr, ret_x);
if (position < 0)
{
position = 0;
}
}
state->selection_new = eptr;
state->selection_new_pos = position;
#endif
LO_Click(context, x, y, ! EDT_IS_EDITOR(context),
layer); /* Only allow selections in editor */
}
/*
* Used for right-clicks.
*/
void LO_SelectObject( MWContext *context, int32 x, int32 y, CL_Layer *layer)
{
int32 doc_id;
lo_TopState *top_state;
lo_DocState *state;
LO_HitResult result;
/*
* Get the unique document ID, and retreive this
* documents layout state.
*/
doc_id = XP_DOCID(context);
top_state = lo_FetchTopState(doc_id);
if ((top_state == NULL)||(top_state->doc_state == NULL))
{
return;
}
state = top_state->doc_state;
LO_Hit(context, x, y, FALSE, &result, layer);
if ( ! lo_ProcessAnchorClick(context, top_state, state, &result ) ) {
lo_ProcessClick(context, top_state, state, &result, FALSE, layer );
}
}
void LO_StartSelectionFromElement( MWContext *context, LO_Element *eptr, int32 new_pos, CL_Layer *layer )
{
int32 doc_id;
lo_TopState *top_state;
lo_DocState *state;
/*
* Get the unique document ID, and retreive this
* documents layout state.
*/
doc_id = XP_DOCID(context);
top_state = lo_FetchTopState(doc_id);
if ((top_state == NULL)||(top_state->doc_state == NULL))
{
return;
}
state = top_state->doc_state;
if ( eptr ) {
if ( ! lo_EnsureEditableSearchPrev2(context, state, &eptr, &new_pos) )
{
XP_ASSERT(FALSE);
return;
}
LO_ASSERT_POSITION2(context, eptr, new_pos);
}
LO_HighlightSelection(context, FALSE);
state->selection_start = NULL;
state->selection_start_pos = 0;
state->selection_end = NULL;
state->selection_end_pos = 0;
state->selection_new = eptr;
state->selection_new_pos = new_pos;
state->selection_layer = layer;
}
#ifdef DEBUG
PRIVATE
void
LO_DUMP_SELECTIONSTATE(MWContext *context)
{
int32 doc_id;
lo_TopState *top_state;
lo_DocState *state;
/*
* Get the unique document ID, and retreive this
* documents layout state.
*/
doc_id = XP_DOCID(context);
top_state = lo_FetchTopState(doc_id);
if ((top_state == NULL)||(top_state->doc_state == NULL))
{
return;
}
state = top_state->doc_state;
XP_TRACE(("selection 0x%08x %d 0x%08x %d new 0x%08x %d extending_start %d\n",
state->selection_start,
state->selection_start_pos,
state->selection_end,
state->selection_end_pos,
state->selection_new,
state->selection_new_pos,
state->extending_start));
}
PRIVATE
void
LO_DUMP_INSERT_POINT(char* s, LO_Element* element, int32 position)
{
if ( element )
{
XP_TRACE(("%s %d:%d.%d ", s, element->type, element->lo_any.ele_id, position));
}
else
{
XP_TRACE(("%s - NIL", s));
}
}
PRIVATE
void
LO_DUMP_POSITION(char* s, LO_Position* position)
{
LO_DUMP_INSERT_POINT(s, position->element, position->position);
}
PRIVATE
void
LO_DUMP_SELECTION2(char* s, LO_Element* a, int32 ap, LO_Element* b, int32 bp)
{
/* const char* kElementCodes = "?tlhi?????????????????";*/
XP_TRACE(("%s %d:%d.%d %d:%d.%d ", s,
a->type, a->lo_any.ele_id, ap,
b->type, b->lo_any.ele_id, bp));
}
PRIVATE
void
LO_DUMP_SELECTION(char* s, LO_Selection* selection)
{
LO_DUMP_SELECTION2(s, selection->begin.element, selection->begin.position,
selection->end.element, selection->end.position);
}
PRIVATE
void
LO_DUMP_HIT_RESULT(LO_HitResult* result)
{
switch ( result->type )
{
case LO_HIT_UNKNOWN:
XP_TRACE(("Unknown"));
break;
case LO_HIT_LINE:
{
char* s;
switch ( result->lo_hitLine.region )
{
case LO_HIT_LINE_REGION_BEFORE:
{
s = "before line";
}
break;
case LO_HIT_LINE_REGION_AFTER:
{
s = "after line";
}
break;
default:
s = "LO_HIT_LINE Bad region";
break;
}
LO_DUMP_SELECTION(s, & result->lo_hitLine.selection);
}
break;
case LO_HIT_ELEMENT:
{
char* s;
switch ( result->lo_hitElement.region )
{
case LO_HIT_ELEMENT_REGION_BEFORE:
{
s = "before element";
}
break;
case LO_HIT_ELEMENT_REGION_MIDDLE:
{
s = "middle element";
}
break;
case LO_HIT_ELEMENT_REGION_AFTER:
{
s = "after element";
}
break;
default:
{
s = "LO_HIT_ELEMENT unknown region";
}
break;
}
LO_DUMP_POSITION(s, & result->lo_hitElement.position);
}
break;
default:
{
XP_TRACE(("LO_HIT unknown result "));
}
break;
}
}
#endif
Bool
LO_IsSelected(MWContext *context)
{
int32 doc_id;
lo_TopState *top_state;
lo_DocState *state;
/*
* Get the unique document ID, and retreive this
* documents layout state.
*/
doc_id = XP_DOCID(context);
top_state = lo_FetchTopState(doc_id);
if ((top_state == NULL)||(top_state->doc_state == NULL))
{
return FALSE;
}
state = top_state->doc_state;
return (state->selection_start != NULL);
}
Bool
LO_IsSelectionStarted(MWContext *context)
{
int32 doc_id;
lo_TopState *top_state;
lo_DocState *state;
/*
* Get the unique document ID, and retreive this
* documents layout state.
*/
doc_id = XP_DOCID(context);
top_state = lo_FetchTopState(doc_id);
if ((top_state == NULL)||(top_state->doc_state == NULL))
{
return FALSE;
}
state = top_state->doc_state;
return (state->selection_new != NULL);
}
PRIVATE
void lo_JiggleToMakeEditable( MWContext *context,
lo_DocState *state,
LO_Element** ppElement,
int32 *pPosition,
Bool bForward)
{
/* If it's not an editable element, move to find one that is.
* We don't call the ensure routines because we don't want to
* select end-of-paragraph linefeeds here.
*
*/
LO_Element* element = *ppElement;
int32 position = *pPosition;
if ( (! EDT_IS_EDITOR(context)) || (element && element->lo_any.edit_element) ) {
return;
}
while( element && ! element->lo_any.edit_element ){
LO_Element* newElement;
int32 newPosition;
lo_bump_position(context, state, element, position, &newElement, &newPosition, bForward);
if ( element == newElement && position == newPosition ) {
/* We got stuck */
element = NULL;
break;
}
element = newElement;
position = newPosition;
}
if ( ! element ) {
/* Ran off end of document */
element = *ppElement;
position = *pPosition;
while( element && ! element->lo_any.edit_element ){
LO_Element* newElement;
int32 newPosition;
lo_bump_position(context, state, element, position, &newElement, &newPosition, !bForward);
if ( element == newElement && position == newPosition ) {
/* We got stuck */
element = NULL;
break;
}
element = newElement;
position = newPosition;
}
}
if ( element ) {
*ppElement = element;
*pPosition = lo_GetElementEdge(element, !bForward);
}
}
/* This is only called from the editor. As a convienience to the
* editor, we return the selection as a 1/2 open selection. That
* means we convert the selection end into an insert point.
*/
void LO_GetSelectionEndPoints( MWContext *context,
LO_Element** ppStart,
intn *pStartOffset,
LO_Element** ppEnd,
intn *pEndOffset,
Bool* pbFromStart,
Bool* pbSingleItemSelection )
{
int32 doc_id;
lo_TopState *top_state;
lo_DocState *state;
LO_Element* startElement;
int32 startPosition;
LO_Element* endElement;
int32 endPosition;
/*
* Get the unique document ID, and retreive this
* documents layout state.
*/
doc_id = XP_DOCID(context);
top_state = lo_FetchTopState(doc_id);
if ((top_state == NULL)||(top_state->doc_state == NULL))
{
return;
}
state = top_state->doc_state;
startElement = state->selection_start;
startPosition = state->selection_start_pos;
/* If it's not an editable element, move backward to find one that is.
* We don't call the ensure routines because we don't want to
* select end-of-paragraph linefeeds here.
*
* But now that we have tables, we need to bump.
*/
lo_JiggleToMakeEditable( context, state, &startElement, &startPosition, FALSE);
if( ppStart ) *ppStart = startElement;
if( pStartOffset ) *pStartOffset = startPosition;
/* Convert the selection end into an insert point. */
endElement = state->selection_end;
endPosition = state->selection_end_pos;
if ( endElement )
{
lo_ConvertSelectionEndToInsertPoint(context, state, &endElement, &endPosition);
}
/* If it's not an editable element, move forward to find one that is.
* We don't call the ensure routines because we don't want to
* select end-of-paragraph linefeeds here.
*/
lo_JiggleToMakeEditable( context, state, &endElement, &endPosition, TRUE);
if( ppEnd ) *ppEnd = endElement;
if( pEndOffset ) *pEndOffset = endPosition;
if( pbFromStart ) *pbFromStart = state->extending_start;
/*
* the editor needs to know if the select is just a single thing like a
* image or a HRULE.
*/
if( pbSingleItemSelection ) *pbSingleItemSelection = (
startElement
&& startElement->type != LO_TEXT
&& startElement->type != LO_TEXTBLOCK
&& startElement == endElement );
/*
* For lloyd's edification, if we have a single item seleciton, start_pos
* is 0 and end_pos is 0 or 1
*/
XP_ASSERT( pbSingleItemSelection && *pbSingleItemSelection
?
startPosition == 0
&& ( endPosition == 0
|| endPosition == 1 )
:
TRUE);
}
void LO_GetSelectionNewPoint( MWContext *context,
LO_Element** ppNew,
intn *pNewOffset)
{
int32 doc_id;
lo_TopState *top_state;
lo_DocState *state;
/*
* Get the unique document ID, and retreive this
* documents layout state.
*/
doc_id = XP_DOCID(context);
top_state = lo_FetchTopState(doc_id);
if ((top_state == NULL)||(top_state->doc_state == NULL))
{
return;
}
state = top_state->doc_state;
if( ppNew ) *ppNew = state->selection_new;
if( pNewOffset ) *pNewOffset = state->selection_new_pos;
}
static intn
lo_compare_selections(LO_Element *sel1, int32 pos1,
LO_Element *sel2, int32 pos2)
{
if ((sel1 == NULL)||(sel2 == NULL))
{
if (sel1 == sel2)
{
return(0);
}
else if (sel1 == NULL)
{
return(-1);
}
else
{
return(1);
}
}
if (sel1->lo_any.ele_id < sel2->lo_any.ele_id)
{
return(-1);
}
else if (sel1->lo_any.ele_id > sel2->lo_any.ele_id)
{
return(1);
}
else if (sel1->lo_any.ele_id == sel2->lo_any.ele_id)
{
if (pos1 < pos2)
{
return(-1);
}
else if (pos1 > pos2)
{
return(1);
}
else if (pos1 == pos2)
{
return(0);
}
}
#ifdef DEBUG
assert (FALSE);
#endif
return(-1);
}
PRIVATE
intn
lo_ComparePositions(LO_Position* a, LO_Position* b)
{
return lo_compare_selections( a->element, a->position, b->element, b->position);
}
PRIVATE
LO_Element*
lo_BoundaryJumpingNext(MWContext *context, lo_DocState *state, LO_Element *eptr)
{
int32 last_id;
LO_Element *cell_parent;
/*
* If no next element, see if we need to jump the cell wall.
*/
Bool success=FALSE;
while(!success)
{
LO_Element* next = eptr->lo_any.next;
last_id = eptr->lo_any.ele_id;
cell_parent = NULL;
while ( eptr && ! next )
{
LO_Element* oldEptr = eptr;
eptr = lo_JumpCellWall(context, state, eptr);
if ( ! eptr || eptr == oldEptr )
break; /* Ran off the end of the document */
next = eptr->lo_any.next;
}
eptr = next;
/*
* When we walk onto a cell,
* we need to walk into it if
* it isn't empty.
*/
if ((eptr != NULL)&&
(eptr->type == LO_CELL)&&
(eptr->lo_cell.cell_list != NULL))
{
cell_parent = eptr;
eptr = eptr->lo_cell.cell_list;
}
/*
* Heuristic: For some difficult tables, you may come back
* to the same cell you left instead of progressing.
* If so, try manually moving the cell forward.
*/
if ((eptr != NULL)&&(eptr->lo_any.ele_id == last_id)&&
(cell_parent != NULL))
{
LO_Element *guess;
guess = cell_parent->lo_any.next;
/*
* If our guessed next element after the parent cell is
* a non-empty cell, make the first element in that
* cell our new element.
*/
if ((guess != NULL)&&(guess->type == LO_CELL)&&
(guess->lo_cell.cell_list != NULL))
{
eptr = guess->lo_cell.cell_list;
}
}
/*
* We don't want infinite loops.
* If the element ID hasn't progressed, something
* serious is wrong, and we should punt.
*/
if ((eptr != NULL)&&(eptr->lo_any.ele_id <= last_id))
{
#ifdef DEBUG
XP_TRACE(("Cell Jump loop avoidance 1\n"));
#endif /* DEBUG */
return NULL;
}
success=TRUE; /* unless we detect a LO_TEXTBLOCK */
/* check textblock until not textblock leave prev as null if you hit
null */
if (( next && eptr->type == LO_TEXTBLOCK ) || ( next && eptr->type == LO_DESCTITLE ))
{
success=FALSE;
}
}
return eptr;
}
/*
* We changed celljumping to boundary jumping to include type 19/LO_TEXTBLOCK as a type to skip past. anthonyd
*/
PRIVATE
LO_Element*
lo_BoundaryJumpingPrev(MWContext *context, lo_DocState *state, LO_Element *eptr)
{
/*
* If no previous element, see if we need to jump the cell wall.
*/
LO_Element* prev;
Bool success=FALSE;
while(!success)
{
prev = eptr->lo_any.prev;
while ( eptr && ! prev )
{
LO_Element* oldEptr = eptr;
eptr = lo_JumpCellWall(context, state, eptr);
if ( ! eptr || eptr == oldEptr )
break; /* Ran off the front of the document */
prev = eptr->lo_any.prev;
if ( ! prev )
continue; /* Ran off the front of the cell/document */
if ( prev->type == LO_TABLE )
{
/* That was the first cell of the table. */
eptr = prev;
prev = eptr->lo_any.prev;
/* Repeat the check that was made above since we've moved eptr. */
if ( eptr == oldEptr )
break;
}
}
success=TRUE; /* successfull until textblock check */
/*
* When we walk onto a cell,
* we need to walk into it if
* it isn't empty.
*/
if ((prev != NULL)&&
(prev->type == LO_CELL)&&
(prev->lo_cell.cell_list_end != NULL))
{
prev = prev->lo_cell.cell_list_end;
}
eptr = prev;
/* check textblock until not textblock leave prev as null if you hit
null */
if (( prev && eptr->type == LO_TEXTBLOCK ) || ( prev && eptr->type == LO_DESCTITLE ))
{
success=FALSE;
}
}
return eptr;
}
static void
lo_bump_position(MWContext *context, lo_DocState *state,
LO_Element *sel, int32 pos,
LO_Element **new_sel, int32 *new_pos, Bool forward)
{
LO_Element *eptr;
int32 position;
eptr = sel;
position = pos;
/*
* Moving forward one selection position
*/
if (forward != FALSE)
{
/*
* If it is not a text element, just move
* to the next element.
*/
if (eptr->lo_any.type != LO_TEXT)
{
eptr = lo_BoundaryJumpingNext(context, state, eptr);
if (eptr == NULL)
{
eptr = sel;
}
/*
* Else start at the beginning of the next element
*/
else
{
position = 0;
}
}
/*
* Else this is a text element, so check moving
* forward within the element.
*/
else
{
intn maxPosition = lo_GetLastCharBeginPosition(eptr);
/*
* If we are already at the end of the text
* element we are back to moving on to the
* next element.
*/
if (position < maxPosition)
{
position = lo_IncrementPosition(eptr, position);
}
else
{
eptr = lo_BoundaryJumpingNext(context, state, eptr);
if (eptr == NULL)
{
eptr = sel;
}
/*
* Else start at the beginning of the next element
*/
else
{
position = 0;
}
}
}
}
/*
* Else moving backward one selection position
*/
else
{
/*
* If it is not a text element, just move
* to the previous element.
*/
if (eptr->lo_any.type != LO_TEXT)
{
eptr = lo_BoundaryJumpingPrev(context, state, eptr);
/*
* If no previous element, don't move it.
*/
if (eptr == NULL)
{
eptr = sel;
}
/*
* Else start at the end of the previous element.
* (only matters for text elements)
*/
else
{
position = lo_GetLastCharBeginPosition(eptr);
}
}
/*
* Else this is a text element, so check moving
* backward within the element.
*/
else
{
/*
* If we are already at the beginning of the text
* element we are back to moving back to the
* previous element.
*/
if (position == 0)
{
eptr = lo_BoundaryJumpingPrev(context, state, eptr);
/*
* If no previous element, don't move it.
*/
if (eptr == NULL)
{
eptr = sel;
}
/*
* Else start at the end of the previous
* element.
* (only matters for text elements)
*/
else
{
position = lo_GetLastCharBeginPosition(eptr);
}
}
/*
* This is the easy case, move one position
* backward in the selected text element.
*/
else
{
position = lo_DecrementPosition(eptr, position);
}
}
}
*new_sel = eptr;
*new_pos = position;
}
PRIVATE
Bool
lo_BumpPosition(MWContext *context, lo_DocState *state, LO_Position* position, Bool forward)
{
LO_Element* newElement;
int32 newPosition;
lo_bump_position(context, state, position->element, position->position,
&newElement, &newPosition, forward);
if ( newElement == NULL
|| lo_compare_selections(newElement, newPosition, position->element, position->position) == 0)
{
/* We ran out of room. */
return FALSE;
}
position->element = newElement;
position->position = newPosition;
return TRUE;
}
PRIVATE
Bool lo_ValidEditableElement(MWContext *context, LO_Element* eptr)
{
return lo_EditableElement(eptr->type) &&
( (!EDT_IS_EDITOR(context) ) ||
(eptr->lo_any.edit_element != 0 &&
eptr->lo_any.edit_offset >= 0));
}
PRIVATE
Bool lo_ValidEditableElementIncludingParagraphMarks(MWContext *context, LO_Element* pElement)
{
return lo_ValidEditableElement(context, pElement)
|| (pElement->type== LO_LINEFEED
&& pElement->lo_linefeed.break_type == LO_LINEFEED_BREAK_PARAGRAPH);
}
/* Moves by one editable position forward or backward. Returns FALSE if it couldn't.
* Assumes that position is normalized, doesn't denormalize it.
*/
PRIVATE
Bool
lo_BumpNormalizedEditablePosition(MWContext *context, lo_DocState *state,
LO_Element **pEptr, int32 *pPosition, Bool direction)
{
LO_Element* newEptr;
int32 newPosition;
if ( ! ( pEptr && *pEptr && pPosition ) )
{
XP_ASSERT(FALSE);
return FALSE;
}
newEptr = *pEptr;
newPosition = *pPosition;
{
int32 length = lo_GetElementLength(newEptr);
if ( ! (newPosition >= 0 && ( newPosition < length || length == 0 ) ) )
{
XP_ASSERT(FALSE);
return FALSE;
}
}
do {
LO_Element* oldEptr = newEptr;
int32 oldPosition = newPosition;
lo_bump_position(context, state, newEptr, newPosition,
&newEptr, &newPosition, direction);
if ( newEptr == NULL
|| lo_compare_selections(newEptr, newPosition, oldEptr, oldPosition) == 0)
{
/* We ran out of room. */
return FALSE;
}
} while ( ! lo_ValidEditableElementIncludingParagraphMarks(context, newEptr) );
*pEptr = newEptr;
*pPosition = newPosition;
return TRUE;
}
/* This function is only intended for the internal use of
* lo_BumpEditablePosition. It handles the quick case where
* the position doesn't cross over the object boundary.
*/
PRIVATE
Bool
lo_QuickBumpEditablePosition(MWContext *context, lo_DocState *state,
LO_Element **pEptr, int32 *pPosition, Bool direction)
{
if ( direction )
{
if ( *pPosition < lo_GetMaximumInsertPointPosition(*pEptr) )
{
/* Easy case: Moving forward within an element. */
*pPosition = lo_IncrementPosition(*pEptr, *pPosition);
/* XP_TRACE(("new position = %d", *pPosition)); */
return TRUE;
}
}
else
{
if ( *pPosition > 0 )
{
/* Easy case: Moving backward within an element. */
*pPosition = lo_DecrementPosition(*pEptr, *pPosition);
/* XP_TRACE(("new position = %d", *pPosition)); */
return TRUE;
}
}
return FALSE;
}
/* Moves by one editable position forward or backward. Returns FALSE if it couldn't.
* position can be denormalized on entry and may be denormalized on exit.
*/
Bool
lo_BumpEditablePosition(MWContext *context, lo_DocState *state,
LO_Element **pEptr, int32 *pPosition, Bool direction)
{
LO_Element* newEptr;
int32 newPosition;
if ( ! ( pEptr && *pEptr && pPosition ) )
{
XP_ASSERT(FALSE);
return FALSE;
}
newEptr = *pEptr;
newPosition = *pPosition;
if ( ! lo_QuickBumpEditablePosition(context, state, &newEptr, &newPosition, direction ) )
{
/* Normalize, and try again. */
if ( ! lo_NormalizeSelectionPoint(context, state, &newEptr, &newPosition) )
return FALSE;
if ( ! lo_QuickBumpEditablePosition(context, state, &newEptr, &newPosition, direction ) )
{
/* Now that it's normalized, try the heavy-duty bump. */
if ( ! lo_BumpNormalizedEditablePosition(context, state, &newEptr, &newPosition, direction) )
{
return FALSE;
}
}
}
*pEptr = newEptr;
*pPosition = newPosition;
return TRUE;
}
PRIVATE
Bool
lo_BumpEditablePositionForward(MWContext *context, lo_DocState *state,
LO_Element **pEptr, int32 *pPosition)
{
return lo_BumpEditablePosition(context, state, pEptr, pPosition, TRUE);
}
PRIVATE
Bool
lo_BumpEditablePositionBackward(MWContext *context, lo_DocState *state,
LO_Element **pEptr, int32 *pPosition)
{
return lo_BumpEditablePosition(context, state, pEptr, pPosition, FALSE);
}
PRIVATE
void
lo_ChangeSelection(MWContext *context, lo_DocState *state,
LO_Element *changed_start, LO_Element *changed_end,
int32 changed_start_pos, int32 changed_end_pos)
{
LO_Element *eptr;
int32 position;
LO_Element *start, *end;
int32 start_pos, end_pos;
intn compare_start;
intn compare_end;
/*
* Handle case where there's no existing selection
*/
if ( state->selection_start == NULL )
{
lo_HighlightSelect(context, state,
changed_start, changed_start_pos,
changed_end, changed_end_pos, TRUE);
return;
}
start = state->selection_start;
start_pos = state->selection_start_pos;
end = state->selection_end;
end_pos = state->selection_end_pos;
lo_NormalizeSelectionEnd(context, state, &end, &end_pos);
/*
* If the end of the old selection is less than
* the start of the new one. Or the end of the new one is
* less than the start of the old one.
* Erase the old one, draw the new one.
*/
if ((lo_compare_selections(end, end_pos, changed_start,
changed_start_pos) < 0)||
(lo_compare_selections(changed_end, changed_end_pos, start,
start_pos) < 0))
{
lo_HighlightSelect(context, state,
start, start_pos, end, end_pos, FALSE);
lo_HighlightSelect(context, state,
changed_start, changed_start_pos,
changed_end, changed_end_pos, TRUE);
}
else
{
compare_start = lo_compare_selections(changed_start,
changed_start_pos, start, start_pos);
compare_end = lo_compare_selections(changed_end,
changed_end_pos, end, end_pos);
/*
* If the start position has moved either draw more,
* or erase some of the old.
*/
if (compare_start < 0)
{
lo_HighlightSelect(context, state,
changed_start, changed_start_pos,
start, start_pos, TRUE);
}
else if (compare_start > 0)
{
lo_bump_position(context, state,
changed_start, changed_start_pos,
&eptr, &position, FALSE);
lo_HighlightSelect(context, state,
start, start_pos, eptr, position, FALSE);
}
/*
* If the end position has moved either draw more,
* or erase some of the old.
*/
if (compare_end > 0)
{
lo_HighlightSelect(context, state, end, end_pos,
changed_end, changed_end_pos, TRUE);
}
else if (compare_end < 0)
{
lo_bump_position(context, state,
changed_end, changed_end_pos,
&eptr, &position, TRUE);
lo_HighlightSelect(context, state,
eptr, position, end, end_pos, FALSE);
}
}
}
Bool LO_SelectElement( MWContext *context, LO_Element *eptr,
int32 position, Bool bFromStart )
{
int32 doc_id;
lo_TopState *top_state;
lo_DocState *state;
/*
* Get the unique document ID, and retreive this
* documents layout state.
*/
doc_id = XP_DOCID(context);
top_state = lo_FetchTopState(doc_id);
if ((top_state == NULL)||(top_state->doc_state == NULL))
{
return FALSE;
}
state = top_state->doc_state;
/* If we've been handed a non-editable item, go to the previous editable item */
if ( ! lo_EnsureEditableSearchPrev2(context, state, &eptr, &position) )
{
return FALSE;
}
if ( lo_IsEdgeOfDocument2(context, state, eptr, position, ! bFromStart ) )
{
return FALSE;
}
/* If we're selecting backwards, actually select one lower */
if ( bFromStart )
{
if ( ! lo_BumpEditablePositionBackward(context, state,
&eptr, &position) ) return FALSE;
}
/* End is the same as the beginning. */
lo_FullSetSelection(context, state, eptr, position, eptr, position, bFromStart);
return TRUE;
}
/* Internal routine that should not be exported */
void LO_ExtendSelectionFromElement( MWContext *context, LO_Element *eptr,
int32 position, Bool bFromStart )
{
int32 doc_id;
lo_TopState *top_state;
lo_DocState *state;
LO_Element *changed_start, *changed_end;
int32 changed_start_pos, changed_end_pos;
/*
* Get the unique document ID, and retreive this
* documents layout state.
*/
doc_id = XP_DOCID(context);
top_state = lo_FetchTopState(doc_id);
if ((top_state == NULL)||(top_state->doc_state == NULL))
{
return;
}
state = top_state->doc_state;
/*
* If we have started a new selection, replace the old one with
* this new one before extending
*/
if (state->selection_new != NULL)
{
lo_StartNewSelection(context, state, eptr, position);
return;
}
/*
* If we somehow got here without having any selection or started
* selection, start it now where we are.
*/
if (state->selection_start == NULL)
{
state->selection_start = eptr;
state->selection_start_pos = position;
state->selection_end = state->selection_start;
state->selection_end_pos = state->selection_start_pos;
state->extending_start = FALSE;
}
/*
* By some horrendous bug the start was set and the end was not,
* and there was no newly started selection. Correct this now.
*/
if (state->selection_end == NULL)
{
state->selection_end = state->selection_start;
state->selection_end_pos = state->selection_start_pos;
state->extending_start = FALSE;
}
/*
* Extend the same side we extended last time.
*/
if (state->extending_start != FALSE)
{
changed_start = eptr;
changed_start_pos = position;
changed_end = state->selection_end;
changed_end_pos = state->selection_end_pos;
}
else
{
changed_start = state->selection_start;
changed_start_pos = state->selection_start_pos;
changed_end = eptr;
changed_end_pos = position;
}
/*
* If we crossed our start and end positions, switch
* them, and switch which end we are extending from.
*/
if (changed_start->lo_any.ele_id > changed_end->lo_any.ele_id)
{
eptr = changed_start;
position = changed_start_pos;
changed_start = changed_end;
changed_start_pos = changed_end_pos;
changed_end = eptr;
changed_end_pos = position;
if (state->extending_start != FALSE)
{
state->extending_start = FALSE;
}
else
{
state->extending_start = TRUE;
}
}
else if ((changed_start->lo_any.ele_id == changed_end->lo_any.ele_id)&&
(changed_start_pos > changed_end_pos))
{
position = changed_start_pos;
changed_start_pos = changed_end_pos;
changed_end_pos = position;
if (state->extending_start != FALSE)
{
state->extending_start = FALSE;
}
else
{
state->extending_start = TRUE;
}
}
lo_ChangeSelection(context, state, changed_start, changed_end,
changed_start_pos, changed_end_pos);
lo_SetSelect(context, state, changed_start, changed_start_pos,
changed_end, changed_end_pos, TRUE);
state->selection_start = changed_start;
state->selection_start_pos = changed_start_pos;
state->selection_end = changed_end;
state->selection_end_pos = changed_end_pos;
/*
* Cannot have a new selection after an extend.
*/
state->selection_new = NULL;
state->selection_new_pos = 0;
#if 0
/*
* If the beginning and the endpoints were not the beginning and end points
* then the editors notion of bFromStart is backwards.
*/
if( state->extending_start ){
state->extending_start = !bFromStart;
}
else {
state->extending_start = bFromStart;
}
#endif
}
void LO_SelectRegion( MWContext *context, LO_Element *begin, int32 beginPosition,
LO_Element* end, int32 endPosition, Bool fromStart , Bool forward)
{
int32 doc_id;
lo_TopState *top_state;
lo_DocState *state;
/*
* Get the unique document ID, and retreive this
* documents layout state.
*/
doc_id = XP_DOCID(context);
top_state = lo_FetchTopState(doc_id);
if ((top_state == NULL)||(top_state->doc_state == NULL))
{
return;
}
state = top_state->doc_state;
if ( ! begin || !end ) {
XP_ASSERT(FALSE);
return;
}
#if 0
lo_NormalizeSelectionPoint(context, state, &begin, &beginPosition);
lo_NormalizeSelectionPoint(context, state, &end, &endPosition);
#endif
#if 0
XP_TRACE(("begin %d %d %d end %d %d %d start %d forward %d",
begin->type, begin->lo_any.ele_id, beginPosition,
end->type, end->lo_any.ele_id, endPosition,
fromStart, forward));
#endif
/* Normalize end-points for end-of line conditions */
if ( fromStart )
{
if ( !forward && begin->type == LO_LINEFEED
&& beginPosition == 0 )
{
lo_BumpEditablePositionBackward(context, state, &begin, &beginPosition);
}
}
else
{
LO_Element* prev;
if ( forward && endPosition == 0 &&
( end->type == LO_LINEFEED ||
((prev = lo_BoundaryJumpingPrev(context, state, end)) != NULL && prev->type == LO_LINEFEED ) ) )
{
lo_BumpEditablePositionForward(context, state, &end, &endPosition);
}
}
LO_StartSelectionFromElement( context, begin, beginPosition, NULL );
LO_ExtendSelectionFromElement( context, end, endPosition, fromStart );
state->extending_start = fromStart;
}
void
lo_SetSelection(MWContext *context, LO_Selection* selection, Bool extendingStart)
{
int32 doc_id;
lo_TopState *top_state;
lo_DocState *state;
/*
* Get the unique document ID, and retreive this
* documents layout state.
*/
doc_id = XP_DOCID(context);
top_state = lo_FetchTopState(doc_id);
if ((top_state == NULL)||(top_state->doc_state == NULL))
{
return;
}
state = top_state->doc_state;
lo_FullSetSelection( context, state,
selection->begin.element,
selection->begin.position,
selection->end.element,
selection->end.position, extendingStart);
}
void
lo_FullSetSelection(MWContext *context, lo_DocState * state,
LO_Element* start, int32 start_pos,
LO_Element* end, int32 end_pos,
Bool extendFromStart)
{
LO_Element* normalizedStart = start;
int32 normalizedStartPosition = start_pos;
LO_Element* normalizedEnd = end;
int32 normalizedEndPosition = end_pos;
LO_ASSERT_POSITION2(context, start, start_pos);
LO_ASSERT_POSITION2(context, end, end_pos);
/* The non-editor portions of layout can't deal with a
* de-normalized positions. So we normalize it here.
*/
/* LO_DUMP_SELECTION2("FullSetSelection", start, start_pos, end, end_pos); */
#if 0
lo_NormalizeSelectionPoint(context, state, &normalizedStart, &normalizedStartPosition);
#endif
if ( lo_GetElementLength(normalizedStart) <= normalizedStartPosition )
{
normalizedStart = lo_BoundaryJumpingNext(context, state, normalizedStart);
normalizedStartPosition = 0;
}
lo_NormalizeSelectionEnd(context, state, &normalizedEnd, &normalizedEndPosition);
/* If we've been handed a bad selection, just return. */
if ( ! ( normalizedStart && normalizedEnd &&
lo_compare_selections(normalizedStart, normalizedStartPosition,
normalizedEnd, normalizedEndPosition) <= 0 ) )
{
XP_TRACE(("lo_FullSetSelection: bad selection."));
#ifdef DEBUG_EDIT_LAYOUT
XP_ASSERT(FALSE);
#endif
return;
}
lo_ChangeSelection(context, state, normalizedStart, normalizedEnd,
normalizedStartPosition, normalizedEndPosition);
lo_SetSelect(context, state, normalizedStart, normalizedStartPosition,
normalizedEnd, normalizedEndPosition, TRUE);
state->selection_start = start;
state->selection_start_pos = start_pos;
state->selection_end = end;
state->selection_end_pos = end_pos;
state->extending_start = extendFromStart;
/*
* Cannot have a new selection after an extend.
*/
state->selection_new = NULL;
state->selection_new_pos = 0;
}
/* Get the selection edges, expressed as insert points. */
PRIVATE
void
lo_GetSelectionEdge(MWContext *context, lo_DocState * state, LO_Element** pElement, int32* pPosition, Bool bForward)
{
if(state->selection_start && state->selection_end )
{
if ( !bForward )
{
*pElement = state->selection_start;
*pPosition = state->selection_start_pos;
}
else
{
*pElement = state->selection_end;
*pPosition = state->selection_end_pos;
lo_ConvertSelectionEndToInsertPoint(context, state, pElement, pPosition);
}
}
else
{
/* Use insert point */
*pElement = state->selection_new;
*pPosition = state->selection_new_pos;
}
}
void
lo_GetAnchorPoint(MWContext *context, lo_DocState * state, LO_Element** pElement, int32* pPosition)
{
lo_GetSelectionEdge(context, state, pElement, pPosition, state->extending_start);
}
void
lo_GetExtensionPoint(MWContext *context, lo_DocState * state, LO_Element** pElement, int32* pPosition)
{
lo_GetSelectionEdge(context, state, pElement, pPosition, !state->extending_start);
}
Bool
lo_IsEndOfParagraph2(MWContext *context, LO_Element* element, int32 position)
{
if ( ! element )
{
XP_ASSERT( FALSE );
return FALSE;
}
/* The browser doesn't set break_type, so every line is a paragraph to it. */
if ( element->type == LO_LINEFEED &&
( ! EDT_IS_EDITOR(context)
|| element->lo_linefeed.break_type == LO_LINEFEED_BREAK_PARAGRAPH)) {
return TRUE;
}
return FALSE;
}
PRIVATE
Bool
lo_IsEndOfParagraph(MWContext *context, LO_Position* position)
{
XP_ASSERT( position );
return lo_IsEndOfParagraph2(context, position->element, position->position);
}
PRIVATE
Bool
lo_IsHardBreak2(MWContext *context, LO_Element* element, int32 position)
{
if ( ! element )
{
XP_ASSERT( FALSE );
return FALSE;
}
/* The browser doesn't set break_type, so every line is a hard break to it. */
if ( element->type == LO_LINEFEED &&
( ! EDT_IS_EDITOR(context)
|| element->lo_linefeed.break_type != LO_LINEFEED_BREAK_SOFT)) {
return TRUE;
}
return FALSE;
}
PRIVATE
Bool
lo_ExtendToIncludeHardBreak(MWContext* context, lo_DocState *state, LO_Selection* selection)
{
/* If this is a selection that ends in a hard break, extend the
* selection to include the hard break.
*/
Bool result = FALSE;
if ( EDT_IS_EDITOR(context) )
{
LO_Position* end = &selection->end;
if ( lo_IsHardBreak2(context, end->element,0) )
{
result = TRUE; /* Already at a break character. */
}
else
{
if ( end->position >= lo_GetLastCharEndPosition(end->element) )
{
LO_Element* eol = lo_BoundaryJumpingNext(context, state, end->element);
if ( lo_IsHardBreak2(context, eol,0) )
{
/* Extend to include the paragraph end element */
end->element = eol;
end->position = 0;
result = TRUE;
}
}
}
}
return result;
}
#ifdef DEBUG
void
lo_ValidatePosition(MWContext *context, LO_Position* position)
{
XP_ASSERT( position );
if ( position ) {
lo_ValidatePosition2(context, position->element, position->position);
}
}
void
lo_ValidatePosition2(MWContext *context, LO_Element* element, int32 position)
{
int32 length;
XP_ASSERT( element );
XP_ASSERT( context );
if ( context && element )
{
length = lo_GetElementLength(element);
XP_ASSERT( 0 <= position );
XP_ASSERT( position <= length );
if ( EDT_IS_EDITOR(context) )
{
/* Either it's a line-feed with an eop, or it has an edit_element. */
if ( ! element->lo_any.edit_element )
{
if (element->type == LO_LINEFEED)
{
if ( ! lo_IsEndOfParagraph2(context, element, position) )
{
XP_TRACE(("lo_ValidatePosition2: illegal position: a line feed without an end-of-paragraph marker."));
#ifdef DEBUG_EDIT_LAYOUT
XP_ASSERT(FALSE);
#endif
}
}
else
{
#ifdef DEBUG_EDIT_LAYOUT
XP_ASSERT(FALSE);
#endif
}
}
}
}
}
void
lo_ValidateSelection(MWContext *context, LO_Selection* selection)
{
XP_ASSERT( selection );
lo_ValidateSelection2( context, selection->begin.element, selection->begin.position,
selection->end.element, selection->end.position );
}
void
lo_ValidateSelection2(MWContext *context, LO_Element* beginElement, int32 beginPosition, LO_Element* endElement, int32 endPosition)
{
lo_ValidatePosition2( context, beginElement, beginPosition );
lo_ValidatePosition2( context, endElement, endPosition );
XP_ASSERT( lo_compare_selections(beginElement, beginPosition,
endElement, endPosition) <= 0 );
}
#endif
/*
* An insert point is one edit position greater than a selection end. But there are
* two insert points for the edit position that falls between elements. One
* insert point is for the end of the previous element. The other is for the
* beginning of the next element. There are two corresponding selection ends.
*
* end of previous element:
* insertion point: element = <previous>, position = <previous.length>
* selection end: element = <previous>, position = <previous.length> - 1
*
* start of next element:
* insertion point: element = <next>, position = 0
* selection end: element = <previous>, position = <previous.length>
*/
PRIVATE
Bool lo_IsAtStartOfLine(MWContext* context, lo_DocState* state,
LO_Element* element, int32 position)
{
LO_Element* prev;
if ( position != 0 || ! element )
return FALSE;
prev = element->lo_any.prev;
if ( ! prev )
return TRUE;
if ( prev->type == LO_LINEFEED ||
prev->type == LO_BULLET )
return TRUE;
/* Numbered list test. */
if ( EDT_IS_EDITOR(context) && prev->type == LO_TEXT && prev->lo_text.edit_element == 0 )
return TRUE;
return FALSE;
}
Bool lo_ConvertInsertPointToSelectionEnd(MWContext* context, lo_DocState * state,
LO_Element** pElement, int32* pPosition)
{
LO_Element* eptr = *pElement;
int32 position = *pPosition;
/* Special case for insert points at beginning of lines. */
if ( lo_IsAtStartOfLine(context, state, eptr, position) )
{
/* Skip backwards to line feed that has end-of-paragraph set */
while ( eptr && lo_BoundaryJumpingPrev(context, state, eptr) ) {
lo_bump_position(context, state, eptr, position, &eptr, &position, FALSE);
if ( ! eptr )
{
break;
}
if ( eptr->type == LO_LINEFEED )
{
if ( eptr->lo_linefeed.break_type != LO_LINEFEED_BREAK_SOFT )
{
break;
}
}
/* Linefeeds are editable, but the if test above filters them out. */
else if (lo_EditableElement(eptr->type) )
{
break;
}
}
position = lo_GetLastCharBeginPosition(eptr);
}
else
if ( lo_BumpEditablePositionBackward(context, state, &eptr, &position) )
{
if ( *pPosition == 0 )
{
/* This is the "Start of next element" case. */
lo_BumpEditablePositionForward(context, state, &eptr, &position);
}
}
else
{
/* If we can't go backwards, it means that the insert point was at the beginning of the document.
* This is an error condition, because there is no legal selection end that corresponds
* to this insert point.
*/
return FALSE;
}
/* It turns out that the end is the address of the last byte selected, not just the position of the
* first byte of the two-byte character. So we have to bump forward and subtract one.
*
* (If we're a denormalized selection end, we don't increment our position.
* that's what the "if" is for.)
*/
if ( position < lo_GetElementLength(eptr) )
{
position = lo_IncrementPosition(eptr, position) - 1;
}
*pElement = eptr;
*pPosition = position;
return TRUE;
}
Bool lo_ConvertSelectionEndToInsertPoint(MWContext* context, lo_DocState * state,
LO_Element** pElement, int32* pPosition)
{
LO_Element* eptr = *pElement;
int32 position = *pPosition;
int32 length = lo_GetElementLength(*pElement);
LO_ASSERT_POSITION2( context, eptr, position );
/* It turns out that the end is the address of the last byte selected, not just the position of the
* first byte of the two-byte character. So we have to add one and bump backward.
*/
if ( position >= length )
{
/* this is the end-of-previous-element case. */
XP_ASSERT( position == length );
position = lo_GetLastCharBeginPosition(*pElement);
/* If we can't go forward, it means that the selection end was beyond the end of the document.
* This is an error condition.
*/
if ( ! lo_BumpEditablePositionForward(context, state, &eptr, &position) )
{
return FALSE;
}
}
else
{
position = lo_DecrementPosition(eptr, position + 1);
/* This maps 0..length-1 into 1..length, which is OK. */
position = lo_IncrementPosition(eptr, position);
}
*pElement = eptr;
*pPosition = position;
LO_ASSERT_POSITION2( context, eptr, position );
return TRUE;
}
Bool
lo_NormalizeSelectionPoint(MWContext *context, lo_DocState * state, LO_Element** pEptr, int32* pPosition)
{
/*
* If the insert point is off the end of the element, make it the 0th selection of the next element.
* If we can't normalize it, leave it alone.
*/
Bool result = TRUE;
if ( *pEptr != NULL )
{
int32 length = lo_GetElementLength(*pEptr);
int32 newPosition = *pPosition;
XP_ASSERT( 0 <= newPosition && newPosition <= length );
if ( newPosition >= length )
{
if ( length <= 0 )
{
/* There are some zero-length text elements in the browser, when
* tables are around. Also, the editor uses zero-length text
* elements for empty paragraphs and empty lines.
*/
newPosition = 0;
}
else {
/* Needs to be internationalized */
newPosition = lo_GetLastCharBeginPosition(*pEptr);
result = lo_BumpNormalizedEditablePosition(context, state, pEptr, &newPosition, TRUE);
}
if ( result ) {
*pPosition = newPosition;
}
}
}
return result;
}
void
lo_NormalizeSelectionEnd(MWContext *context, lo_DocState * state, LO_Element** pEptr, int32* pPosition)
{
/*
* If the selection end point is off the end of the element, make it the last position of the element.
*/
if ( *pEptr != NULL )
{
int32 length = lo_GetElementLength(*pEptr);
XP_ASSERT( 0 <= *pPosition && *pPosition <= length );
if ( *pPosition >= length )
{
*pPosition = lo_GetLastCharEndPosition(*pEptr);
}
}
}
PRIVATE
intn
lo_FancyHalfCompareSelections(LO_Element* start, int32 startPosition, LO_Element* end, int32 endPosition)
{
int32 startID = start->lo_any.ele_id;
int32 endID = end->lo_any.ele_id;
if ( startID < endID )
{
if ( startID < (endID - 1) )
return -1; /* Has to be less */
if ( startPosition < lo_GetElementLength(start) )
return -1;
if ( endPosition > 0 )
return -1;
return 0;
}
XP_ASSERT(FALSE);
return -1;
}
PRIVATE
intn
lo_FancyCompareSelections(MWContext *context, lo_DocState * state,
LO_Element* start, int32 startPosition, LO_Element* end, int32 endPosition)
{
#if 0 /* We can't normalize past the end of the document. */
Bool startIsEOD = ! lo_NormalizeSelectionPoint(context, state, &start, &startPosition);
Bool endIsEOD = ! lo_NormalizeSelectionPoint(context, state, &end, &endPosition);
if ( startIsEOD && endIsEOD ) return 0;
else if ( startIsEOD ) return 1;
else if ( endIsEOD ) return -1;
return lo_compare_selections(start, startPosition, end, endPosition);
#endif
int32 startID = 0;
int32 endID = 0;
/* Can not perform if no start or end element */
if(!start || !end)
{
return 0;
}
/* Now safe to set IDs */
startID = start->lo_any.ele_id;
endID = end->lo_any.ele_id;
if ( startID < endID )
{
return lo_FancyHalfCompareSelections(start, startPosition, end, endPosition);
}
else if ( startID == endID )
{
if ( startPosition < endPosition )
return -1;
else if ( startPosition == endPosition )
return 0;
return 1;
}
else /* startID > endID */
{
return - lo_FancyHalfCompareSelections(end, endPosition, start, startPosition);
}
}
void
lo_StartNewSelection(MWContext *context, lo_DocState * state,
LO_Element* eptr, int32 position)
{
/*
* If the user hasn't moved the insertion point yet, start a selection.
*/
LO_Element* i_eptr = state->selection_new;
int32 i_position = state->selection_new_pos;
intn compare = lo_FancyCompareSelections(context, state, i_eptr, i_position, eptr, position);
if ( compare < 0 )
{
/* old < now, so they're dragging towards the end of the document */
lo_ConvertInsertPointToSelectionEnd(context, state, &eptr, &position);
state->extending_start = FALSE;
lo_FullSetSelection(context, state, i_eptr, i_position, eptr, position, FALSE);
}
else if ( compare == 0 )
{
/* The user hasn't moved the mouse far enough to select anything. */
return;
}
else
{
/* Dragging towards start of document */
lo_ConvertInsertPointToSelectionEnd(context, state, &i_eptr, &i_position);
state->extending_start = TRUE;
lo_FullSetSelection(context, state, eptr, position, i_eptr, i_position, TRUE);
}
}
void
LO_ExtendSelection(MWContext *context, int32 x, int32 y)
{
int32 doc_id;
lo_TopState *top_state;
lo_DocState *state;
LO_Element *eptr;
int32 position;
LO_HitResult result;
/*
* Get the unique document ID, and retreive this
* documents layout state.
*/
doc_id = XP_DOCID(context);
top_state = lo_FetchTopState(doc_id);
if ((top_state == NULL)||(top_state->doc_state == NULL))
{
return;
}
state = top_state->doc_state;
LO_Hit(context, x, y, FALSE, &result, state->selection_layer);
position = 0;
eptr = NULL;
switch ( result.type )
{
case LO_HIT_LINE:
{
switch ( result.lo_hitLine.region )
{
case LO_HIT_LINE_REGION_BEFORE:
{
/* Insertion point before first element of line */
eptr = result.lo_hitLine.selection.begin.element;
position = result.lo_hitLine.selection.begin.position;
}
break;
case LO_HIT_LINE_REGION_AFTER:
{
/* Insertion point after last element of line */
eptr = result.lo_hitLine.selection.end.element;
position = result.lo_hitLine.selection.end.position;
lo_ConvertSelectionEndToInsertPoint(context, state, &eptr, &position);
if ( eptr->type == LO_LINEFEED ) position = 0; /* For editable line-feeds */
}
break;
default:
break;
}
}
break;
case LO_HIT_ELEMENT:
{
eptr = result.lo_hitElement.position.element;
position = result.lo_hitElement.position.position;
switch ( result.lo_hitElement.region )
{
case LO_HIT_ELEMENT_REGION_BEFORE:
break;
case LO_HIT_ELEMENT_REGION_MIDDLE:
{
/* The user wants this item selected.
*
* If the anchor point is before or at this item, the insert point should be
* after this item.
*
*/
LO_Element* anchorElement;
int32 anchorPosition;
lo_GetAnchorPoint(context, state, &anchorElement, &anchorPosition);
if ( lo_FancyCompareSelections(context, state, anchorElement, anchorPosition, eptr, position) <= 0 )
{
lo_BumpEditablePositionForward(context, state, &eptr, &position);
}
}
break;
case LO_HIT_ELEMENT_REGION_AFTER:
{
lo_BumpEditablePositionForward(context, state, &eptr, &position);
}
break;
default:
break;
}
}
break;
default:
break;
}
if (eptr == NULL)
{
return;
}
lo_ExtendSelectionToPosition2(context, top_state, state, eptr, position);
}
void
lo_ExtendSelectionToPosition2(MWContext *context, lo_TopState* top_state, lo_DocState *state, LO_Element* eptr, int32 position)
{
/*
* Are we starting a new selection?
*/
if (state->selection_new != NULL)
{
lo_StartNewSelection(context, state, eptr, position);
return;
}
/*
* If we somehow got here without having any selection or started
* selection, start it now where we are.
*/
if (state->selection_start == NULL)
{
state->selection_start = eptr;
state->selection_start_pos = position;
state->selection_end = state->selection_start;
state->selection_end_pos = state->selection_start_pos;
state->extending_start = FALSE;
}
/*
* By some horrendous bug the start was set and the end was not,
* and there was no newly started selection. Correct this now.
*/
if (state->selection_end == NULL)
{
state->selection_end = state->selection_start;
state->selection_end_pos = state->selection_start_pos;
state->extending_start = FALSE;
}
{
LO_Element* anchorElement;
int32 anchorPosition;
intn compare;
lo_GetAnchorPoint(context, state, &anchorElement, &anchorPosition);
compare = lo_FancyCompareSelections(context, state, eptr, position, anchorElement, anchorPosition );
if ( compare == 0 )
{
/* The anchor point might not be editable -- it might be the start of a line feed.
* So Jiggle it.
*/
lo_JiggleToMakeEditable(context, state, &anchorElement, &anchorPosition, FALSE);
lo_SetInsertPoint(context, top_state, anchorElement, anchorPosition, state->selection_layer);
}
else
{
LO_Element *changed_start, *changed_end;
int32 changed_start_pos, changed_end_pos;
Bool extendingStart;
if ( compare < 0 )
{
/* new position is less than anchor. So it's the start */
changed_start = eptr;
changed_start_pos = position;
changed_end = anchorElement;
changed_end_pos = anchorPosition;
extendingStart = TRUE;
}
else
{
/* new position is greater than anchor. So it's the end */
changed_start = anchorElement;
changed_start_pos = anchorPosition;
changed_end = eptr;
changed_end_pos = position;
extendingStart = FALSE;
}
lo_ConvertInsertPointToSelectionEnd(context, state, &changed_end, &changed_end_pos);
lo_FullSetSelection(context, state, changed_start, changed_start_pos,
changed_end, changed_end_pos, extendingStart);
}
}
}
void
LO_EndSelection(MWContext *context)
{
int32 doc_id;
lo_TopState *top_state;
lo_DocState *state;
/*
* Get the unique document ID, and retreive this
* documents layout state.
*/
doc_id = XP_DOCID(context);
top_state = lo_FetchTopState(doc_id);
if ((top_state == NULL)||(top_state->doc_state == NULL))
{
return;
}
state = top_state->doc_state;
}
#ifdef NOT_USED
static void
lo_copy_color(LO_Color *from_color, LO_Color *to_color)
{
to_color->red = from_color->red;
to_color->green = from_color->green;
to_color->blue = from_color->blue;
}
#endif /* NOT_USED */
/*
* If this element is part of a cell, return that cell.
*/
LO_Element *
lo_JumpCellWall(MWContext *context, lo_DocState *state, LO_Element *eptr)
{
LO_Element *parent;
LO_Element *cell = NULL;
LO_Element *prev_eptr;
LO_Element *stop_eptr;
int32 x, y;
int32 ret_x, ret_y;
Bool guess_mode;
CL_Layer *layer;
/*
* We only turn this on if we are desparate and think
* we are in a cell with NO selectable elements!
*/
guess_mode = FALSE;
prev_eptr = NULL;
x = eptr->lo_any.x + eptr->lo_any.x_offset;
y = eptr->lo_any.y + eptr->lo_any.y_offset;
/*
* If this element is a zero width linefeed, it is unselectable, so
* we need to special case this to work off the previous element
* in the cell (if any).
*/
if (eptr->lo_any.width <= 0)
{
prev_eptr = eptr->lo_any.prev;
/*
* Gads, you have have zero width text followed by a
* zero width linefeed. Move back until we get a real
* width or can't go any furthur.
*/
while ((prev_eptr != NULL)&&(prev_eptr->lo_any.width <= 0))
{
prev_eptr = prev_eptr->lo_any.prev;
}
if (prev_eptr != NULL)
{
x = prev_eptr->lo_any.x + prev_eptr->lo_any.x_offset;
y = prev_eptr->lo_any.y + prev_eptr->lo_any.y_offset;
}
/*
* If there is no previous element to the zero-width
* one, guess in desperation by moving back one.
* Probably there are no other elements in this
* cell, go into guess_mode.
*/
else
{
guess_mode = TRUE;
/*
* We used to subtract 1 from the x pixel position, but
* that didn't work in all cases and had the potential to
* cause infinite loops. Now we don't and I feel a lot
* happier.
*/
}
}
/*
* figure out when to stop traversing inward.
* If we have a prev_eptr then that is where we stop,
* otherwise stop if we hit the starting element.
*/
if (prev_eptr != NULL)
{
stop_eptr = prev_eptr;
}
else
{
stop_eptr = eptr;
}
parent = NULL;
/* This deals with the case where the table is in a layer.
* We want to search within the corresponding block and
* not in the base document for the enclosing cell.
* BUGBUG This makes the assumption that if this table is
* in a layer, that layer is part of the current selection
* state. I don't know if this assumption is always true
* for all invocations of this function.
* joki: changing function to work based on layer key focus
* instead of selected layer so that form elements will be
* tabable in tables. 5/25/97.
*/
if (context->compositor && !(CL_IsKeyEventGrabber(context->compositor, NULL))) {
layer = CL_GetKeyEventGrabber(context->compositor);
if (layer && LO_GetIdFromLayer(context, layer) != LO_DOCUMENT_LAYER_ID) {
LO_CellStruct *layer_cell = lo_GetCellFromLayer(context, layer);
if (layer_cell)
cell = lo_XYToCellElement(context,
state,
layer_cell,
x, y, TRUE, FALSE, FALSE);
}
}
if (cell == NULL)
cell = lo_XYToDocumentElement(context, state,
x, y, TRUE, FALSE, FALSE,
&ret_x, &ret_y);
while ((cell != NULL)&&(cell != stop_eptr)&&(cell->type == LO_CELL))
{
parent = cell;
cell = lo_XYToCellElement(context, state, (LO_CellStruct *)cell,
x, y, TRUE, FALSE, FALSE);
}
/*
* If we've traversed to our stopping point.
*/
if (cell == stop_eptr)
{
/*
* If the stopping point has a cell parent, return it.
*/
if ((parent != NULL)&&(parent->type == LO_CELL))
{
return(parent);
}
/*
* Else we aren't in a cell, return the original element.
*/
else
{
return(eptr);
}
}
/*
* Else if we are in guess mode, we found a valid parent
* cell that appears to have no children, then we need to check
* if the first child of the parent is our stop_eptr, and if
* so, return the cell parent.
*/
else if ((guess_mode != FALSE)&&(cell == NULL)&&(parent != NULL)&&
(parent->type == LO_CELL))
{
if (parent->lo_cell.cell_list == stop_eptr)
{
return(parent);
}
/*
* If we get here we don't know what we found, so just
* return.
*/
return(eptr);
}
/* If we found a parent that's a cell, then return it.
* I think this case happens when we hit an element
* that has the same coordinates are the element we
* were given. An example would be a zero-length
* text element before a linefeed. Maybe we should
* change the search algorithm to skip zero-lenght elements?
*
*/
else if ( (parent != NULL) && (parent->type == LO_CELL))
{
return parent;
}
/*
* Else, we've missed the stopping point for some reason.
* Assume we aren't in a cell.
*/
else
{
return(eptr);
}
}
void
lo_SetSelect(MWContext *context, lo_DocState *state,
LO_Element *start, int32 start_pos, LO_Element *end, int32 end_pos,
Bool on)
{
LO_Element *eptr;
if ((start == NULL)||(end == NULL))
{
return;
}
eptr = start;
while ((eptr != NULL)&&(eptr->lo_any.ele_id <= end->lo_any.ele_id))
{
int32 last_id;
switch (eptr->type)
{
case LO_TEXT:
if (eptr->lo_text.text != NULL)
{
int32 p1, p2;
if (eptr == start)
{
p1 = start_pos;
}
else
{
p1 = 0;
}
if (eptr == end)
{
p2 = end_pos;
}
else
{
p2 = lo_GetLastCharEndPosition(eptr);
}
if (p2 < p1)
{
p2 = p1;
}
if (on != FALSE)
{
eptr->lo_text.ele_attrmask |= LO_ELE_SELECTED;
eptr->lo_text.sel_start = (intn) p1;
eptr->lo_text.sel_end = (intn) p2;
}
else
{
eptr->lo_text.ele_attrmask &= (~(LO_ELE_SELECTED));
eptr->lo_text.sel_start = -1;
eptr->lo_text.sel_end = -1;
}
}
break;
case LO_LINEFEED:
if (on != FALSE)
{
eptr->lo_linefeed.ele_attrmask |= LO_ELE_SELECTED;
eptr->lo_linefeed.sel_start = 0;
eptr->lo_linefeed.sel_end = 0;
}
else
{
eptr->lo_linefeed.ele_attrmask &= (~(LO_ELE_SELECTED));
eptr->lo_linefeed.sel_start = -1;
eptr->lo_linefeed.sel_end = -1;
}
break;
case LO_IMAGE:
#ifdef EDITOR
if ( EDT_IS_EDITOR(context) )
{
if (on != FALSE)
{
eptr->lo_image.ele_attrmask |= LO_ELE_SELECTED;
eptr->lo_image.sel_start = 0;
eptr->lo_image.sel_end = 0;
}
else
{
eptr->lo_image.ele_attrmask &= (~(LO_ELE_SELECTED));
eptr->lo_image.sel_start = -1;
eptr->lo_image.sel_end = -1;
}
}
break;
#endif /* EDITOR */
case LO_HRULE:
#ifdef EDITOR
if ( EDT_IS_EDITOR(context) )
{
if (on != FALSE)
{
eptr->lo_hrule.ele_attrmask |= LO_ELE_SELECTED;
eptr->lo_hrule.sel_start = 0;
eptr->lo_hrule.sel_end = 0;
}
else
{
eptr->lo_hrule.ele_attrmask &= (~(LO_ELE_SELECTED));
eptr->lo_hrule.sel_start = -1;
eptr->lo_hrule.sel_end = -1;
}
}
break;
#endif /* EDITOR */
case LO_FORM_ELE:
#ifdef EDITOR
if ( EDT_IS_EDITOR(context) )
{
if (on != FALSE)
{
eptr->lo_form.ele_attrmask |= LO_ELE_SELECTED;
eptr->lo_form.sel_start = 0;
eptr->lo_form.sel_end = 0;
}
else
{
eptr->lo_form.ele_attrmask &= (~(LO_ELE_SELECTED));
eptr->lo_form.sel_start = -1;
eptr->lo_form.sel_end = -1;
}
}
break;
#endif /* EDITOR */
case LO_BULLET:
case LO_SUBDOC:
case LO_TABLE:
default:
break;
}
last_id = eptr->lo_any.ele_id;
/*
* Jump cell boundries if there is one between start
* and end.
*/
if ((eptr->lo_any.next == NULL)&&(eptr != end))
{
eptr = lo_JumpCellWall(context, state, eptr);
}
eptr = eptr->lo_any.next;
/*
* When we walk onto a cell, we need to walk into
* it if it isn't empty.
*/
if ((eptr != NULL)&&(eptr->type == LO_CELL)&&
(eptr->lo_cell.cell_list != NULL))
{
eptr = eptr->lo_cell.cell_list;
}
/*
* We don't want infinite loops.
* If the element ID hasn't progressed, something
* serious is wrong, and we should punt.
*/
if ((eptr != NULL)&&(eptr->lo_any.ele_id <= last_id))
{
#ifdef DEBUG
XP_TRACE(("Selection loop avoidance 1\n"));
#endif /* DEBUG */
break;
}
}
}
typedef enum {
HL_STARTED,
HL_NOT_STARTED,
HL_COMPLETED
} LO_HighlightState;
/* Highlight a range of elements from within a list of layout
elements. Return values:
HL_STARTED At least one element highlighted from list, but
last element in range was not encountered on list.
HL_NOT_STARTED Specified range did not contain any of the range
of elements specified in the arguments.
HL_COMPLETED Specified range of elements has been highlighted. */
static LO_HighlightState
lo_HighlightElementList(MWContext *context,
int32 base_x, int32 base_y,
LO_Element *list,
LO_Element *start, int32 start_pos,
LO_Element *end, int32 end_pos,
CL_Layer *layer,
Bool on)
{
LO_Element *eptr;
int16 charset;
int32 p1, p2;
LO_HighlightState highlight_state;
if (list == NULL)
return HL_COMPLETED;
/* Locate first element to be highlighted in list */
eptr = list;
while (eptr && eptr != start) {
if (eptr->type == LO_CELL) {
int32 x_offset = 0;
int32 y_offset = 0;
CL_Layer *cell_layer = layer;
/* LO_CELLs can be either ordinary table cells or
containers for in-flow layers */
if (eptr->lo_cell.cell_inflow_layer) {
cell_layer = eptr->lo_cell.cell_inflow_layer;
lo_GetLayerXYShift(cell_layer, &x_offset, &y_offset);
}
if (eptr->lo_cell.cell_list) {
highlight_state=lo_HighlightElementList(context,
base_x - x_offset,
base_y - y_offset,
eptr->lo_cell.cell_list,
start, start_pos,
end, end_pos,
cell_layer, on);
if (highlight_state == HL_COMPLETED)
return HL_COMPLETED;
if (highlight_state == HL_STARTED) {
eptr = eptr->lo_any.next;
break;
}
}
}
eptr = eptr->lo_any.next;
}
if (!eptr)
return HL_NOT_STARTED;
/* Paint highlighted elements */
while ((eptr != NULL) && (eptr->lo_any.ele_id <= end->lo_any.ele_id))
{
int32 last_id;
switch (eptr->type)
{
case LO_TEXT:
if (eptr->lo_text.text == NULL)
break;
if (eptr == start)
p1 = start_pos;
else
p1 = 0;
if (eptr == end)
p2 = end_pos;
else
p2 = lo_GetLastCharEndPosition(eptr);
if (p2 < p1)
p2 = p1;
if ( p2 >= eptr->lo_text.text_len )
p2 = lo_GetLastCharEndPosition(eptr);
if (on != FALSE)
{
eptr->lo_text.ele_attrmask |= LO_ELE_SELECTED;
eptr->lo_text.sel_start = (intn) p1;
eptr->lo_text.sel_end = (intn) p2;
}
else
{
eptr->lo_text.ele_attrmask &= (~(LO_ELE_SELECTED));
eptr->lo_text.sel_start = -1;
eptr->lo_text.sel_end = -1;
}
charset = ((LO_TextStruct *) eptr)->text_attr->charset;
if ((eptr == start) || ((eptr == end) &&
INTL_CharSetType(charset) != SINGLEBYTE))
{
/* ugly processing for multibyte here */
char *string;
int n;
PA_LOCK(string, char *, eptr->lo_text.text);
/*
* find beginning of first character
*/
switch (n = INTL_NthByteOfChar(charset, string, (int)(p1+1)))
{
case 0:
case 1:
break;
default:
p1 -= (n - 1);
if (p1 < 0)
p1 = 0;
break;
}
/*
* find end of last character
*/
switch (n = INTL_NthByteOfChar(charset, string, (int)(p2+1)))
{
case 0:
break;
default:
p2 -= (n - 1);
if (p2 < 0)
p2 = 0;
/* FALL THROUGH */
case 1:
p2 += INTL_IsLeadByte(charset, string[p2]);
if (p2 > lo_GetLastCharEndPosition(eptr))
{
p2 = lo_GetLastCharEndPosition(eptr);
}
break;
}
PA_UNLOCK(eptr->lo_text.text);
}
if (p1 <= p2)
{
lo_DisplaySubtext(context,
(LO_TextStruct *)eptr, p1, p2,
!on,
layer);
}
break;
case LO_LINEFEED:
if (on != FALSE)
{
eptr->lo_linefeed.ele_attrmask |= LO_ELE_SELECTED;
eptr->lo_linefeed.sel_start = 0;
eptr->lo_linefeed.sel_end = 0;
}
else
{
eptr->lo_linefeed.ele_attrmask &= (~(LO_ELE_SELECTED));
eptr->lo_linefeed.sel_start = -1;
eptr->lo_linefeed.sel_end = -1;
}
lo_RefreshElement(eptr, layer, FALSE);
break;
case LO_HRULE:
#ifdef EDITOR
if ( EDT_IS_EDITOR(context) )
{
if (on != FALSE)
{
eptr->lo_hrule.ele_attrmask |= LO_ELE_SELECTED;
eptr->lo_hrule.sel_start = 0;
eptr->lo_hrule.sel_end = 0;
}
else
{
eptr->lo_hrule.ele_attrmask &= (~(LO_ELE_SELECTED));
eptr->lo_hrule.sel_start = -1;
eptr->lo_hrule.sel_end = -1;
}
lo_RefreshElement(eptr, layer, FALSE);
}
break;
#endif
case LO_FORM_ELE:
#ifdef EDITOR
if ( EDT_IS_EDITOR(context) )
{
if (on != FALSE)
{
eptr->lo_form.ele_attrmask |= LO_ELE_SELECTED;
eptr->lo_form.sel_start = 0;
eptr->lo_form.sel_end = 0;
}
else
{
eptr->lo_form.ele_attrmask &= (~(LO_ELE_SELECTED));
eptr->lo_form.sel_start = -1;
eptr->lo_form.sel_end = -1;
}
lo_DisplayFormElement(context, (LO_FormElementStruct *)eptr);
}
break;
#endif
case LO_IMAGE:
#ifdef EDITOR
if ( EDT_IS_EDITOR(context) )
{
if (on != FALSE)
{
eptr->lo_image.ele_attrmask |= LO_ELE_SELECTED;
eptr->lo_image.sel_start = 0;
eptr->lo_image.sel_end = 0;
}
else
{
eptr->lo_image.ele_attrmask &= (~(LO_ELE_SELECTED));
eptr->lo_image.sel_start = -1;
eptr->lo_image.sel_end = -1;
}
{
LO_ImageStruct *lo_image = (LO_ImageStruct*)eptr;
XP_Rect rect;
rect.left = 0;
rect.top = 0;
rect.right = lo_image->width;
rect.bottom = lo_image->height;
CL_UpdateLayerRect(context->compositor, lo_image->layer,
&rect, (PRBool)FALSE);
}
}
#endif
break;
case LO_CELL:
{
LO_CellStruct *cell = &eptr->lo_cell;
int32 x_offset = 0;
int32 y_offset = 0;
CL_Layer *cell_layer = layer;
/* LO_CELLs can be either ordinary table cells or
containers for in-flow layers */
if (cell->cell_inflow_layer) {
cell_layer = cell->cell_inflow_layer;
lo_GetLayerXYShift(cell_layer, &x_offset, &y_offset);
}
if (!eptr->lo_cell.cell_list)
break;
highlight_state = lo_HighlightElementList(context,
base_x - x_offset,
base_y - y_offset,
eptr->lo_cell.cell_list,
eptr->lo_cell.cell_list, 0,
end, end_pos,
cell_layer, on);
if (highlight_state == HL_COMPLETED) {
return HL_COMPLETED;
}
break;
}
case LO_SUBDOC:
case LO_TABLE:
case LO_BULLET:
default:
break;
}
last_id = eptr->lo_any.ele_id;
eptr = eptr->lo_any.next;
/*
* We don't want infinite loops.
* If the element ID hasn't progressed, something
* serious is wrong, and we should punt.
*/
if ((eptr != NULL)&&(eptr->lo_any.ele_id <= last_id))
{
#ifdef DEBUG
XP_TRACE(("Selection loop avoidance 2\n"));
#endif /* DEBUG */
break;
}
}
/* We ran off the list without reaching the last element in the
selection range, so tell our caller that there's more work to do. */
if (!eptr)
return HL_STARTED;
/* We reached the last element in the selection range. We're done. */
return HL_COMPLETED;
}
void
lo_HighlightSelect(MWContext *context, lo_DocState *state,
LO_Element *start, int32 start_pos,
LO_Element *end, int32 end_pos,
Bool on)
{
LO_Element **line_array, *list;
LO_CellStruct *layer_cell;
if ((start == NULL) || (end == NULL))
return;
/*
* Do a synchronous composite to flush any other drawing
* request. Since we're going to replace the painter_func
* of state->selection_layer, we don't want to ignore other
* requests that might require this layer to draw. This call
* must happen before the text element is modified in any way.
*/
if (context->compositor)
CL_CompositeNow(context->compositor);
/* The stinkin' editor doesn't set the selection layer yet. */
if (!state->selection_layer)
state->selection_layer = CL_FindLayer(context->compositor, LO_BODY_LAYER_NAME);
layer_cell = lo_GetCellFromLayer(context, state->selection_layer);
if (layer_cell == NULL) {
XP_LOCK_BLOCK(line_array, LO_Element**, state->line_array );
if (line_array)
list = line_array[0];
else
list = NULL;
XP_UNLOCK_BLOCK( state->line_array );
} else
list = layer_cell->cell_list;
lo_HighlightElementList(context, state->base_x, state->base_y, list,
start, start_pos, end, end_pos,
state->selection_layer, on);
/* On the Mac, we don't run timeouts during a selection, so the
compositor won't run. Therefore we need to force any new
selection to paint now. */
if (context->compositor)
CL_CompositeNow(context->compositor);
}
void
LO_HighlightSelection(MWContext *context, Bool on)
{
int32 doc_id;
lo_TopState *top_state;
lo_DocState *state;
LO_Element *start, *end;
int32 start_pos, end_pos;
/*
* Get the unique document ID, and retreive this
* documents layout state.
*/
doc_id = XP_DOCID(context);
top_state = lo_FetchTopState(doc_id);
if ((top_state == NULL)||(top_state->doc_state == NULL))
{
return;
}
state = top_state->doc_state;
if ((state->selection_start == NULL)||(state->selection_end == NULL))
{
return;
}
start = state->selection_start;
start_pos = state->selection_start_pos;
end = state->selection_end;
end_pos = state->selection_end_pos;
lo_HighlightSelect(context, state, start, start_pos, end, end_pos,
on);
}
void
LO_ClearSelection(MWContext *context)
{
int32 doc_id;
lo_TopState *top_state;
lo_DocState *state;
/*
* Get the unique document ID, and retreive this
* documents layout state.
*/
doc_id = XP_DOCID(context);
top_state = lo_FetchTopState(doc_id);
if ((top_state == NULL)||(top_state->doc_state == NULL))
{
return;
}
state = top_state->doc_state;
LO_HighlightSelection(context, FALSE);
state->selection_start = NULL;
state->selection_start_pos = 0;
state->selection_end = NULL;
state->selection_end_pos = 0;
state->selection_layer = NULL;
}
PRIVATE
XP_Block
lo_SelectionToText(MWContext *context, lo_DocState *state,
LO_Element *start, int32 start_pos, LO_Element *end, int32 end_pos)
{
LO_Element *eptr;
LO_TextStruct tmp_text;
LO_TextInfo text_info;
LO_TextAttr tmp_attr;
int32 space_width;
int32 length;
Bool indent_counts;
PA_Block sbuff;
XP_Block buff;
char *str;
char *tptr;
/* int16 charset;*/
if ((start == NULL)||(end == NULL))
{
return(NULL);
}
/*
* All this work is to get the width of a " " in the default
* fixed font.
*/
memset (&tmp_text, 0, sizeof (tmp_text));
sbuff = PA_ALLOC(1);
if (sbuff == NULL)
{
return(NULL);
}
PA_LOCK(str, char *, sbuff);
str[0] = ' ';
PA_UNLOCK(sbuff);
tmp_text.text = sbuff;
tmp_text.text_len = 1;
/*
* Fill in default font information.
*/
lo_SetDefaultFontAttr(state, &tmp_attr, context);
tmp_attr.fontmask |= LO_FONT_FIXED;
tmp_text.text_attr = lo_FetchTextAttr(state, &tmp_attr);
FE_GetTextInfo(context, &tmp_text, &text_info);
PA_FREE(sbuff);
space_width = text_info.max_width;
if (space_width <= 0)
{
space_width = 2;
}
indent_counts = FALSE;
length = 0;
eptr = start;
while ((eptr != NULL)&&(eptr->lo_any.ele_id <= end->lo_any.ele_id))
{
int32 last_id;
switch (eptr->type)
{
case LO_TEXT:
if (eptr->lo_text.text != NULL)
{
if (indent_counts != FALSE)
{
int32 width;
width = eptr->lo_text.x +
eptr->lo_text.x_offset -
state->win_left;
width = (width + space_width - 1) / space_width;
if (width < 0)
{
width = 0;
}
length += width;
indent_counts = FALSE;
}
if ((eptr == start)||(eptr == end))
{
int32 p1, p2;
if (eptr == start)
{
p1 = start_pos;
}
else
{
p1 = 0;
}
if (eptr == end)
{
char *string;
PA_LOCK(string, char *, eptr->lo_text.text);
/* charset = eptr->lo_text.text_attr->charset;
* p2 = end_pos + INTL_IsLeadByte(charset, string[end_pos]);
*
* p2 already point to the end of the selection
* calling INTL_IsLeadByte(charset, string[end_pos]) is wrong here.
*/
p2 = end_pos;
PA_UNLOCK(eptr->lo_text.text);
if (p2 > lo_GetLastCharEndPosition(eptr))
{
p2 = lo_GetLastCharEndPosition(eptr);
}
}
else
{
p2 = lo_GetLastCharEndPosition(eptr);
}
if (p2 < p1)
{
p2 = p1;
}
length += (p2 - p1 + 1);
}
else
{
length += eptr->lo_text.text_len;
}
}
break;
case LO_LINEFEED:
length += LINEBREAK_LEN;
indent_counts = TRUE;
break;
case LO_HRULE:
case LO_FORM_ELE:
case LO_BULLET:
case LO_IMAGE:
case LO_SUBDOC:
case LO_TABLE:
default:
break;
}
last_id = eptr->lo_any.ele_id;
/*
* Jump cell boundries if there is one between start
* and end.
*/
if ((eptr->lo_any.next == NULL)&&(eptr != end))
{
eptr = lo_JumpCellWall(context, state, eptr);
}
eptr = eptr->lo_any.next;
/*
* When we walk onto a cell, we need to walk into
* it if it isn't empty.
*/
if ((eptr != NULL)&&(eptr->type == LO_CELL)&&
(eptr->lo_cell.cell_list != NULL))
{
eptr = eptr->lo_cell.cell_list;
}
/*
* We don't want infinite loops.
* If the element ID hasn't progressed, something
* serious is wrong, and we should punt.
*/
if ((eptr != NULL)&&(eptr->lo_any.ele_id <= last_id))
{
#ifdef DEBUG
XP_TRACE(("Selection loop avoidance 3\n"));
#endif /* DEBUG */
break;
}
}
length++;
#ifdef XP_WIN16
if (length > SIZE_LIMIT)
{
length = SIZE_LIMIT;
}
#endif /* XP_WIN16 */
buff = XP_ALLOC_BLOCK(length * sizeof(char));
if (buff == NULL)
{
return(NULL);
}
XP_LOCK_BLOCK(str, char *, buff);
tptr = str;
indent_counts = FALSE;
length = 0;
eptr = start;
while ((eptr != NULL)&&(eptr->lo_any.ele_id <= end->lo_any.ele_id))
{
int32 last_id;
switch (eptr->type)
{
case LO_TEXT:
if (eptr->lo_text.text != NULL)
{
char *text;
if (indent_counts != FALSE)
{
int32 width;
int32 i;
width = eptr->lo_text.x +
eptr->lo_text.x_offset -
state->win_left;
width = (width + space_width - 1) / space_width;
if (width < 0)
{
width = 0;
}
length += width;
#ifdef XP_WIN16
if (length > SIZE_LIMIT)
{
length -= width;
break;
}
#endif /* XP_WIN16 */
for (i=0; i<width; i++)
{
XP_BCOPY(" ", tptr, 1);
tptr++;
}
indent_counts = FALSE;
}
if ((eptr == start)||(eptr == end))
{
int32 p1, p2, len;
int32 maxPos = lo_GetLastCharEndPosition(eptr);
if (eptr == start)
{
p1 = start_pos;
}
else
{
p1 = 0;
}
if (eptr == end)
{
char *string;
PA_LOCK(string, char *, eptr->lo_text.text);
/* charset = eptr->lo_text.text_attr->charset;
* p2 = end_pos + INTL_IsLeadByte(charset, string[end_pos]);
*
* p2 already point to the end of the selection
* calling INTL_IsLeadByte(charset, string[end_pos]) is wrong here.
*/
p2 = end_pos;
PA_UNLOCK(eptr->lo_text.text);
if (p2 > maxPos)
{
p2 = maxPos;
}
}
else
{
p2 = maxPos;
}
if (p2 < p1)
{
p2 = p1;
}
len = p2 - p1 + 1;
length += len;
#ifdef XP_WIN16
if (length > SIZE_LIMIT)
{
length -= len;
break;
}
#endif /* XP_WIN16 */
PA_LOCK(text, char *, eptr->lo_text.text);
XP_BCOPY((char *)(text + p1), tptr, len);
tptr = (char *)(tptr + len);
PA_UNLOCK(eptr->lo_text.text);
}
else
{
length += eptr->lo_text.text_len;
#ifdef XP_WIN16
if (length > SIZE_LIMIT)
{
length -= eptr->lo_text.text_len;
break;
}
#endif /* XP_WIN16 */
PA_LOCK(text, char *, eptr->lo_text.text);
XP_BCOPY(text, tptr, eptr->lo_text.text_len);
tptr = (char *)(tptr + eptr->lo_text.text_len);
PA_UNLOCK(eptr->lo_text.text);
}
}
break;
case LO_LINEFEED:
length += LINEBREAK_LEN;
#ifdef XP_WIN16
if (length > SIZE_LIMIT)
{
length -= LINEBREAK_LEN;
break;
}
#endif /* XP_WIN16 */
XP_BCOPY(LINEBREAK, tptr, LINEBREAK_LEN);
tptr = (char *)(tptr + LINEBREAK_LEN);
indent_counts = TRUE;
break;
case LO_HRULE:
case LO_FORM_ELE:
case LO_BULLET:
case LO_IMAGE:
case LO_SUBDOC:
case LO_TABLE:
default:
break;
}
last_id = eptr->lo_any.ele_id;
/*
* Jump cell boundries if there is one between start
* and end.
*/
if ((eptr->lo_any.next == NULL)&&(eptr != end))
{
eptr = lo_JumpCellWall(context, state, eptr);
}
eptr = eptr->lo_any.next;
/*
* When we walk onto a cell, we need to walk into
* it if it isn't empty.
*/
if ((eptr != NULL)&&(eptr->type == LO_CELL)&&
(eptr->lo_cell.cell_list != NULL))
{
eptr = eptr->lo_cell.cell_list;
}
/*
* We don't want infinite loops.
* If the element ID hasn't progressed, something
* serious is wrong, and we should punt.
*/
if ((eptr != NULL)&&(eptr->lo_any.ele_id <= last_id))
{
#ifdef DEBUG
XP_TRACE(("Selection loop avoidance 4\n"));
#endif /* DEBUG */
break;
}
}
str[length] = '\0';
length++;
XP_UNLOCK_BLOCK(buff);
return(buff);
}
XP_Block
LO_GetSelectionText(MWContext *context)
{
int32 doc_id;
lo_TopState *top_state;
lo_DocState *state;
LO_Element *start, *end;
int32 start_pos, end_pos;
XP_Block buff;
/*
* Get the unique document ID, and retreive this
* documents layout state.
*/
doc_id = XP_DOCID(context);
top_state = lo_FetchTopState(doc_id);
if ((top_state == NULL)||(top_state->doc_state == NULL))
{
return(NULL);
}
state = top_state->doc_state;
if ((state->selection_start == NULL)||(state->selection_end == NULL))
{
return(NULL);
}
start = state->selection_start;
start_pos = state->selection_start_pos;
end = state->selection_end;
end_pos = state->selection_end_pos;
/* Make sure the selection is normalized */
lo_NormalizeSelectionPoint(context, state, &start, &start_pos);
lo_NormalizeSelectionEnd(context, state, &end, &end_pos);
buff = lo_SelectionToText(context, state, start, start_pos,
end, end_pos);
return(buff);
}
Bool
LO_HaveSelection(MWContext *context)
{
int32 doc_id;
lo_TopState *top_state;
lo_DocState *state;
/*
* Get the unique document ID, and retreive this
* documents layout state.
*/
doc_id = XP_DOCID(context);
top_state = lo_FetchTopState(doc_id);
if ((top_state == NULL)||(top_state->doc_state == NULL))
{
return(FALSE);
}
state = top_state->doc_state;
if ((state->selection_start == NULL)||(state->selection_end == NULL))
{
return(FALSE);
}
else
{
return(TRUE);
}
}
/* return true if there is a current selection */
PRIVATE
Bool
lo_GetSelection(MWContext *context, LO_Selection* selection)
{
int32 beginPosition;
int32 endPosition;
CL_Layer *sel_layer;
/* LO_GetSelectionEndPoints uses int32. LO_Selection uses intn. So we need to
* do the conversion here.
*/
LO_GetSelectionEndpoints(context, &selection->begin.element, &selection->end.element,
&beginPosition, &endPosition, &sel_layer);
selection->begin.position = (intn) beginPosition;
selection->end.position = (intn) endPosition;
return selection->begin.element != NULL && selection->end.element != NULL;
}
void
LO_GetSelectionEndpoints(MWContext *context, LO_Element **start, LO_Element **end,
int32 *start_pos, int32 *end_pos, CL_Layer **sel_layer)
{
int32 doc_id;
lo_TopState *top_state;
lo_DocState *state;
*start = NULL;
*end = NULL;
*start_pos = 0;
*end_pos = 0;
*sel_layer = NULL;
/*
* Get the unique document ID, and retreive this
* documents layout state.
*/
doc_id = XP_DOCID(context);
top_state = lo_FetchTopState(doc_id);
if ((top_state == NULL)||(top_state->doc_state == NULL))
{
return;
}
state = top_state->doc_state;
if ((state->selection_start == NULL)||(state->selection_end == NULL))
{
return;
}
*start = state->selection_start;
*start_pos = state->selection_start_pos;
*end = state->selection_end;
*end_pos = state->selection_end_pos;
*sel_layer = state->selection_layer;
}
/* Returns TRUE if there was anything to select. If there is no data, or if
* we're in editor mode and there is no editable data, then FALSE is returned.
*/
Bool
LO_SelectAll(MWContext *context)
{
int32 doc_id;
lo_TopState *top_state;
lo_DocState *state;
LO_Selection selection;
/*
* Get the unique document ID, and retreive this
* documents layout state.
*/
doc_id = XP_DOCID(context);
top_state = lo_FetchTopState(doc_id);
if ((top_state == NULL)||(top_state->doc_state == NULL))
{
return FALSE;
}
state = top_state->doc_state;
LO_HighlightSelection(context, FALSE);
selection.begin.element = NULL;
selection.begin.position = 0;
selection.end.element = NULL;
selection.end.position = 0;
if ( ! ( lo_FindDocumentEdge(context, state, &selection.begin, TRUE, FALSE)
&& lo_FindDocumentEdge(context, state, &selection.end, TRUE, TRUE) ) )
{
return FALSE;
}
#if 0
lo_ConvertInsertPointToSelectionEnd(context, state, &selection.end.element, &selection.end.position);
#endif
LO_ASSERT_SELECTION(context, &selection);
/*
* Nothing to select.
*/
if (selection.begin.element == NULL)
{
return FALSE;
}
state->selection_start = selection.begin.element;
state->selection_start_pos = selection.begin.position;
state->selection_end = selection.end.element;
state->selection_end_pos = selection.end.position;
#if 0
if ( ! lo_NormalizeSelection(context) ){
return FALSE;
}
#endif
LO_HighlightSelection(context, TRUE);
return TRUE;
}
#ifdef EDITOR
/* This routine normalizes a selection so that it only selects editable elements.
* It returns TRUE if the resulting selection is not empty.
*/
Bool
lo_NormalizeSelection(MWContext *context)
{
Bool result;
int32 doc_id;
lo_TopState *top_state;
lo_DocState *state;
intn comparisonResult;
#ifdef DEBUG
lo_VerifyLayout(context);
#endif /* DEBUG */
result = FALSE;
/*
* Get the unique document ID, and retreive this
* document's layout state.
*/
doc_id = XP_DOCID(context);
top_state = lo_FetchTopState(doc_id);
if ((top_state == NULL)||(top_state->doc_state == NULL))
{
return FALSE;
}
state = top_state->doc_state;
if ( ! ( lo_EnsureEditableSearchNext2(context, state, &state->selection_start, & state->selection_start_pos)
&& lo_EnsureEditableSearchPrev2(context, state, &state->selection_end, & state->selection_end_pos) ) )
{
XP_ASSERT(FALSE);
return FALSE;
}
/*
* Ensure that the start position is before the end position
*/
comparisonResult = lo_compare_selections(
state->selection_start, state->selection_start_pos,
state->selection_end, state->selection_end_pos );
result = comparisonResult <= 0;
if ( comparisonResult > 0 )
{
/*
* It's not a legal selection. This can happen if we're editing and
* the selected element is not editable. In this case we null out the selection
*/
state->selection_start = NULL;
state->selection_end = NULL;
state->selection_start_pos = 0;
state->selection_end_pos = 0;
}
return result;
}
#endif
Bool
lo_EnsureEditableSearchNext(MWContext *context, lo_DocState* state, LO_Element** eptr)
{
int32 position = 0;
return lo_EnsureEditableSearch2(context, state, eptr, &position, TRUE);
}
Bool
lo_EnsureEditableSearchNext2(MWContext *context, lo_DocState* state, LO_Element** eptr,
int32* ePositionPtr)
{
return lo_EnsureEditableSearch2(context, state, eptr, ePositionPtr, TRUE);
}
Bool
lo_EnsureEditableSearchPrev(MWContext *context, lo_DocState* state, LO_Element** eptr)
{
int32 position = lo_GetMaximumInsertPointPosition(*eptr);
return lo_EnsureEditableSearch2(context, state, eptr, &position, FALSE);
}
Bool
lo_EnsureEditableSearchPrev2(MWContext *context, lo_DocState* state, LO_Element** eptr, int32* ePositionPtr)
{
return lo_EnsureEditableSearch2(context, state, eptr, ePositionPtr, FALSE);
}
/* Returns TRUE if the result is editable. Doesn't change position if can't find editable. */
Bool
lo_EnsureEditableSearch(MWContext *context, lo_DocState* state, LO_Position* p, Bool forward)
{
return lo_EnsureEditableSearch2(context, state, &p->element, &p->position, forward);
}
PRIVATE
Bool
lo_BumpToNextElement(MWContext *context, lo_DocState* state, LO_Element** eptr, int32* ePositionPtr, Bool forward)
{
LO_Element* element = *eptr;
int32 position = *ePositionPtr;
if ( forward )
{
element = lo_BoundaryJumpingNext(context, state, element);
}
else
{
element = lo_BoundaryJumpingPrev(context, state, element);
}
if ( ! element ) return FALSE;
if ( forward )
{
position = 0;
}
else
{
position = lo_GetMaximumInsertPointPosition(element);
}
*eptr = element;
*ePositionPtr = position;
return TRUE;
}
Bool
lo_EnsureEditableSearch2(MWContext *context, lo_DocState* state, LO_Element** eptr, int32* ePositionPtr, Bool forward)
{
Bool moved = FALSE;
LO_Element* element = *eptr;
int32 position = *ePositionPtr;
if ( ! element ) return FALSE;
while ( ! lo_IsValidEditableInsertPoint2(context, state, element, position ) )
{
if ( ! lo_BumpToNextElement(context, state, &element, &position, forward ) )
{
return FALSE;
}
moved = TRUE;
}
if ( moved )
{
*eptr = element;
*ePositionPtr = position;
}
return TRUE;
}
Bool
lo_IsValidEditableInsertPoint2(MWContext *context, lo_DocState* state, LO_Element* eptr, int32 position)
{
return lo_ValidEditableElement(context, eptr)
&& position >= 0 && position <= lo_GetMaximumInsertPointPosition(eptr);
}
#ifdef EDITOR
/*
* This routine has too much knowledge of the layout engine. Need to revisit
*/
void LO_PositionCaret(MWContext *context, int32 x, int32 y, CL_Layer *layer)
{
LO_Click(context, x, y, TRUE, layer);
}
void LO_DoubleClick(MWContext *context, int32 x, int32 y, CL_Layer *layer)
{
int32 doc_id;
lo_TopState *top_state;
lo_DocState *state;
LO_HitResult result;
/*
* Get the unique document ID, and retreive this
* documents layout state.
*/
doc_id = XP_DOCID(context);
top_state = lo_FetchTopState(doc_id);
if ((top_state == NULL)||(top_state->doc_state == NULL))
{
return;
}
state = top_state->doc_state;
LO_Hit(context, x, y, FALSE, &result, layer);
lo_ProcessDoubleClick(context, top_state, state, &result, layer);
}
#endif
PRIVATE
void lo_GetHorizontalBounds(LO_Element* eptr, int32* returnBegin, int32* returnEnd)
{
int32 width = eptr->lo_any.width;
/*
* Images need to account for border width
*/
if (eptr->type == LO_IMAGE)
{
width = width + (2 * eptr->lo_image.border_width);
}
if (width <= 0)
{
width = 1;
}
*returnBegin = eptr->lo_any.x + eptr->lo_any.x_offset;
*returnEnd = *returnBegin + width;
}
int32
lo_GetTextAttrMask(LO_Element* eptr)
{
int32 mask = 0;
LO_TextAttr* textAttr = 0;
if ( ! eptr )
{
XP_ASSERT(FALSE);
return mask;
}
switch ( eptr->type ) {
case LO_TEXT:
textAttr = eptr->lo_text.text_attr;
break;
case LO_IMAGE:
textAttr = eptr->lo_image.text_attr;
break;
case LO_LINEFEED:
textAttr = eptr->lo_linefeed.text_attr;
break;
default:
break;
}
if ( textAttr ) {
mask = textAttr->attrmask;
}
return mask;
}
int32
lo_GetElementLength(LO_Element* eptr)
{
int32 length;
if ( ! eptr )
{
XP_ASSERT(FALSE);
return 1;
}
switch ( eptr->type ) {
case LO_TEXT:
length = eptr->lo_text.text_len;
break;
case LO_TEXTBLOCK:
case LO_DESCTITLE:
length = 0; /*MJUDGE 2-5-98*/
break;
case LO_HRULE:
case LO_IMAGE:
case LO_LINEFEED:
default:
length = 1;
break;
}
return length;
}
LO_AnchorData*
lo_GetAnchorData(LO_Element* eptr)
{
LO_AnchorData* result = 0;
if ( ! eptr )
{
XP_ASSERT(FALSE);
return result;
}
switch ( eptr->type ) {
case LO_TEXT:
result = eptr->lo_text.anchor_href;
break;
case LO_IMAGE:
result = eptr->lo_image.anchor_href;
break;
case LO_LINEFEED:
result = eptr->lo_linefeed.anchor_href;
break;
default:
break;
}
return result;
}
LO_Element*
lo_GetNeighbor(LO_Element* element, Bool forward)
{
LO_Element* result = NULL;
if ( element ) {
if ( forward )
result = element->lo_any.next;
else
result = element->lo_any.prev;
}
return result;
}
int32
lo_GetElementEdge(LO_Element* element, Bool forward)
{
int32 result = 0;
if ( element ) {
if ( forward )
result = lo_GetElementLength(element);
}
return result;
}
int32
lo_GetMaximumInsertPointPosition(LO_Element* eptr)
{
if ( ! eptr )
{
XP_ASSERT(FALSE);
return 0;
}
#if 0
if ( eptr->type == LO_LINEFEED )
{
return 0;
}
else
{
return lo_GetElementLength(eptr);
}
#endif
return lo_GetElementLength(eptr);
}
int32
lo_IncrementPosition(LO_Element* eptr, int32 position)
{
int32 length = lo_GetElementLength(eptr);
if ( position < length )
{
if (eptr->type == LO_TEXT)
{
unsigned char *string;
PA_LOCK(string, unsigned char *, eptr->lo_text.text);
position = INTL_NextCharIdx(eptr->lo_text.text_attr->charset,
string, position);
PA_UNLOCK(eptr->lo_text.text);
}
else
position++;
}
if ( position > length ) {
position = length;
}
return position;
}
int32
lo_DecrementPosition(LO_Element* eptr, int32 position)
{
if ( position > 0 )
{
if (eptr->type == LO_TEXT)
{
unsigned char *string;
PA_LOCK(string, unsigned char *, eptr->lo_text.text);
position = INTL_PrevCharIdx(eptr->lo_text.text_attr->charset,
string, position);
PA_UNLOCK(eptr->lo_text.text);
}
else
position--;
}
if ( position < 0 ) {
position = 0;
}
return position;
}
int32
lo_GetLastCharBeginPosition(LO_Element* eptr)
{
return lo_DecrementPosition(eptr,
lo_GetElementLength(eptr));
}
int32
lo_GetLastCharEndPosition(LO_Element* eptr)
{
return lo_GetElementLength(eptr) - 1;
}
void lo_HitLine(MWContext *context, lo_DocState *state, int32 x, int32 y, Bool requireCaret,
LO_HitResult* result);
void lo_HitLine2(MWContext *context, lo_DocState *state, LO_Element* element, int32 position,
int32 x, LO_HitResult* result);
Bool lo_PositionIsOffEndOfLine(LO_HitElementResult* elementResult);
void lo_FullHitElement(MWContext *context, lo_DocState* state, int32 x, int32 y,
Bool requireCaret,
LO_Element* eptr, int32 ret_x, int32 ret_y, LO_HitResult* result);
PRIVATE
void lo_HitElement(MWContext *context, lo_DocState* state, int32 x, int32 y,
Bool requireCaret,
LO_Element* eptr, int32 ret_x, int32 ret_y, LO_HitResult* result)
{
/* We hit something */
result->type = LO_HIT_ELEMENT;
result->lo_hitElement.position.element = eptr;
switch ( eptr->lo_any.type )
{
case LO_TEXT:
{
int32 charStart;
int32 charEnd;
result->lo_hitElement.position.position = lo_ElementToCharOffset2(context, state, eptr, ret_x,
&charStart, &charEnd);
if (result->lo_hitElement.position.position < 0)
{
result->lo_hitElement.position.position = 0;
result->lo_hitElement.region = LO_HIT_ELEMENT_REGION_BEFORE;
}
else {
/*
* eptr points to the element
* region is the part of the element
*/
if( x > eptr->lo_any.x + eptr->lo_text.width )
{
result->lo_hitElement.region = LO_HIT_ELEMENT_REGION_AFTER;
}
else
{
int32 charMiddle = (charStart + charEnd) / 2;
if ( ret_x < charMiddle ) {
result->lo_hitElement.region = LO_HIT_ELEMENT_REGION_BEFORE;
} else {
result->lo_hitElement.region = LO_HIT_ELEMENT_REGION_AFTER;
}
}
}
}
break;
default:
{
int32 begin;
int32 end;
result->lo_hitElement.position.position = 0;
lo_GetHorizontalBounds(eptr, &begin, &end);
if ( requireCaret )
{
int32 middle = (begin + end) / 2;
result->lo_hitElement.region =
( x < middle ) ? LO_HIT_ELEMENT_REGION_BEFORE
: LO_HIT_ELEMENT_REGION_AFTER;
}
else
{
int32 midBegin;
int32 midEnd;
midBegin = begin + 3;
midEnd = end - 3;
if ( midEnd - midBegin < 6 ) {
if ( end - begin <= 6 ) {
/* Very narrow picture. 0..6 pixels */
midBegin = begin;
midEnd = end;
} else {
/* Narrow picture. 6+..12 pixels */
int32 margin = end - begin - 6;
int32 halfMargin = margin / 2;
midBegin = begin + halfMargin;
midEnd = end - (margin - halfMargin);
}
}
if ( x < midBegin ) {
result->lo_hitElement.region = LO_HIT_ELEMENT_REGION_BEFORE;
} else if ( x < midEnd ) {
result->lo_hitElement.region = LO_HIT_ELEMENT_REGION_MIDDLE;
} else {
result->lo_hitElement.region = LO_HIT_ELEMENT_REGION_AFTER;
}
}
}
break;
}
}
void lo_FullHitElement(MWContext *context, lo_DocState* state, int32 x, int32 y,
Bool requireCaret,
LO_Element* eptr, int32 ret_x, int32 ret_y, LO_HitResult* result)
{
if ( eptr->type != LO_LINEFEED ) {
/* Seek forward to find an editable element */
if ( ! lo_EnsureEditableSearchNext(context, state, &eptr) )
{
lo_EnsureEditableSearchPrev(context, state, &eptr);
}
lo_HitElement(context, state, x, y, requireCaret, eptr, ret_x, ret_y, result);
/* Check if we ran off the end of the line */
if ( result->type == LO_HIT_UNKNOWN )
#if 0 /* leads to infinite recursion when draging around tables. */
if ( result->type == LO_HIT_UNKNOWN
|| result->type == LO_HIT_ELEMENT
&& lo_PositionIsOffEndOfLine(& result->lo_hitElement) )
#endif
{
/* XP_TRACE(("Element off end of line.")); */
lo_HitLine(context, state, x, y, requireCaret, result);
}
}
else
{
/* There's a bug selecting the last character of text in a table
* that bites us if we use lo_HitLine. So rather than starting the
* search from the beginning, we start from the linefeed.
*/
lo_HitLine2(context, state, eptr, 0, x, result);
}
}
PRIVATE
void lo_HitCellWideMatch(MWContext *context, lo_DocState *state, LO_CellStruct* cellPtr, int32 x, int32 y, Bool requireCaret, LO_HitResult* result)
{
#if 0
LO_Element* eptr = lo_XYToNearestCellElement(context, state, cellPtr, x, y);
#endif
/*
* The last argument is FALSE so that we search upwards. This helps us in tables
* in the editor.
*/
LO_Element* eptr = lo_search_element_list_WideMatch(context, cellPtr->cell_list, NULL, x, y, FALSE);
if ( eptr )
{
lo_FullHitElement(context, state, x, y, requireCaret, eptr, x, y, result);
}
}
PRIVATE
void lo_HitCell(MWContext *context, lo_DocState *state, LO_CellStruct* cellPtr, int32 x, int32 y, Bool requireCaret, LO_HitResult* result)
{
LO_Element* eptr = lo_XYToNearestCellElement(context, state, cellPtr, x, y);
if ( eptr )
{
lo_FullHitElement(context, state, x, y, requireCaret, eptr, x, y, result);
}
}
void lo_HitLine(MWContext *context, lo_DocState *state, int32 x, int32 y, Bool requireCaret,
LO_HitResult* result)
{
int32 line;
result->type = LO_HIT_UNKNOWN;
/*
* Search from current line backwards to find something to edit.
*/
for ( line = lo_PointToLine(context, state, x, y);
line >= 0;
line-- )
{
LO_Element* begin;
LO_Element* end;
LO_Element* tptr;
lo_GetLineEnds(context, state, line, & begin, & end);
/* lo_GetLineEnds returns the start of the next line for 'end' */
if ( end ) {
end = end->lo_any.prev;
} else {
/* Last line. We know that the last line only has one element. */
end = begin;
}
/* Except for cases where the entire line is a line feed, don't select the end line-feed. */
if ( begin->type != LO_LINEFEED && end->type == LO_LINEFEED ) {
end = end->lo_any.prev;
}
if ( begin->type == LO_TABLE )
{
/* Search inside the table to find which cell/caption we hit */
LO_Element* tptr;
tptr = begin->lo_any.next;
while ((tptr != NULL)&&(tptr->type == LO_CELL))
{
if ( tptr->lo_any.x <= x &&
x < tptr->lo_any.x + tptr->lo_any.width &&
tptr->lo_any.y <= y &&
y < tptr->lo_any.y + tptr->lo_any.height ) {
/* We hit this cell */
/* lo_HitCellWideMatch(context, state, (LO_CellStruct*) tptr, x, y, requireCaret, result); */
/* Replacing call to lo_HitCellWideMatch with lo_HitCell because the former function does
not drill down into cell lists to find the closest document element to x,y. This fixes
selection in tables. */
lo_HitCell(context, state, (LO_CellStruct*) tptr, x, y, requireCaret, result);
break;
}
tptr = tptr->lo_any.next;
}
return;
}
/*
* Loop through the elements in the line and, if any are inflow
* layers, go into them. We bail out if we've reached the end of
* the line (or null for the last line).
*/
for( tptr = begin; tptr && ((tptr != end) || (begin == end));
tptr = tptr->lo_any.next) {
if ( tptr->type == LO_CELL && tptr->lo_cell.cell_inflow_layer &&
tptr->lo_any.x <= x &&
x < tptr->lo_any.x + tptr->lo_any.width &&
tptr->lo_any.y <= y &&
y < tptr->lo_any.y + tptr->lo_any.height ) {
lo_HitCell(context, state, (LO_CellStruct *)tptr,
x, y, requireCaret, result);
return;
}
}
/* Make the end-points editable */
if ( ! lo_EnsureEditableSearchNext(context, state, &begin) )
continue;
if ( ! lo_EnsureEditableSearchPrev(context, state, &end) )
return;
if ( begin && end && begin->lo_any.ele_id <= end->lo_any.ele_id )
{
result->type = LO_HIT_LINE;
if ( x < begin->lo_any.x ) {
result->lo_hitLine.region = LO_HIT_LINE_REGION_BEFORE;
} else {
result->lo_hitLine.region = LO_HIT_LINE_REGION_AFTER;
}
result->lo_hitLine.selection.begin.element = begin;
result->lo_hitLine.selection.begin.position = 0;
result->lo_hitLine.selection.end.element = end;
if ( end->type == LO_LINEFEED )
{
result->lo_hitLine.selection.end.position = 0;
}
else
{
result->lo_hitLine.selection.end.position = lo_GetMaximumInsertPointPosition(end);
}
#if 0
XP_TRACE(("b %d e %d\n", result->lo_hitLine.begin->lo_any.ele_id,
result->lo_hitLine.end->lo_any.ele_id));
#endif
break;
}
}
}
#ifdef MQUOTE
/* Returns true if the only elements between begin and end are bullets, not including endpoints. */
Bool lo_OnlyBulletsBetween(LO_Element *begin,LO_Element *end)
{
/* Both begin and end should exist, and begin should be strictly before end. */
if (!begin || !end || (begin->lo_any.ele_id >= end->lo_any.ele_id))
{
XP_ASSERT(FALSE);
return FALSE;
}
/* We should never get into an infinite loop because we made sure begin was before end. */
do {
if (begin->lo_any.next == end)
{
return TRUE;
}
begin = begin->lo_any.next;
} while (begin->type == LO_BULLET);
/* Hit something that's not a bullet between begin and end. */
return FALSE;
}
#endif
void lo_HitLine2(MWContext *context, lo_DocState *state, LO_Element* element,
int32 position, int32 x, LO_HitResult* result)
{
LO_Element* begin;
LO_Element* end;
result->type = LO_HIT_UNKNOWN;
end = element;
for(;;)
{
/* If this is a non-editable line feed, go backwards to the previous editable line. */
while ( end && end->type == LO_LINEFEED
&& end->lo_linefeed.break_type == LO_LINEFEED_BREAK_SOFT)
{
end = end->lo_any.prev;
}
if ( ! end )
return;
/* Search forward to find the end of line */
for ( ;
end;
end = end->lo_any.next)
{
if ( end->type == LO_LINEFEED ) break;
}
if ( ! end )
{
return;
}
/* Search backwards to find the beginning of the line. */
for ( begin = end->lo_any.prev;
begin;
begin = begin->lo_any.prev)
{
if ( begin->type == LO_LINEFEED )
{
#ifdef MQUOTE
/* We have the case of an editable linefeed on a line by itself when the only
thing on the line are bullets. I.e. from a <mquote> tag. */
if ( lo_OnlyBulletsBetween(begin,end) )
#else
if ( begin->lo_any.next == end )
#endif
{
/* editable linefeed on a line by itself. */
result->type = LO_HIT_LINE;
if ( x < end->lo_any.x ) {
result->lo_hitLine.region = LO_HIT_LINE_REGION_BEFORE;
} else {
result->lo_hitLine.region = LO_HIT_LINE_REGION_AFTER;
}
result->lo_hitLine.selection.begin.element = end;
result->lo_hitLine.selection.begin.position = 0;
result->lo_hitLine.selection.end = result->lo_hitLine.selection.begin;
return;
}
begin = begin->lo_any.next;
break;
}
if ( ! begin->lo_any.prev )
break; /* Start of document */
}
if ( ! begin )
{
/* Must be a line-feed at the beginning of the document */
begin = end;
}
/* Except for cases where the entire line is a line feed, don't select the end line-feed. */
if ( begin->type != LO_LINEFEED && end->type == LO_LINEFEED ) {
end = end->lo_any.prev;
}
if ( ( begin->type == LO_TABLE ) ||
(begin->type == LO_CELL && begin->lo_cell.cell_inflow_layer) )
{
/* If this is a table or an inflow layer, give up.
We don't understand them, yet. */
return;
}
/* Make the end-points editable */
if ( ! lo_EnsureEditableSearchNext(context, state, & begin) ) {
/* This is the last, unselectable line of a document in edit mode.
* Select the previous line.
*/
if ( lo_EnsureEditableSearchPrev(context, state, & begin) ){
lo_HitLine2(context, state, begin, 0, 0, result);
}
return;
}
if ( ! lo_EnsureEditableSearchPrev(context, state, & end) )
return;
if ( begin && end && begin->lo_any.ele_id <= end->lo_any.ele_id )
{
/* This is a good line */
break;
}
/* At this point "end" points to the previous editable line. So try again. */
}
result->type = LO_HIT_LINE;
if ( x < begin->lo_any.x ) {
result->lo_hitLine.region = LO_HIT_LINE_REGION_BEFORE;
} else {
result->lo_hitLine.region = LO_HIT_LINE_REGION_AFTER;
}
result->lo_hitLine.selection.begin.element = begin;
result->lo_hitLine.selection.begin.position = 0;
result->lo_hitLine.selection.end.element = end;
if ( end->type == LO_LINEFEED )
{
result->lo_hitLine.selection.end.position = 0;
}
else
{
result->lo_hitLine.selection.end.position = lo_GetMaximumInsertPointPosition(end);
}
}
Bool
lo_PositionIsOffEndOfLine(LO_HitElementResult* elementResult)
{
if ( elementResult->region == LO_HIT_ELEMENT_REGION_AFTER )
{
LO_Element* eptr = elementResult->position.element;
int32 position = elementResult->position.position;
if ( eptr && eptr->lo_any.next && eptr->lo_any.next->type == LO_LINEFEED )
{
return position >= lo_GetLastCharEndPosition(eptr);
}
}
return FALSE;
}
/*
* LO_Hit
* Determines what semantic part of the layout was hit for a given x/y position.
* This is intended to be called by cursor tracking, drag & drop, etc.
*
*/
void LO_Hit(MWContext *context, int32 x, int32 y, Bool requireCaret,
LO_HitResult* result, CL_Layer *layer)
{
int32 doc_id;
lo_TopState *top_state;
lo_DocState *state;
LO_Element *eptr;
int32 position;
int32 ret_x, ret_y;
LO_CellStruct *layer_cell;
#if 0
lo_PrintLayout(context);
#endif /* DEBUG */
result->type = LO_HIT_UNKNOWN;
/*
* Get the unique document ID, and retreive this
* documents layout state.
*/
doc_id = XP_DOCID(context);
top_state = lo_FetchTopState(doc_id);
if ((top_state == NULL)||(top_state->doc_state == NULL)) {
return;
}
state = top_state->doc_state;
layer_cell = lo_GetCellFromLayer(context, layer);
if (layer_cell != NULL) {
lo_HitCell(context,
state,
layer_cell,
x, y, requireCaret, result);
}
else {
/* Clip Y to the last line of the document */
{
LO_Position endOfDocument;
int32 endY;
LO_Element *last_eptr;
last_eptr = LO_getFirstLastElement(context, FALSE);
if (last_eptr == NULL)
{
return;
}
endY = last_eptr->lo_any.y
+ last_eptr->lo_any.y_offset
+ last_eptr->lo_any.height;
if ( y >= endY ){
y = endY - 1;
}
}
/* Clip Y to the first line of the document */
{
LO_Position startOfDocument;
int32 startY;
LO_Element *first_eptr;
first_eptr = LO_getFirstLastElement(context, TRUE);
if (first_eptr == NULL)
{
return;
}
/* Curiously, tables have an offset which excludes their captions. So don't
* add in the offset, or else you won't be able to select the caption of a
* table at the start of a document.
*/
startY = first_eptr->lo_any.y;
if ( y < startY ){
y = startY;
}
}
/* Note: Setting the first Boolean to TRUE allows you to hit-select into floating elements,
* which basicly means into floating tables. Unfortunately, floating element ids are not numbered
* consecutively with respect to the main document flow, which means that selections that
* cross from below the floating element into the floating element will compare and draw wrong.
*/
position = 0;
eptr = lo_XYToDocumentElement2(context, state, x, y, FALSE, TRUE, TRUE,
TRUE, &ret_x, &ret_y);
/* LO_DUMP_INSERT_POINT("lo_XYToDocumentElement2", eptr, 0); */
if ( eptr ) {
lo_FullHitElement(context, state, x, y, requireCaret, eptr, ret_x, ret_y, result);
}
else {
lo_HitLine(context, state, x, y, requireCaret, result);
}
}
#ifdef DEBUG
{
/* const char* kTypes[] = { "LO_HIT_UNKNOWN", "LO_HIT_LINE", "LO_HIT_ELEMENT"};*/
XP_ASSERT ( result->type >= LO_HIT_UNKNOWN && result->type <= LO_HIT_ELEMENT );
}
#if 0
/* Useful for debugging mouse selection, */
LO_DUMP_HIT_RESULT(result);
#endif
#endif
}
void
lo_SetInsertPoint(MWContext *context, lo_TopState *top_state, LO_Element* eptr, int32 position, CL_Layer *layer)
{
if ( EDT_IS_EDITOR(context) )
{
#ifdef EDITOR
LO_ASSERT_POSITION2(context, eptr, position);
if ( ! lo_IsValidEditableInsertPoint2(context, top_state->doc_state, eptr, position) )
{
XP_ASSERT(FALSE);
}
else
{
EDT_SetInsertPoint( top_state->edit_buffer,
eptr->lo_any.edit_element,
eptr->lo_any.edit_offset + position,
position == 0);
}
#endif
}
LO_StartSelectionFromElement( context, eptr, position, layer );
}
/*
* Like LO_PositionCaret, except it selects non-text items as well.
* Returns TRUE if the result is a selection, FALSE if the result is
* an insertion point. (The layout level
* does not understand the concept of insertion point.)
*/
Bool LO_Click(MWContext *context, int32 x, int32 y, Bool requireCaret,
CL_Layer *layer)
{
int32 doc_id;
lo_TopState *top_state;
lo_DocState *state;
LO_HitResult result;
/*
* Get the unique document ID, and retreive this
* documents layout state.
*/
doc_id = XP_DOCID(context);
top_state = lo_FetchTopState(doc_id);
if ((top_state == NULL)||(top_state->doc_state == NULL))
{
return FALSE;
}
state = top_state->doc_state;
LO_Hit(context, x, y, requireCaret, &result, layer);
return lo_ProcessClick(context, top_state, state, &result, requireCaret,
layer);
}
Bool lo_ProcessClick(MWContext *context, lo_TopState *top_state, lo_DocState *state, LO_HitResult* result, Bool requireCaret, CL_Layer *layer)
{
switch ( result->type )
{
case LO_HIT_LINE:
{
switch ( result->lo_hitLine.region )
{
case LO_HIT_LINE_REGION_BEFORE:
{
if ( requireCaret )
/* Insertion point before first element of line */
{
LO_Element* eptr = result->lo_hitLine.selection.begin.element;
int32 position = result->lo_hitLine.selection.begin.position;
lo_SetInsertPoint(context, top_state, eptr, position, layer);
return FALSE;
}
else
{
/* Select the line */
lo_ExtendToIncludeHardBreak(context, state, & result->lo_hitLine.selection);
lo_SetSelection(context, & result->lo_hitLine.selection, FALSE);
return TRUE;
}
}
break;
case LO_HIT_LINE_REGION_AFTER:
{
/* Set the insertion point after the last element of line */
LO_Element* eptr = result->lo_hitLine.selection.end.element;
int32 position = result->lo_hitLine.selection.end.position;
lo_EnsureEditableSearchPrev2(context, state, &eptr, &position);
lo_SetInsertPoint(context, top_state, eptr,
position, layer);
return FALSE;
}
break;
default:
break;
}
}
break;
case LO_HIT_ELEMENT:
{
LO_Element* eptr = result->lo_hitElement.position.element;
int32 position = result->lo_hitElement.position.position;
switch ( result->lo_hitElement.region )
{
case LO_HIT_ELEMENT_REGION_BEFORE:
{
lo_SetInsertPoint(context, top_state, eptr, position,
layer);
return FALSE;
}
break;
case LO_HIT_ELEMENT_REGION_MIDDLE:
{
/* Select the element */
LO_StartSelectionFromElement( context, eptr, position,
layer );
lo_BumpEditablePositionForward(context, state, &eptr, &position);
LO_ExtendSelectionFromElement( context, eptr, position, FALSE );
LO_HighlightSelection(context, TRUE);
return TRUE;
}
break;
case LO_HIT_ELEMENT_REGION_AFTER:
{
lo_BumpEditablePositionForward(context, state,
&eptr, &position);
lo_SetInsertPoint(context, top_state, eptr,
position, layer);
return FALSE;
}
break;
default:
break;
}
}
break;
default:
break;
}
return FALSE;
}
/* Shares much functionality with lo_ProcessClick
* If any changes made in logic above, please check
* this and make corresponding changes if relevant
* Returns the element that we will drop at and position
* within this element for text data
*/
LO_Element * lo_PositionDropCaret(MWContext *pContext, int32 x, int32 y, int32 * pPosition)
{
LO_Element* eptr = NULL;
#ifdef EDITOR
lo_TopState *top_state = lo_FetchTopState(XP_DOCID(pContext));
int32 position;
LO_HitResult result;
lo_DocState *state;
if ((top_state == NULL)||(top_state->doc_state == NULL)) {
return NULL;
}
state = top_state->doc_state;
LO_Hit(pContext, x, y, FALSE /*requireCaret*/, &result, 0);
/* This was copied from lo_ProcessClick above
* We want to execute most of the same logic to locate the caret position
* without calling lo_SetInsertPoint, which sets the regular caret
* and is incompatable with a selection
*/
switch ( result.type )
{
case LO_HIT_LINE:
switch ( result.lo_hitLine.region )
{
case LO_HIT_LINE_REGION_BEFORE:
/* Drop point before first element of line */
eptr = result.lo_hitLine.selection.begin.element;
position = result.lo_hitLine.selection.begin.position;
break;
case LO_HIT_LINE_REGION_AFTER:
/* Set the drop point after the last element of line */
eptr = result.lo_hitLine.selection.end.element;
position = result.lo_hitLine.selection.end.position;
lo_EnsureEditableSearchPrev2(pContext, state, &eptr, &position);
break;
default:
break;
}
break;
case LO_HIT_ELEMENT:
eptr = result.lo_hitElement.position.element;
position = result.lo_hitElement.position.position;
/* Move to just past the element if after it */
if( result.lo_hitElement.region == LO_HIT_ELEMENT_REGION_AFTER ){
lo_BumpEditablePositionForward(pContext, state, &eptr, &position);
}
break;
default:
break;
}
if( eptr )
{
LO_ASSERT_POSITION2(pContext, eptr, position);
if ( lo_IsValidEditableInsertPoint2(pContext, state, eptr, position) )
{
if( EDT_IsSelected(pContext) )
{
FE_DestroyCaret(pContext);
}
switch ( eptr->type )
{
case LO_TEXT:
FE_DisplayTextCaret( pContext, FE_VIEW,
&eptr->lo_text,
(ED_CaretObjectPosition)position );
break;
case LO_IMAGE:
FE_DisplayImageCaret( pContext,
&eptr->lo_image,
(ED_CaretObjectPosition)position );
break;
default:
FE_DisplayGenericCaret( pContext,
&eptr->lo_any,
(ED_CaretObjectPosition)position );
break;
}
}
else
{
XP_ASSERT(FALSE);
}
}
if( pPosition ){
*pPosition = position;
}
#endif /* EDITOR */
return eptr;
}
/* Returns TRUE if the result is an anchor. Also selects the entire anchor. */
Bool lo_ProcessAnchorClick(MWContext *context, lo_TopState *top_state, lo_DocState *state, LO_HitResult* result)
{
switch ( result->type )
{
case LO_HIT_LINE:
{
return FALSE;
}
break;
case LO_HIT_ELEMENT:
{
LO_Element* eptr = result->lo_hitElement.position.element;
switch ( result->lo_hitElement.region )
{
case LO_HIT_ELEMENT_REGION_BEFORE:
case LO_HIT_ELEMENT_REGION_AFTER:
{
return lo_SelectAnchor(context, state, eptr);
}
break;
case LO_HIT_ELEMENT_REGION_MIDDLE:
{
return FALSE;
}
break;
default:
break;
}
}
break;
default:
break;
}
return FALSE;
}
PRIVATE
void
lo_FindStartOfParagraph(MWContext* context, lo_DocState * state, LO_Position* where, LO_Position* paragraphStart)
{
/*
* Search backwards to find the beginning of the paragraph. A paragraph starts
* with the beginning of the document, or after a forced break.
*/
LO_Element* element;
if ( ! (where && where->element ) )
{
XP_ASSERT(FALSE);
return;
}
element = where->element;
for(;;)
{
LO_Element* prev = element->lo_any.prev;
if ( ! prev ) break;
if ( lo_IsEndOfParagraph2(context, prev, 0) )
{
break;
}
element = prev;
}
paragraphStart->element = element;
paragraphStart->position = 0;
lo_EnsureEditableSearchNext2(context, state, &paragraphStart->element, & paragraphStart->position);
}
PRIVATE
void
lo_FindEndOfParagraph(MWContext* context, lo_DocState * state, LO_Position* where, LO_Position* paragraphEnd)
{
/*
* Search forward to find the end of the paragraph. A paragraph ends
* at the end of the document, or before a forced break.
*/
LO_Element* element;
if ( ! (where && where->element ) )
{
XP_ASSERT(FALSE);
return;
}
element = where->element;
for(;;) {
LO_Element* p = element->lo_any.next;
if ( ! p ) break;
element = p;
if ( lo_IsEndOfParagraph2(context, element, 0) ) break;
}
paragraphEnd->element = element;
paragraphEnd->position = lo_GetElementLength(element);
if ( paragraphEnd->element->type == LO_LINEFEED )
{
paragraphEnd->position = 1;
}
/* lo_EnsureEditableSearchPrev2(context, state, &paragraphEnd->element, & paragraphEnd->position); */
}
PRIVATE
void
lo_FindParagraph(MWContext* context, lo_DocState *state, LO_Position* where, LO_Selection* paragraph)
{
lo_FindStartOfParagraph(context, state, where, & paragraph->begin);
lo_FindEndOfParagraph(context, state, where, &paragraph->end);
lo_ConvertInsertPointToSelectionEnd(context, state, &paragraph->end.element, &paragraph->end.position);
LO_ASSERT_SELECTION(context, paragraph);
}
PRIVATE
void
lo_SelectParagraph(MWContext* context, lo_DocState *state, LO_Position* where)
{
LO_Selection selection;
lo_FindParagraph(context, state, where, &selection);
lo_SetSelection(context, &selection, FALSE);
}
PRIVATE
intn pa_strcmp( PA_Block p1, PA_Block p2 )
{
char *s1, *s2;
intn ret;
PA_LOCK( s1, char*, p1 );
PA_LOCK( s2, char*, p2 );
ret = XP_STRCMP( s1, s2 );
PA_UNLOCK( p1 );
PA_UNLOCK( p2 );
return ret;
}
PRIVATE
Bool lo_AnchorsEqual( LO_AnchorData *p1, LO_AnchorData *p2 )
{
if( pa_strcmp( p1->anchor, p2->anchor ) != 0 )
{
return FALSE;
}
if( p1->target == 0 && p2->target == 0 )
{
return TRUE;
}
if( p1->target && p2->target && pa_strcmp( p1->target, p2->target ) == 0 )
{
return TRUE;
}
return FALSE;
}
PRIVATE
Bool
lo_FindAnchorEdge(MWContext* context, lo_DocState *state, LO_Position* where, LO_Position* result, Bool forward)
{
LO_Element* element = where->element;
LO_AnchorData* goal = lo_GetAnchorData(element);
if ( ! goal ) return FALSE;
while(1) {
LO_Element* next = lo_GetNeighbor(element, forward);
LO_AnchorData* next_anchor;
if ( next == 0
|| (next_anchor = lo_GetAnchorData(next)) == 0
|| !lo_AnchorsEqual( next_anchor,goal ))
{
break;
}
element = next;
}
/* LINEFEED elements can have anchor information. Backup.
*/
while ( element && element->type == LO_LINEFEED ) {
element = lo_GetNeighbor(element, !forward);
}
result->element = element;
result->position = lo_GetElementEdge(element, forward);
return TRUE;
}
PRIVATE
Bool
lo_FindAnchor(MWContext* context, lo_DocState *state, LO_Position* where, LO_Selection* anchor)
{
LO_Selection selection;
Bool result = lo_FindAnchorEdge(context, state, where, & selection.begin, FALSE) &&
lo_FindAnchorEdge(context, state, where, &selection.end, TRUE);
if ( result ) {
lo_ConvertInsertPointToSelectionEnd(context, state, &selection.end.element, &selection.end.position);
LO_ASSERT_SELECTION(context, &selection);
*anchor = selection;
}
return result;
}
Bool
lo_SelectAnchor(MWContext* context, lo_DocState *state, LO_Element* eptr)
{
/* Select the entire anchor associated with eptr */
Bool result = FALSE;
LO_Selection selection;
LO_Position where;
where.element = eptr;
where.position = 0;
result = lo_FindAnchor(context, state, &where, &selection);
if ( result ) {
lo_SetSelection(context, &selection, FALSE);
}
return result;
}
/*
* This should move to a public header so that the routine can be internationalized more easily.
*
* The word-finding algorithm assumes that characters can be divided into different classes.
* The classes are: single, space, and grouping. There are currently only two grouping
* classes, because that's enough for english text. Some languages (e.g. Japanese, which has
* romanji, katakana and hirigana, might encourage us to define more grouping classes.)
*/
#define LO_CC_SINGLE 0
#define LO_CC_SPACE 1
#define LO_CC_ALPHA 2
#define LO_CC_PUNCT 3
#define LO_CC_KANJI 4
#define LO_CC_KANA 5
#define LO_CC_OTHERS 6
PRIVATE
intn
lo_CharacterClassOf(MWContext* context, lo_DocState *state, LO_Position* where)
{
LO_Position position = *where;
if ( ! lo_NormalizeSelectionPoint(context, state, &position.element, &position.position) )
{
/* We've gone off the end of the world. */
return LO_CC_SINGLE;
}
switch ( position.element->type )
{
case LO_LINEFEED:
return LO_CC_SPACE;
case LO_TEXT:
{
int16 charset;
if ( position.element->lo_text.text_len <= 0 ) {
return LO_CC_SPACE;
}
charset = position.element->lo_text.text_attr->charset;
XP_ASSERT(position.position < position.element->lo_text.text_len);
if (INTL_CharSetType(charset) == SINGLEBYTE)
{
/* Need to handle this on a character-set basis. */
char *tptr;
char c;
/* To do: The lock should be outside of the inner loop. */
PA_LOCK(tptr, char *, position.element->lo_text.text);
if ( tptr )
{
c = tptr[position.position];
}
else
{
XP_ASSERT(FALSE);
c = '\0';
}
PA_UNLOCK(where->element->lo_text.text);
if ( XP_IS_SPACE(c) ) return LO_CC_SPACE;
else if ( isalnum(c) || ((unsigned char)c > 0x7F)) return LO_CC_ALPHA;
else return LO_CC_PUNCT;
}
else {
/* Do something smarter here. */
char *tptr;
char c;
intn iRet;
/* To do: The lock should be outside of the inner loop. */
PA_LOCK(tptr, char *, position.element->lo_text.text);
if ( tptr == NULL )
{
XP_ASSERT(FALSE);
c = '\0';
iRet = LO_CC_SINGLE;
}
else
{
c = tptr[position.position];
/* Don't know how much detail we want to do for multibyte,
right now it divide into pronounce character and kanji character
*/
switch (INTL_CharClass(charset, (unsigned char *)(tptr+position.position)))
{
case SEVEN_BIT_CHAR:
if ( XP_IS_SPACE(c) ) iRet = LO_CC_SPACE;
else if ( isalnum(c) ) iRet = LO_CC_ALPHA;
else iRet = LO_CC_PUNCT;
break;
case HALFWIDTH_PRONOUNCE_CHAR:
case FULLWIDTH_PRONOUNCE_CHAR:
iRet = LO_CC_KANA;
break;
case FULLWIDTH_ASCII_CHAR:
iRet = LO_CC_ALPHA;
break;
case KANJI_CHAR:
iRet = LO_CC_KANJI;
break;
case UNCLASSIFIED_CHAR:
iRet = LO_CC_OTHERS;
break;
default:
iRet = LO_CC_PUNCT;
}
}
PA_UNLOCK(where->element->lo_text.text);
return iRet;
}
}
default:
return LO_CC_SINGLE;
}
}
Bool
lo_IsEdgeOfDocument2(MWContext* context, lo_DocState *state, LO_Element* element, int32 position, Bool forward)
{
/* If we can bump the pointer, then we're not at the end of the document. */
if ( forward )
{
LO_Element* next;
if ( element->type != LO_LINEFEED
&& position < lo_GetElementLength(element) )
return FALSE;
next = lo_BoundaryJumpingNext(context, state, element);
if ( next == NULL ||
lo_BoundaryJumpingNext(context, state, next) == NULL )
return TRUE;
}
else
{
if ( position > 0 )
return FALSE;
}
{
return ! lo_BumpEditablePosition(context, state, &element, &position, forward);
}
}
Bool
lo_IsEdgeOfDocument(MWContext* context, lo_DocState *state, LO_Position* where, Bool forward)
{
return lo_IsEdgeOfDocument2(context, state, where->element, where->position, forward);
}
PRIVATE
Bool
lo_TraverseElement(MWContext* context, lo_DocState *state, LO_Element** element, Bool forward)
{
if ( ! element || ! *element ) return FALSE;
if ( forward )
{
*element = lo_BoundaryJumpingNext(context, state, *element);
}
else
{
*element = lo_BoundaryJumpingPrev(context, state, *element);
}
return *element != NULL;
}
/* Stop for linefeeds */
PRIVATE
Bool
lo_IsLineEdge(MWContext* context, lo_DocState *state, LO_Element* element, int32 position,
Bool skipSoftBreaks, Bool forward)
{
LO_Element* next = element;
int32 nextPosition = position;
if ( ! lo_BumpEditablePosition(context, state, &next, &nextPosition, forward) )
{
/* Hit end of document. That counts as a linefeed. */
return TRUE;
}
/* Increment element towards next, looking for a linefeed */
if ( next == element )
{
return element->type == LO_LINEFEED;
}
if ( next->type == LO_LINEFEED )
{
return TRUE;
}
if ( ! lo_TraverseElement(context, state, &element, forward) ) return TRUE;
while ( next != element )
{
if ( element->type == LO_LINEFEED &&
(forward ? position <= 0 : position > 0)
&& ! lo_ValidEditableElement(context, element) )
{
Bool hardBreak = element->lo_linefeed.break_type != LO_LINEFEED_BREAK_SOFT;
if ( !skipSoftBreaks || hardBreak )
return TRUE;
}
/* Go on to next element */
if ( ! lo_TraverseElement(context, state, &element, forward) ) return TRUE;
}
/* Didn't find a linefeed yet. */
return FALSE;
}
/* As long as there's room and where is of the target class, move forward.
* returns with where not equal to the character class, unless edge of line or document.
* returns TRUE if not edge of document, else false.
*/
#define LO_SO_DOCUMENT_EDGE 0
#define LO_SO_LINEFEED 1
#define LO_SO_NEW_CHARACTER_CLASS 2
PRIVATE
intn
lo_SkipOver(MWContext* context, lo_DocState *state, LO_Position* where, intn targetCharacterClass, Bool forward)
{
while ( lo_CharacterClassOf(context, state, where) == targetCharacterClass )
{
/* Stop for linefeeds */
if ( lo_IsLineEdge(context, state, where->element, where->position,
targetCharacterClass == LO_CC_SPACE, forward) )
{
/* Skip to the next character */
return LO_SO_LINEFEED;
}
if ( ! lo_BumpEditablePosition(context, state, &where->element, &where->position, forward) ) return LO_SO_DOCUMENT_EDGE;
}
return LO_SO_NEW_CHARACTER_CLASS;
}
PRIVATE
Bool
lo_MoveToNearestEdgeOfNextLine(MWContext* context, lo_DocState *state, LO_Position* where, Bool forward)
{
LO_Position caret = *where;
Bool bHardBreak;
while ( ! lo_IsLineEdge(context, state, caret.element, caret.position, FALSE, forward) )
{
if (! lo_BumpEditablePosition ( context, state, &caret.element, &caret.position, forward ) )
return FALSE;
}
bHardBreak = caret.element && caret.element->type == LO_LINEFEED
&& caret.element->lo_linefeed.break_type == LO_LINEFEED_BREAK_HARD;
if ( bHardBreak && ! forward && caret.position == 1 )
{
/* This is the after-a-hard-break-and-before-a-paragraph-end case. */
caret.position = 0;
}
else if ( bHardBreak && forward && caret.position == 0 && caret.element->lo_any.next
&& caret.element->lo_any.next->type == LO_LINEFEED
&& caret.element->lo_any.next->lo_linefeed.break_type == LO_LINEFEED_BREAK_PARAGRAPH )
{
/* This is the before-a-hard-break-before-a-paragraph-end case. */
caret.position = 1;
}
else
{
do {
lo_TraverseElement(context, state, &caret.element, forward);
#ifdef MQUOTE
} while ( caret.element &&
((caret.element->type == LO_LINEFEED &&
caret.element->lo_linefeed.break_type == LO_LINEFEED_BREAK_SOFT) ||
caret.element->type == LO_BULLET));
/* Added code above to skip over leading bullets resulting from <mquote> */
#else
} while ( caret.element && caret.element->type == LO_LINEFEED
&& caret.element->lo_linefeed.break_type == LO_LINEFEED_BREAK_SOFT);
#endif
if ( ! caret.element )
return FALSE;
caret.position = 0;
if ( ! forward && caret.element ){
if ( ! (caret.element->type == LO_LINEFEED
&& caret.element->lo_linefeed.break_type == LO_LINEFEED_BREAK_HARD ) ){
caret.position = lo_GetMaximumInsertPointPosition(caret.element);
}
}
{
/* Bump by one position if crossing from one line of wrapped text to another.
* This is because the editor can't represent the difference between one edge
* and the next for wrapped text. *sigh*
*/
if ( caret.element && where->element
&& caret.element->type == LO_TEXT && where->element->type == LO_TEXT
&& caret.element->lo_any.edit_element == where->element->lo_any.edit_element )
{
if (! lo_BumpEditablePosition ( context, state, &caret.element, &caret.position, forward ) )
return FALSE;
}
}
}
/* Skip over junk at edge of line. (For example, bullets.) */
if ( ! lo_EnsureEditableSearch2(context, state, &caret.element, &caret.position, forward) )
{
/* Either the end of the document, or the end of a cell. */
#if 0
if (! lo_BumpEditablePosition ( context, state, &caret.element, &caret.position, forward ) )
return FALSE;
#endif
return FALSE;
}
XP_ASSERT(caret.element != NULL);
*where = caret;
return TRUE;
}
/* Return TRUE if we actually found an edge. FALSE if we're off the start or end of the document. */
PRIVATE
Bool
lo_FindEdgeOfWord(MWContext* context, lo_DocState *state, LO_Position* where, LO_Position* wordEdge,
Bool bSelect, Bool forward, Bool bIncludeSpacesAtEndOfWord)
{
/*
* Search to find the edge of the word.
* word = { single_word | alpha_word | punct_word | space_word }
* single_word = LO_CC_SINGLE (e.g. chinese character for the season "autumn".)
* alpha_word = LO_CC_ALPHA + LO_CC_SPACE * (e.g. "foo10 ")
* punct_word = LO_CC_PUNCT + LO_CC_SPACE * (e.g. "---() ")
* space_word = LO_CC_SPACE + (e.g. " ")
*/
intn characterClass;
*wordEdge = *where;
/* Check for end of document */
if ( lo_IsEdgeOfDocument(context, state, wordEdge, forward) )
{
lo_FindDocumentEdge(context, state, wordEdge, bSelect, forward);
return TRUE;
}
if ( lo_IsLineEdge(context, state, where->element, where->position, FALSE, forward) )
{
return TRUE;
}
if ( !forward && lo_IsLineEdge(context, state, where->element, where->position, FALSE, !forward) )
{
/* Starting at end of line. Move backward. */
lo_BumpEditablePosition(context, state, &wordEdge->element, &wordEdge->position, forward);
}
characterClass = lo_CharacterClassOf(context, state, wordEdge);
if ( characterClass == LO_CC_SINGLE ) {
if ( forward )
{
lo_BumpEditablePositionForward(context, state, &wordEdge->element, &wordEdge->position);
}
}
else {
/* Match more of this class */
if ( ! forward )
{
/* Skip over spaces */
if ( lo_SkipOver(context, state, wordEdge, LO_CC_SPACE, forward ) != LO_SO_NEW_CHARACTER_CLASS )
{
goto exit;
}
characterClass = lo_CharacterClassOf(context, state, wordEdge);
}
if ( characterClass != LO_CC_SPACE )
{
if ( lo_SkipOver(context, state, wordEdge, characterClass, forward) != LO_SO_NEW_CHARACTER_CLASS )
goto exit;
}
if ( forward )
{
if ( bIncludeSpacesAtEndOfWord )
{
/* Skip over spaces */
if ( lo_SkipOver(context, state, wordEdge, LO_CC_SPACE, forward ) != LO_SO_NEW_CHARACTER_CLASS )
goto exit;
}
}
else
{
/* If we didn't hit the edge of the document, we are now one element further than we want to be. So back up one position. */
lo_BumpEditablePositionForward(context, state, &wordEdge->element, &wordEdge->position);
}
}
exit:
/* The edge might be a line feed. If so, move to an editable position. */
lo_EnsureEditableSearch(context, state, wordEdge, forward);
return TRUE;
}
PRIVATE
Bool
lo_IsEmptyText(LO_Element* eptr)
{
if ( eptr && eptr->type == LO_TEXT &&
lo_GetElementLength(eptr) == 0 )
{
return TRUE;
}
return FALSE;
}
PRIVATE
Bool
lo_FindLineEdge(MWContext* context, lo_DocState *state, LO_Position* where, LO_Position* lineEdge, Bool select, Bool forward)
{
/* Special case for hard breaks at end of paragraph */
Bool bHardBreakAtEndOfParagraph;
if ( lo_IsEdgeOfDocument(context, state, lineEdge, forward) )
{
lo_FindDocumentEdge(context, state, lineEdge, select, forward);
return TRUE;
}
bHardBreakAtEndOfParagraph = lo_IsHardBreak2(context, where->element, 0)
|| (lo_IsEmptyText(where->element) && lo_IsHardBreak2(context, where->element->lo_any.next, 0));
if ( bHardBreakAtEndOfParagraph && forward) {
/* "where" is the correct edge */
*lineEdge = *where;
}
else {
LO_HitResult result;
lo_HitLine2(context, state, where->element, where->position, where->element->lo_any.x, &result);
if ( result.type != LO_HIT_LINE )
{
XP_ASSERT(FALSE);
return FALSE;
}
if ( forward )
{
if ( select )
{
lo_ExtendToIncludeHardBreak(context, state, & result.lo_hitLine.selection);
}
*lineEdge = result.lo_hitLine.selection.end;
lo_ConvertSelectionEndToInsertPoint(context, state, &lineEdge->element, &lineEdge->position);
}
else
{
*lineEdge = result.lo_hitLine.selection.begin;
}
}
return TRUE;
}
Bool
lo_FindDocumentEdge(MWContext* context, lo_DocState *state, LO_Position* edge, Bool select, Bool forward)
{
LO_Element **array;
LO_Element* eptr = NULL;
#ifdef XP_WIN16
XP_Block *larray_array;
#endif /* XP_WIN16 */
/*
* Nothing to select.
*/
if (state->line_num <= 1)
{
return FALSE;
}
if ( forward )
{
/*
* Here we deal with the case where the current selection is within
* a layer. In that case, we need to find the edge of the layer.
*/
if (state->selection_layer) {
LO_CellStruct *layer_cell = lo_GetCellFromLayer(context,
state->selection_layer);
if (layer_cell)
eptr = layer_cell->cell_list_end;
}
if (!eptr)
/*
* Get last element in doc.
*/
eptr = state->end_last_line;
if (eptr == NULL)
{
eptr = state->selection_start;
}
/*
* Since the last element is always a
* linefeed, it is safe to set selection_end_pos to 0.
*/
edge->element = eptr;
edge->position = 0;
if ( EDT_IS_EDITOR(context) ) {
/* Skip over end-of-document elements. */
int32 position = 0;
while ( eptr && eptr->type == LO_LINEFEED
&& eptr->lo_linefeed.break_type == LO_LINEFEED_BREAK_PARAGRAPH ) {
if ( ! lo_BumpEditablePositionBackward(context, state, &eptr, &position) ) break;
}
edge->element = eptr;
edge->position = position;
/* Skip forward to end of paragraph. */
while ( eptr && ! lo_IsEndOfParagraph2(context, eptr,0) ) {
eptr = eptr->lo_any.next;
}
if ( eptr ) {
edge->element = eptr;
edge->position = select ? 1 : 0;
}
else {
XP_ASSERT(FALSE);
}
}
}
else
{
/*
* Here we deal with the case where the current selection is within
* a layer. In that case, we need to find the edge of the layer.
*/
if (state->selection_layer) {
LO_CellStruct *layer_cell = lo_GetCellFromLayer(context,
state->selection_layer);
if (layer_cell)
eptr = layer_cell->cell_list;
}
if (!eptr)
{
/*
* Get first element in doc.
*/
#ifdef XP_WIN16
XP_LOCK_BLOCK(larray_array, XP_Block *, state->larray_array);
state->line_array = larray_array[0];
XP_UNLOCK_BLOCK(state->larray_array);
#endif /* XP_WIN16 */
XP_LOCK_BLOCK(array, LO_Element **, state->line_array);
eptr = array[0];
XP_UNLOCK_BLOCK(state->line_array);
}
/*
* No elements.
*/
if (eptr == NULL)
{
return FALSE;
}
edge->element = eptr;
edge->position = 0;
}
/* Backtrack to ensure editability. */
lo_EnsureEditableSearch(context, state, edge, !forward);
/* However, for selecting forward, we need to go over the edge. */
if ( select && forward ) {
if ( edge->element->lo_any.next && edge->element->lo_any.next->type == LO_LINEFEED) {
edge->element = edge->element->lo_any.next;
edge->position = 1;
}
}
return TRUE;
}
Bool
lo_FindWord(MWContext* context, lo_DocState *state, LO_Position* where, LO_Selection* word)
{
lo_FindEdgeOfWord(context, state, where, &word->begin, TRUE, FALSE, TRUE);
LO_ASSERT_POSITION(context, &word->begin);
lo_FindEdgeOfWord(context, state, where, &word->end, TRUE, TRUE, FALSE);
if ( word->begin.element == word->end.element && word->begin.position == word->end.position ) {
/* For some reason the result is an insert point. Perhaps there are no words in
* this document.
*/
return FALSE;
}
lo_ConvertInsertPointToSelectionEnd(context, state, &word->end.element, &word->end.position);
LO_ASSERT_SELECTION(context, word);
return TRUE;
}
PRIVATE
void
lo_SelectWord(MWContext* context, lo_DocState *state, LO_Position* where)
{
LO_Selection selection;
Bool success = lo_FindWord(context, state, where, &selection);
if ( success ) {
lo_SetSelection(context, &selection, FALSE);
}
else {
/* No word available. */
lo_SelectParagraph(context, state, where );
}
}
Bool lo_ProcessDoubleClick(MWContext *context, lo_TopState *top_state, lo_DocState *state, LO_HitResult* result, CL_Layer *layer)
{
switch ( result->type )
{
case LO_HIT_LINE:
{
switch ( result->lo_hitLine.region )
{
case LO_HIT_LINE_REGION_BEFORE:
{
lo_SelectParagraph(context, state, & result->lo_hitLine.selection.begin );
return TRUE;
}
break;
case LO_HIT_LINE_REGION_AFTER:
{
LO_Selection selection = result->lo_hitLine.selection;
/* If they double clicked after the end of the document, back them up. */
lo_EnsureEditableSearchPrev2(context, state, &selection.end.element, &selection.end.position);
if ( lo_ExtendToIncludeHardBreak(context, state, &selection) )
{
/* Select the paragraph end */
selection.begin = selection.end;
lo_SetSelection(context, &selection, FALSE);
}
else
{
lo_SelectWord(context, state, &selection.end );
}
return FALSE;
}
break;
default:
break;
}
}
break;
case LO_HIT_ELEMENT:
{
switch ( result->lo_hitElement.region )
{
case LO_HIT_ELEMENT_REGION_BEFORE:
case LO_HIT_ELEMENT_REGION_AFTER:
{
if ( ! lo_SelectAnchor(context, state, result->lo_hitElement.position.element ) )
lo_SelectWord(context, state, &result->lo_hitElement.position );
return FALSE;
}
break;
case LO_HIT_ELEMENT_REGION_MIDDLE:
{
/* To do: Open editor on image. For now, act like click */
return lo_ProcessClick( context, top_state, state, result, FALSE, layer);
}
break;
default:
break;
}
}
break;
default:
break;
}
return FALSE;
}
PRIVATE
Bool lo_FindCharacterEdge(MWContext* context, lo_DocState *state, LO_Position* where, LO_Position* edge, Bool bSelect, Bool forward)
{
*edge = *where;
if ( forward )
{
if ( lo_IsEdgeOfDocument(context, state, edge, forward) )
{
lo_FindDocumentEdge(context, state, edge, bSelect, forward);
return where->element != edge->element && where->position != edge->position;
}
return lo_BumpEditablePosition(context, state, &edge->element, &edge->position, forward);
}
else
{
/* We're already at the edge of the character. */
return TRUE;
}
}
PRIVATE
Bool
lo_FindChunkEdge(MWContext* context, lo_DocState *state, LO_Position* where, LO_Position* wordEdge, intn chunkType, Bool select, Bool forward)
{
Bool result;
*wordEdge = *where;
switch ( chunkType )
{
case LO_NA_CHARACTER:
result = lo_FindCharacterEdge(context, state, where, wordEdge, select, forward);
break;
case LO_NA_WORD:
result = lo_FindEdgeOfWord(context, state, where, wordEdge, select, forward, TRUE);
break;
case LO_NA_LINEEDGE:
result = lo_FindLineEdge(context, state, where, wordEdge, select, forward);
break;
case LO_NA_DOCUMENT:
/* Doesn't care where we start. */
result = lo_FindDocumentEdge(context, state, wordEdge, select, forward);
break;
default:
XP_ASSERT(FALSE);
return FALSE;
}
/* Did we go off the end of the world? */
/* if ( result && ! lo_EnsureEditableSearch(context, state, wordEdge, forward) ) result = FALSE; */
return result;
}
PRIVATE
Bool
lo_GapWithBothSidesAllowed( MWContext *context, lo_DocState *state, LO_Position* caret)
{
LO_Position prev = *caret;
LO_Position next = *caret;
Bool result = FALSE;
if ( caret->position == 0 )
{
result = lo_BumpEditablePosition(context, state, &prev.element, &prev.position, FALSE);
}
else if ( caret->position == lo_GetElementLength(caret->element) )
{
result = lo_BumpEditablePosition(context, state, &next.element, &next.position, TRUE);
}
if ( ! result )
return FALSE;
/* OK, we're in the gap. */
if ( prev.element->type == LO_HRULE
|| next.element->type == LO_HRULE )
{
/* It's a gap with hrules. Now, if there isn't a hard break, we're all set. */
LO_Element* element;
for ( element = prev.element;
element && element != next.element;
element = element->lo_any.next)
{
if ( lo_IsHardBreak2(context, element, 0) )
{
return FALSE;
}
}
return TRUE;
}
return FALSE;
}
/* For logical navigation (e.g. next / prev word, character, paragraph) this function
* performs all the book keeping to implement extension vs. moving the cursor.
*
* target - the unit of navigation -- the word / character that is selected.
* bSelect - true if the current selection is to be extended.
* bDeselecting - true if there used to be a selection, but it's going away.
* bForward - true if the end of the target is to be used.
*
*/
/* Compute new position
*/
Bool
LO_ComputeNewPosition( MWContext *context, intn chunkType,
Bool bSelect, Bool bDeselecting, Bool bForward,
LO_Element** pElement, int32* pPosition )
{
lo_TopState* top_state;
lo_DocState *state;
LO_Position wordEdge;
LO_Position caret;
Bool result = TRUE;
/* LO_DUMP_SELECTIONSTATE(context); */
/*
* Get the unique document ID, and retreive this
* documents layout state.
*/
top_state = lo_FetchTopState(XP_DOCID(context));
if ((top_state == NULL)||(top_state->doc_state == NULL))
{
return FALSE;
}
state = top_state->doc_state;
if ( ! pElement || ! pPosition )
return FALSE;
caret.element = *pElement;
caret.position = *pPosition;
wordEdge = caret;
switch ( chunkType )
{
case LO_NA_WORD:
{
Bool isLineEdge = lo_IsLineEdge(context, state, caret.element, caret.position, FALSE, bForward);
Bool isHardBreak = lo_IsLineEdge(context, state, caret.element, caret.position, TRUE, bForward);
if ( isLineEdge && bDeselecting ) break;
if ( lo_IsEdgeOfDocument(context, state, &caret, bForward) )
{
lo_FindDocumentEdge(context, state, &caret, bSelect, bForward);
wordEdge = caret;
}
else
{
if ( isLineEdge )
{
Bool bOverEdge = caret.element->type == LO_LINEFEED && caret.position > 0;
lo_MoveToNearestEdgeOfNextLine(context, state, &caret, bForward);
wordEdge = caret;
if ( isHardBreak )
{
if ( bSelect && bOverEdge && bForward )
{
/* If the next line is a single line feed. */
if ( lo_GetElementLength(caret.element) == 0 )
{
lo_BumpEditablePosition(context, state, &caret.element, &caret.position, bForward);
wordEdge = caret;
}
else
{
lo_BumpEditablePosition(context, state, &caret.element, &caret.position, bForward);
lo_FindChunkEdge(context, state, &caret, &wordEdge, chunkType, bSelect, bForward);
}
}
}
else
{
lo_FindChunkEdge(context, state, &caret, &wordEdge, chunkType, bSelect, bForward);
}
}
else {
if ( isLineEdge && ! bSelect )
{
if ( isHardBreak )
{
wordEdge = caret;
}
else
{
lo_FindChunkEdge(context, state, &caret, &wordEdge, chunkType, bSelect, bForward);
}
}
else
{
/* If we're at the beginning of a word, and we are searching backwards,
* we want the word before the word we're currently on.
*/
if ( ! bDeselecting && !isLineEdge && !bForward )
{
lo_BumpEditablePosition(context, state, &caret.element, &caret.position, bForward);
}
lo_FindChunkEdge(context, state, &caret, &wordEdge, chunkType, bSelect, bForward);
}
}
}
}
break;
case LO_NA_CHARACTER:
{
Bool isLineEdge;
/* Special rule: If navigating by unselected character, and there is a selection, then the
* result is the edge in the direction of travel.
*/
if ( bDeselecting )
{
break;
}
isLineEdge = lo_IsLineEdge(context, state, caret.element, caret.position, FALSE, bForward);
if ( isLineEdge )
{
Bool bDoubleGap = lo_GapWithBothSidesAllowed(context, state, &caret);
result = lo_MoveToNearestEdgeOfNextLine(context, state, &caret, bForward);
if ( ! result && bSelect && bForward )
{
/* End of document case. Allowed to select the paragraph. */
lo_FindDocumentEdge(context, state, &caret, TRUE, TRUE);
result = TRUE;
}
else if ( ! result )
{
return FALSE;
}
else
{
/* If we are shift-selecting an hrule, bump an extra position,
because the pseudo-position before and after an hrule doesn't
really exist. */
if ( result && bSelect && bDoubleGap )
{
result = lo_BumpEditablePosition(context, state, &caret.element, &caret.position, bForward);
if ( ! result )
{
/* End of document */
lo_FindDocumentEdge(context, state, &caret, bSelect, bForward);
result = TRUE;
}
}
}
result = TRUE; /* Bogus -- should remove. */
wordEdge = caret;
}
else
{
/* If we are searching backwards,
* we want the character before the character we're currently on.
*/
result = TRUE;
if ( !bForward )
{
result = lo_BumpEditablePosition(context, state, &caret.element, &caret.position, bForward);
}
if ( result )
{
result = lo_FindChunkEdge(context, state, &caret, &wordEdge, chunkType, bSelect, bForward);
}
}
}
break;
default:
{
result = lo_FindChunkEdge(context, state, &caret, &wordEdge, chunkType, bSelect, bForward);
}
break;
}
result = result && (bDeselecting || wordEdge.element != *pElement || wordEdge.position != *pPosition);
if ( result ){
*pElement = wordEdge.element;
*pPosition = wordEdge.position;
}
return result;
}
/*
* Find the next or previous word and possibly extend the selection.
*/
Bool
LO_NavigateChunk( MWContext *context, intn chunkType, Bool bSelect, Bool bForward )
{
lo_TopState* top_state;
lo_DocState *state;
LO_Position caret;
LO_Element *element;
int32 position;
Bool bSelectionExists;
Bool bDeselecting;
Bool bResult;
/* LO_DUMP_SELECTIONSTATE(context); */
/*
* Get the unique document ID, and retreive this
* documents layout state.
*/
top_state = lo_FetchTopState(XP_DOCID(context));
if ((top_state == NULL)||(top_state->doc_state == NULL))
{
return FALSE;
}
state = top_state->doc_state;
bSelectionExists = state->selection_new == NULL;
bDeselecting = bSelectionExists && ! bSelect;
if ( bSelectionExists )
{
/* There is no insert point. Use the extension point. */
if ( bSelect )
{
lo_GetExtensionPoint(context, state, &element, &position);
}
else
{
lo_GetSelectionEdge(context, state, &element, &position, bForward);
}
}
else
{
element = state->selection_new;
position = state->selection_new_pos;
}
bResult = LO_ComputeNewPosition(context, chunkType, bSelect, bDeselecting, bForward, &element, &position);
if ( bResult ) {
caret.element = element;
caret.position = position;
if ( bSelect )
{
lo_ExtendSelectionToPosition2(context, top_state, state, caret.element, caret.position);
}
else
{
if ( ! lo_EnsureEditableSearch(context, state, &caret, bForward) ) {
/* Off the edge of the world. */
lo_EnsureEditableSearch(context, state, &caret, !bForward);
}
lo_SetInsertPoint(context, top_state, caret.element, caret.position, state->selection_layer);
}
}
return bResult;
}
void LO_GetEffectiveCoordinates( MWContext *pContext, LO_Element *pElement, int32 position,
int32* pX, int32* pY, int32* pWidth, int32* pHeight )
{
*pY = pElement->lo_any.y + pElement->lo_any.y_offset;
*pX = pElement->lo_any.x + pElement->lo_any.x_offset;
*pWidth = pElement->lo_any.width;
*pHeight = pElement->lo_any.height;
switch ( pElement->type )
{
case LO_IMAGE:
*pWidth += 2 * pElement->lo_image.border_width;
*pHeight += 2 * pElement->lo_image.border_width;
if ( position > 0 ) {
*pX = *pX + *pWidth;
}
break;
case LO_TEXT:
{
int32 start = LO_TextElementWidth( pContext, (LO_TextStruct*)pElement,
position);
*pX += start;
}
break;
case LO_HRULE:
/* The effective y and height is the height of the following linefeed. */
{
LO_Element* pNext = pElement->lo_any.next;
if ( pNext && pNext->type == LO_LINEFEED )
{
int32 dummyWidth;
int32 dummyX;
LO_GetEffectiveCoordinates(pContext, pNext, 0, &dummyX, pY, &dummyWidth, pHeight);
}
if ( position > 0 ) {
*pX = *pX + *pWidth;
}
}
break;
case LO_LINEFEED:
{
if ( position > 0 )
{
/* At start of next element. */
LO_Element* pNext = pElement->lo_any.next;
if ( pNext )
{ /* Just one level of recursion, since position = 0 */
LO_GetEffectiveCoordinates(pContext, pNext, 0, pX, pY, pWidth, pHeight);
}
}
}
break;
default:
break;
}
}
PRIVATE
Bool lo_FindClosestUpDown(MWContext *pContext, lo_DocState* state, int32 x, int32 y,
Bool bForward, int32* ret_x, int32* ret_y)
{
int32 iLine;
Bool bFound = FALSE;
lo_TopState* top_state = lo_FetchTopState(XP_DOCID(pContext));
if ((top_state == NULL)||(top_state->doc_state == NULL))
{
return FALSE;
}
/* find the line we are currently on */
iLine = lo_PointToLine( pContext, state, x, y );
if ( bForward )
{
int32 maxLine;
/* Down. MaxLine is - 2 for the lines data structure */
maxLine = state->line_num - 2;
if ( EDT_IS_EDITOR(pContext) && top_state->doc_state == state )
{
maxLine -= 2; /* and -2 for the phantom end-of-doc hrule*/
}
for( ;
iLine <= maxLine && !bFound;
iLine++)
{
bFound = lo_FindBestPositionOnLine( pContext, state, iLine, x, y, bForward, ret_x, ret_y );
}
}
else
{
/* Up */ /*changed iline comparisson to include 0. is this correct? uparrow was not working*/
for( ;
iLine >= 0 && !bFound;
--iLine)
{
bFound = lo_FindBestPositionOnLine( pContext, state, iLine, x, y, bForward, ret_x, ret_y );
}
}
return bFound;
}
/*
* Find a element up from this and set the cursor there.
*/
void LO_UpDown( MWContext *pContext, LO_Element *pElement, int32 position, int32 iDesiredX, Bool bSelect, Bool bForward )
{
#ifdef EDITOR
lo_TopState* top_state;
lo_DocState *state;
int32 x, y, width, height;
Bool bFound = FALSE;
LO_Element *pNext;/*used to look ahead for correct y*/
/*
* Get the unique document ID, and retreive this
* documents layout state.
*/
top_state = lo_FetchTopState(XP_DOCID(pContext));
if ((top_state == NULL)||(top_state->doc_state == NULL))
{
return;
}
state = top_state->doc_state;
/* Special case. If the element is a linefeed and the position is 1,
* we have to move the cursor to the next element.
*/
if ( pElement && pElement->type == LO_LINEFEED && position > 0 ) {
LO_Element* pNext = pElement->lo_linefeed.next;
if ( pNext ) {
pElement = pNext;
position = 0;
}
}
{
LO_Position p;
p.element = pElement;
p.position = position;
if ( lo_IsEdgeOfDocument(pContext, state, &p, bForward) )
{
LO_NavigateChunk( pContext, LO_NA_DOCUMENT, bSelect, bForward);
return;
}
}
/* Find the effective coordinate of our current position */
LO_GetEffectiveCoordinates(pContext, pElement, position, &x, &y, &width, &height);
/* Skip over this element */
pNext=NULL; /*if not forward, let the old way handle it.*/
if (bForward)
{
pNext=pElement->lo_any.next;
while (pNext && (pNext->type!=LO_CELL) && (!lo_ValidEditableElement(pContext,pNext) || (pNext->lo_any.y == pElement->lo_any.y)) )
pNext=pNext->lo_any.next;
/*if we find a cell, we will need more work.*/
if (pNext)
y=pNext->lo_any.y;
}
if (!pNext)
{
if ( bForward) {
y = pElement->lo_any.y + pElement->lo_any.line_height;
}
else {
y = pElement->lo_any.y - 1;
}
}
bFound = lo_FindClosestUpDown(pContext, state, iDesiredX, y, bForward, &x, &y);
/* We found a text element on this line. Now, lets re-scan the line for
* the best fit.
*/
if( bFound ){
if( bSelect ){
EDT_ExtendSelection( pContext, x, y );
EDT_EndSelection( pContext, x, y );
}
else {
LO_PositionCaret( pContext, x, y, NULL );
}
}
else {
/* No more elements. */
/* Navigate to the edge of the document. */
LO_NavigateChunk( pContext, LO_NA_DOCUMENT, bSelect, bForward);
}
#endif
}
/* like lo_search_element_list, but matches nearest X */
LO_Element *
lo_search_element_list_WideMatch(MWContext *context, LO_Element *eptr, LO_Element* eEndPtr, int32 x, int32 y,
Bool bForward)
{
LO_Element* pFound = NULL;
int32 bestDistanceX = 2000000;
int32 bestDistanceY = 2000000;
int32 distanceY;
for ( ;
eptr != eEndPtr && eptr != NULL;
eptr = eptr->lo_any.next
)
{
int32 width, height;
if( eptr->type != LO_CELL
&& eptr->type != LO_TABLE
&& ! lo_ValidEditableElementIncludingParagraphMarks(context, eptr)){
continue;
}
width = eptr->lo_any.width;
/*
* Images need to account for border width
*/
if (eptr->type == LO_IMAGE)
{
width = width + (2 * eptr->lo_image.border_width);
}
if (width <= 0)
{
width = 1;
}
height = eptr->lo_any.height;
/*
* Images need to account for border height
*/
if (eptr->type == LO_IMAGE)
{
height = height + (2 * eptr->lo_image.border_width);
}
distanceY = bestDistanceY + 1;
if ( y < eptr->lo_any.y ) {
/* This element is below the target. Consider it if we're searching forward. */
if ( bForward ) {
distanceY = eptr->lo_any.y - y;
}
}
else if ( eptr->lo_any.y + eptr->lo_any.y_offset + height <= y ) {
/* This element is above the target. Consider it if we're searching backwards. */
if ( ! bForward ) {
distanceY = y - (eptr->lo_any.y + eptr->lo_any.y_offset + height) + 1;
}
}
else {
distanceY = 0;
}
if ( distanceY <= bestDistanceY ) {
if ( distanceY < bestDistanceY ) {
/* We have a new winner in Y, so forget our X */
bestDistanceX = 2000000;
bestDistanceY = distanceY;
}
/* We're looking for the closest match */
if ((x < (eptr->lo_any.x + eptr->lo_any.x_offset +
width))&&(x >= eptr->lo_any.x))
{
/* Can't get closer than containing. */
pFound = eptr;
bestDistanceX = 0;
if ( distanceY == 0 ) {
break;
}
}
else {
int32 distance;
if ( x < eptr->lo_any.x ) {
distance = eptr->lo_any.x - x;
}
else {
distance = x - (eptr->lo_any.x + eptr->lo_any.x_offset +
width) + 1;
}
if ( distance < bestDistanceX ) {
pFound = eptr;
bestDistanceX = distance;
}
}
}
/* Skip over cells while searchig for the closest item */
if (eptr->type == LO_TABLE) {
while ( eptr->lo_any.next && eptr->lo_any.next->type == LO_CELL )
{
eptr = eptr->lo_any.next;
}
}
}
return pFound;
}
PRIVATE
Bool lo_FindClosestUpDown_SubDoc(MWContext *pContext, lo_DocState* state,
LO_SubDocStruct* pSubdoc, int32 x, int32 y,
Bool bForward, int32* ret_x, int32* ret_y)
{
/* Cribbed from the end of lo_XYToDocumentElement2 */
int32 new_x, new_y;
new_x = x - (pSubdoc->x + pSubdoc->x_offset +
pSubdoc->border_width);
new_y = y - (pSubdoc->y + pSubdoc->y_offset +
pSubdoc->border_width);
return lo_FindClosestUpDown(pContext, (lo_DocState *)pSubdoc->state, new_x, new_y, bForward, ret_x, ret_y);
}
Bool lo_FindClosestUpDown_Cell(MWContext *pContext, lo_DocState* state,
LO_CellStruct* cell, int32 x, int32 y,
Bool bForward, int32* ret_x, int32* ret_y)
{
/* Cells don't have line arrays. So we linearly search for our element.
* This code was inspired by the code in lo_search_element_list and
* lo_XYToCellElement
*/
LO_Element *eptr;
LO_Element *element;
eptr = cell->cell_list;
element = lo_search_element_list_WideMatch(pContext, eptr, NULL, x, y, bForward);
if (element == NULL)
{
eptr = cell->cell_float_list;
element = lo_search_element_list_WideMatch(pContext, eptr, NULL, x, y, bForward);
}
if ((element != NULL)&&(element->type == LO_SUBDOC))
{
return lo_FindClosestUpDown_SubDoc(pContext, state, (LO_SubDocStruct *)element,
x, y, bForward, ret_x, ret_y);
}
else if ((element != NULL)&&(element->type == LO_CELL))
{
return lo_FindClosestUpDown_Cell(pContext, state,
(LO_CellStruct *)element, x, y,
bForward, ret_x, ret_y);
}
else if ((element != NULL)&&(element->type == LO_TABLE))
{
if ( lo_FindBestPositionInTable(pContext, state,
(LO_TableStruct *)element, x, y,
bForward, ret_x, ret_y) )
return TRUE;
else {
/* no more stuff in the table. Skip out of the table and try again. */
if ( bForward ) {
y = element->lo_table.y + element->lo_table.y_offset + element->lo_table.height;
}
else {
y = element->lo_table.y - 1;
}
/* Tail recursion. Maybe we should use goto? */
return lo_FindClosestUpDown_Cell(pContext, state, cell, x, y, bForward, ret_x, ret_y);
}
}
else if (element != NULL) {
/* Return this element's Y. */
int32 eHeight;
int32 eWidth;
int32 eX;
int32 eY;
LO_GetEffectiveCoordinates(pContext, element, 0, &eX, &eY, & eWidth, &eHeight);
/* Clip x to be inside the bounds of the element */
if ( x >= eX + eWidth ) x = eX + eWidth - 1;
if ( x < eX ) x = eX;
*ret_x = x;
*ret_y = eY;
return TRUE;
}
else
return FALSE;
}
PRIVATE
LO_CellStruct* lo_FindBestCellInTable(MWContext *context, lo_DocState* state, LO_TableStruct* pTable, int32 iDesiredX,
int32 iDesiredY, Bool bForward )
{
/* Find end of table */
LO_Element* pClosest = NULL;
LO_CellStruct* result = NULL;
if ( pTable ) {
LO_Element* pStart = pTable->next;
LO_Element* pEnd = pStart;
while ( pEnd && pEnd->type == LO_CELL ) {
pEnd = pEnd->lo_any.next;
}
if ( pStart ) {
pClosest = lo_search_element_list_WideMatch(context, pStart, pEnd, iDesiredX, iDesiredY, bForward);
if ( pClosest && pClosest->type == LO_CELL ) {
return result = &pClosest->lo_cell;
}
}
}
return result;
}
Bool lo_FindBestPositionInTable(MWContext *context, lo_DocState* state, LO_TableStruct* pTable, int32 iDesiredX,
int32 iDesiredY, Bool bForward, int32* pRetX, int32 *pRetY )
{
/* Up/Down can skip over cell boundaries. So we may have to search in two cells, the one
* we started in, and the next one in the direction we're searching.
* For simplicity, we search a cell, and if we don't hit anything, we bump the
* y coordinate by the cell height, and search again.
*/
while(1) {
LO_CellStruct* pCell = lo_FindBestCellInTable(context, state, pTable, iDesiredX, iDesiredY, bForward);
if ( ! pCell ) return FALSE;
if ( lo_FindClosestUpDown_Cell(context, state, pCell, iDesiredX, iDesiredY, bForward, pRetX, pRetY) ) {
return TRUE;
}
/* Search the next cell */
if ( ! bForward ) {
iDesiredY = pCell->y - 1;
}
else {
iDesiredY = pCell->y + pCell->y_offset + pCell->height;
}
}
}
Bool lo_FindBestPositionOnLine(MWContext *context, lo_DocState* state, int32 iLine, int32 iDesiredX,
int32 iDesiredY, Bool bForward, int32* pRetX, int32 *pRetY )
{
LO_Element **line_array;
LO_Element *pElement, *pEnd;
LO_Element *pFound = NULL;
Bool bDone = FALSE;
XP_LOCK_BLOCK(line_array, LO_Element **, state->line_array);
if( iLine == state->line_num - 2 ){
pEnd = 0;
}
else {
pEnd = line_array[iLine+1];
}
pElement = line_array[iLine];
XP_UNLOCK_BLOCK(state->line_array);
if ( pElement->type == LO_TABLE ) {
return lo_FindBestPositionInTable(context, state, & pElement->lo_table, iDesiredX, iDesiredY, bForward, pRetX, pRetY);
}
for( ;
pElement != pEnd && !bDone;
pElement = pElement->lo_any.next){
if( lo_ValidEditableElementIncludingParagraphMarks(context, pElement)){
if( pFound ){
/* what is desired is before the element, take the absolute
* value of the distance between the two elements
*/
if( iDesiredX < pElement->lo_any.x ){
if( pElement->lo_any.x - iDesiredX < iDesiredX - (*pRetX) ){
*pRetX = pElement->lo_any.x;
*pRetY = pElement->lo_any.y;
pFound = pElement;
}
bDone = TRUE;
}
else if( iDesiredX < pElement->lo_any.x + pElement->lo_any.width ) {
*pRetY = pElement->lo_any.y;
*pRetX = iDesiredX;
pFound = pElement;
bDone = TRUE;
}
else {
*pRetX = iDesiredX;
/* *pRetX = pElement->lo_any.x + pElement->lo_any.width; */
*pRetY = pElement->lo_any.y;
pFound = pElement;
}
}
else {
if( iDesiredX < pElement->lo_any.x ){
*pRetX = pElement->lo_any.x;
*pRetY = pElement->lo_any.y;
bDone = TRUE;
}
else if( iDesiredX < pElement->lo_any.x + pElement->lo_any.width ) {
*pRetY = pElement->lo_any.y;
*pRetX = iDesiredX;
bDone = TRUE;
}
else {
*pRetX = iDesiredX;
/* *pRetX = pElement->lo_any.x + pElement->lo_any.width; */
*pRetY = pElement->lo_any.y;
}
pFound = pElement;
}
}
}
if ( pFound && pFound->type == LO_CELL ) {
return lo_FindClosestUpDown_Cell(context, state, &pFound->lo_cell, iDesiredX, iDesiredY, bForward, pRetX, pRetY);
}
else if ( pFound && pFound->type == LO_SUBDOC ) {
return lo_FindClosestUpDown_SubDoc(context, state, &pFound->lo_subdoc, iDesiredX, iDesiredY, bForward, pRetX, pRetY);
}
return pFound != NULL;
}
ED_Buffer* LO_GetEDBuffer( MWContext *context)
{
#ifdef EDITOR
int32 doc_id;
lo_TopState *top_state;
/*
* Get the unique document ID, and retreive this
* documents layout state.
*/
doc_id = XP_DOCID(context);
top_state = lo_FetchTopState(doc_id);
if ((top_state == NULL)||(top_state->doc_state == NULL))
{
return NULL;
}
return top_state->edit_buffer;
#else
return 0;
#endif
}
/* #endif */
/*
* Given a Text element and a character offset, find the width of upto that
* offset.
*/
int32
LO_TextElementWidth(MWContext *context, LO_TextStruct *text_ele, int charOffset)
{
LO_TextInfo text_info;
int orig_len;
int32 retval;
if ((text_ele->text == NULL)||(text_ele->text_len <= 0))
{
return(0);
}
orig_len = text_ele->text_len;
text_ele->text_len = charOffset;
FE_GetTextInfo(context, text_ele, &text_info);
retval = text_info.max_width;
text_ele->text_len = orig_len;
return retval;
}
#ifdef TEST_16BIT
#undef XP_WIN16
#endif /* TEST_16BIT */
#ifdef PROFILE
#pragma profile off
#endif
/* #ifndef NO_TAB_NAVIGATION */
/*
Arthur Liu, 9/13/96
Reading through this file, I found LO_getDocState() should
be a separate function.
TODO, check this file(may be also other files), to replace
the duplicated code with this function.
*/
PRIVATE
lo_DocState *
LO_getDocState(MWContext *context)
{
int32 doc_id;
lo_TopState *top_state;
/*
* Get the unique document ID, and retreive this
* documents layout state.
*/
doc_id = XP_DOCID(context);
top_state = lo_FetchTopState(doc_id);
if ((top_state == NULL)||(top_state->doc_state == NULL))
{
return NULL ;
}
return( top_state->doc_state );
}
/*
Arthur Liu, 9/13/96
LO_getFirstLastElement() was created for Keyboard Navigation.
TODO: Its function is a subset of lo_FindDocumentEdge(..), and the
latter may call LO_getFirstLastElement() to avoid code duplication.
*/
LO_Element *
LO_getFirstLastElement(MWContext *context, int forward )
{
lo_DocState *state;
LO_Element **array;
LO_Element* eptr;
#ifdef XP_WIN16
XP_Block *larray_array;
#endif /* XP_WIN16 */
if( NULL == ( state = LO_getDocState(context) ) )
return( NULL );
/*
* Nothing to select.
*/
if (state->line_num <= 1)
{
return NULL ;
}
if ( ! forward )
{
/*
* Get last element in doc.
*/
eptr = state->end_last_line;
if (eptr == NULL)
{
eptr = state->selection_start; /* ??? */
}
}
else /* wantFirst */
{
/*
* Get first element in doc.
*/
#ifdef XP_WIN16
XP_LOCK_BLOCK(larray_array, XP_Block *, state->larray_array);
state->line_array = larray_array[0];
XP_UNLOCK_BLOCK(state->larray_array);
#endif /* XP_WIN16 */
XP_LOCK_BLOCK(array, LO_Element **, state->line_array);
eptr = array[0];
XP_UNLOCK_BLOCK(state->line_array);
/*
* No elements.
*/
if (eptr == NULL)
{
return NULL ;
}
}
return( eptr );
} /* LO_getFirstLastElement() */
/*
return TRUE if pCurrentFocus->pElement is a tabable element,
may also fill in pCurrentFocus->pAnchor, and pCurrentFocus->mapAreaIndex
*/
Bool
LO_isTabableElement(MWContext *context, LO_TabFocusData *pCurrentFocus )
{
/*
For all LO_ types which update status bar, see CWinCX::OnMouseMoveForLayerCX(UINT uFlags, CPoint& cpPoint,
for all links see CWinCX::OnLButtonUpForLayerCX(UINT uFlags, CPoint& cpPoint, XY& Point,
*/
Bool bIsTabable;
LO_TextStruct *pText;
LO_ImageStruct *pImage;
lo_MapAreaRec *tempArea;
LO_Element *pElement;
LO_FormElementStruct *formEleStruct;
if( pCurrentFocus == NULL )
return( FALSE );
pCurrentFocus->mapAreaIndex = 0; /* no area */
pCurrentFocus->pAnchor = NULL;
pElement = pCurrentFocus->pElement;
if( pElement == NULL )
return( FALSE );
bIsTabable = FALSE;
switch( pElement->type ) {
case LO_NONE : /* 0 */
break;
case LO_TEXT : /* 1 */
pText = (LO_TextStruct *) pElement;
if( pText )
bIsTabable = (pText->anchor_href && pText->anchor_href->anchor);
if( bIsTabable )
pCurrentFocus->pAnchor = pText->anchor_href;
break;
case LO_LINEFEED : /* 2 */
break;
case LO_HRULE : /* 3 */
break;
case LO_IMAGE : /* 4 */
pImage = ( LO_ImageStruct *) pElement;
/* first test image with a link */
bIsTabable = (pImage->anchor_href != NULL) && (pImage->anchor_href->anchor != NULL);
if( bIsTabable ) {
pCurrentFocus->pAnchor = pImage->anchor_href;
break;
}
/* test use map */
tempArea = LO_getTabableMapArea(context, pImage, 1 ); /* 1 means get the first */
if( tempArea != NULL ) {
pCurrentFocus->mapAreaIndex = 1; /* the first area */
pCurrentFocus->pAnchor = tempArea->anchor;
bIsTabable = TRUE;
}
break;
case LO_BULLET : /* 5 */
break;
case LO_FORM_ELE : /* 6 */
/*TODO get anchor for buttons...*/
formEleStruct = (LO_FormElementStruct *) pElement;
if( formEleStruct )
bIsTabable = LO_isTabableFormElement( formEleStruct );
break;
case LO_SUBDOC : /* 7 */
break;
case LO_TABLE : /* 8 */
break;
case LO_CELL : /* 9 */
break;
case LO_EMBED : /* 10 */
break;
case LO_EDGE : /* 11 */
break;
#ifdef JAVA
case LO_JAVA : /* 12 */
break;
#endif
case LO_SCRIPT : /* 13 */
break;
default : /* something wrong!! */
bIsTabable = FALSE;
break;
}
return( bIsTabable );
}
/*
get first(last) if current element is NULL.
*/
Bool
LO_getNextTabableElement( MWContext *context, LO_TabFocusData *pCurrentFocus, int forward )
{
lo_DocState *state;
LO_Element *currentElement;
lo_MapAreaRec *tempArea;
LO_AnchorData *pLastAnchorData;
if( NULL == ( state = LO_getDocState(context) ) )
return( FALSE );
/*
A text may have been fragmented into multiple lines because the text folding.
If last tab focus is a LO_Text, remember its anchor to skip continuious text fragments.
When the focus is drawn, all continuious fragments will hav efocus, See
CWinCX::setTextTabFocusDrawFlag() in cmd\winfe\cxwin.cpp file.
*/
pLastAnchorData = NULL; /* assuming no need to skip continuious text fragments. */
currentElement = pCurrentFocus->pElement;
/* find the first candidate, */
if( currentElement == NULL ) {
/* find the first(last) element in the Doc as candidate, */
pCurrentFocus->pElement = LO_getFirstLastElement( context, forward ); /* forward is getting first */
/*TODO go down to cell_list if pElement has subtree */
} else {
if( pCurrentFocus->pElement->lo_any.type == LO_TEXT )
pLastAnchorData = pCurrentFocus->pAnchor; /* need to skip continuious text fragments. */
/* if the last element is a image map, try go to next area in the map */
if( currentElement->lo_any.type == LO_IMAGE ) {
if( forward ) {
pCurrentFocus->mapAreaIndex++;
tempArea = LO_getTabableMapArea(context, (LO_ImageStruct *)currentElement, pCurrentFocus->mapAreaIndex );
if( tempArea ) {
pCurrentFocus->pAnchor = tempArea->anchor;
return( TRUE );
}
} else { /* if( forward ) */
pCurrentFocus->mapAreaIndex--;
if( pCurrentFocus->mapAreaIndex > 0 ) { /* otherwise no need to search. */
tempArea = LO_getTabableMapArea(context, (LO_ImageStruct *)currentElement, pCurrentFocus->mapAreaIndex );
if( tempArea ) {
pCurrentFocus->pAnchor = tempArea->anchor;
return( TRUE );
}
}
}
}
/* advance the pointer, use the next(previous) as first candidate.
pElement = lo_GetNeighbor( currentElement, forward);
pElement = currentElement;
*/
lo_TraverseElement(context, state, &(pCurrentFocus->pElement), forward);
}
/*todo if not forward, get last area. */
pCurrentFocus->mapAreaIndex = 0; /* assumming no focus area */
pCurrentFocus->pAnchor = NULL;
while ( pCurrentFocus->pElement != NULL ) {
if( LO_isTabableElement(context, pCurrentFocus) ) {
if( pLastAnchorData == NULL /* no need to skip continuious text fragments. */
|| pCurrentFocus->pElement->lo_any.type != LO_TEXT
|| pLastAnchorData != pCurrentFocus->pAnchor )
return( TRUE );
/* else it is a text fragment with the same anchor as the last tabFocus text.
In such case, continue search.
*/
}
lo_TraverseElement(context, state, &(pCurrentFocus->pElement), forward);
}
return( FALSE );
} /* LO_getNextTabableElement */
/* NO_TAB_NAVIGATION */