/* -*- 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 /* 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 || pElement->lo_linefeed.break_type == LO_LINEFEED_BREAK_HARD); } /* 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 = , position = * selection end: element = , position = - 1 * * start of next element: * insertion point: element = , position = 0 * selection end: element = , position = */ 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; ilo_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 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 */ { 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 */ { 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, ¶graphStart->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, ¶graphEnd->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, ¶graph->end); lo_ConvertInsertPointToSelectionEnd(context, state, ¶graph->end.element, ¶graph->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 */ #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; #ifdef SHACK case LO_BUILTIN : /* 28 */ break; #endif /* SHACK */ 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 */