зеркало из https://github.com/mozilla/gecko-dev.git
7408 строки
209 KiB
C
7408 строки
209 KiB
C
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
|
*
|
|
* The contents of this file are subject to the Netscape Public License
|
|
* Version 1.0 (the "NPL"); you may not use this file except in
|
|
* compliance with the NPL. You may obtain a copy of the NPL at
|
|
* http://www.mozilla.org/NPL/
|
|
*
|
|
* Software distributed under the NPL is distributed on an "AS IS" basis,
|
|
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
|
|
* for the specific language governing rights and limitations under the
|
|
* NPL.
|
|
*
|
|
* The Initial Developer of this code under the NPL is Netscape
|
|
* Communications Corporation. Portions created by Netscape are
|
|
* Copyright (C) 1998 Netscape Communications Corporation. All Rights
|
|
* Reserved.
|
|
*/
|
|
|
|
|
|
#include "xp.h"
|
|
#include "layout.h"
|
|
#include "laylayer.h"
|
|
#include "libi18n.h"
|
|
#include "edt.h"
|
|
#include "layers.h"
|
|
#include <ctype.h> /* For lo_CharacterClassOf */
|
|
|
|
#ifndef XP_TRACE
|
|
# define XP_TRACE(X) fprintf X
|
|
#endif
|
|
|
|
#ifdef TEST_16BIT
|
|
#define XP_WIN16
|
|
#endif /* TEST_16BIT */
|
|
|
|
#ifdef XP_WIN16
|
|
#define SIZE_LIMIT 32000
|
|
#endif /* XP_WIN16 */
|
|
|
|
#ifdef PROFILE
|
|
#pragma profile on
|
|
#endif
|
|
|
|
#ifdef DEBUG
|
|
void lo_ValidatePosition(MWContext *context, LO_Position* position);
|
|
void lo_ValidateSelection(MWContext *context, LO_Selection* selection);
|
|
void lo_ValidatePosition2(MWContext *context, LO_Element* element, int32 position);
|
|
void lo_ValidateSelection2(MWContext *context, LO_Element* elementB, int32 positionB, LO_Element* elementE, int32 positionE);
|
|
|
|
#define LO_ASSERT_POSITION(context, p) lo_ValidatePosition(context, p)
|
|
#define LO_ASSERT_SELECTION(context, s) lo_ValidateSelection(context, s)
|
|
#define LO_ASSERT_POSITION2(context, e, p) lo_ValidatePosition2(context, e, p)
|
|
#define LO_ASSERT_SELECTION2(context, eb, pb, ee, pe) lo_ValidateSelection2(context, eb, pb, ee, pe)
|
|
|
|
#else
|
|
|
|
#define LO_ASSERT_POSITION(context, p) {}
|
|
#define LO_ASSERT_SELECTION(context, s) {}
|
|
#define LO_ASSERT_POSITION2(context, e, p) {}
|
|
#define LO_ASSERT_SELECTION2(context, eb, pb, ee, pe) {}
|
|
|
|
#endif
|
|
|
|
static void
|
|
lo_bump_position(MWContext *context, lo_DocState *state,
|
|
LO_Element *sel, int32 pos,
|
|
LO_Element **new_sel, int32 *new_pos, Bool forward);
|
|
|
|
/*
|
|
* Utility functions to convert between insert point and selection end.
|
|
* Returns FALSE if the conversion can't be done.
|
|
* (Only happens when the insert point is off the beginning of the document, or the
|
|
* selection end is off the end of the document.) In those cases, the input position is
|
|
* returned unchanged.
|
|
*
|
|
*/
|
|
|
|
Bool lo_ConvertInsertPointToSelectionEnd(MWContext* context, lo_DocState * state,
|
|
LO_Element** element, int32* position);
|
|
Bool lo_ConvertSelectionEndToInsertPoint(MWContext* context, lo_DocState * state,
|
|
LO_Element** element, int32* position);
|
|
|
|
void lo_HighlightSelect(MWContext *context, lo_DocState *state,
|
|
LO_Element *start, int32 start_pos, LO_Element *end, int32 end_pos,
|
|
Bool on);
|
|
void lo_SetSelect(MWContext *context, lo_DocState *state,
|
|
LO_Element *start, int32 start_pos, LO_Element *end, int32 end_pos,
|
|
Bool on);
|
|
void
|
|
lo_StartNewSelection(MWContext *context, lo_DocState * state,
|
|
LO_Element* eptr, int32 position);
|
|
|
|
void
|
|
lo_ExtendSelectionToPosition2(MWContext *context, lo_TopState* top_state, lo_DocState *state, LO_Element* eptr, int32 position);
|
|
|
|
/* Makes sure *eptr points to an editable element. If *eptr doesn't, searches towards
|
|
* the end of the document.
|
|
* If no editiable element is found, the function
|
|
* returns FALSE.
|
|
*/
|
|
Bool
|
|
lo_EnsureEditableSearchNext(MWContext *context, lo_DocState *state, LO_Element** eptr);
|
|
|
|
Bool
|
|
lo_EnsureEditableSearchNext2(MWContext *context, lo_DocState *state, LO_Element** eptr, int32* ePositionPtr);
|
|
|
|
/* Same as lo_EnsureEditableSearchNext, except searches towards the start of document.
|
|
*/
|
|
|
|
Bool
|
|
lo_EnsureEditableSearchPrev(MWContext *context, lo_DocState *state, LO_Element** eptr);
|
|
|
|
Bool
|
|
lo_EnsureEditableSearchPrev2(MWContext *context, lo_DocState *state, LO_Element** eptr, int32* ePositionPtr);
|
|
|
|
Bool
|
|
lo_EnsureEditableSearch(MWContext *context, lo_DocState* state, LO_Position* p, Bool forward);
|
|
|
|
Bool
|
|
lo_EnsureEditableSearch2(MWContext *context, lo_DocState* state, LO_Element** eptr, int32* ePositionPtr, Bool forward);
|
|
|
|
Bool
|
|
lo_IsValidEditableInsertPoint2(MWContext *context, lo_DocState* state, LO_Element* eptr, int32 ePositionPtr);
|
|
|
|
|
|
/* Normalizes the selection, if editing is turned on. Returns TRUE if the selection
|
|
* is non empty.
|
|
*/
|
|
|
|
Bool lo_NormalizeSelection(MWContext *context);
|
|
|
|
/*
|
|
* Return FALSE if the selection point can't be normalized.
|
|
* (i.e. if it's at the end of the document.)
|
|
*/
|
|
|
|
Bool
|
|
lo_NormalizeSelectionPoint(MWContext *context, lo_DocState *state, LO_Element** pEptr, int32* pPosition);
|
|
|
|
void
|
|
lo_NormalizeSelectionEnd(MWContext *context, lo_DocState *state, LO_Element** pEptr, int32* pPosition);
|
|
|
|
int32
|
|
lo_GetTextAttrMask(LO_Element* eptr);
|
|
|
|
LO_AnchorData*
|
|
lo_GetAnchorData(LO_Element* eptr);
|
|
|
|
LO_Element*
|
|
lo_GetNeighbor(LO_Element* element, Bool forward);
|
|
|
|
int32
|
|
lo_GetElementEdge(LO_Element* element, Bool forward);
|
|
|
|
int32
|
|
lo_GetMaximumInsertPointPosition(LO_Element* eptr);
|
|
|
|
int32
|
|
lo_GetLastCharEndPosition(LO_Element* eptr);
|
|
|
|
int32
|
|
lo_GetLastCharBeginPosition(LO_Element* eptr);
|
|
|
|
int32
|
|
lo_IncrementPosition(LO_Element* eptr, int32 position);
|
|
|
|
int32
|
|
lo_DecrementPosition(LO_Element* eptr, int32 position);
|
|
|
|
Bool
|
|
lo_IsEndOfParagraph2(MWContext* context, LO_Element* element, int32 position);
|
|
|
|
Bool
|
|
lo_FindDocumentEdge(MWContext* context, lo_DocState *state, LO_Position* edge, Bool select, Bool forward);
|
|
|
|
Bool
|
|
lo_IsEdgeOfDocument2(MWContext* context, lo_DocState *state, LO_Element* element, int32 position, Bool forward);
|
|
|
|
Bool
|
|
lo_IsEdgeOfDocument(MWContext* context, lo_DocState *state, LO_Position* where, Bool forward);
|
|
|
|
void
|
|
lo_SetInsertPoint(MWContext *context, lo_TopState *top_state, LO_Element* eptr, int32 position, CL_Layer *layer);
|
|
|
|
/*
|
|
* Implements the UI policy for a click on an element. Returns TRUE if the result is an insertion point.
|
|
*/
|
|
|
|
Bool lo_ProcessClick(MWContext *context, lo_TopState *top_state, lo_DocState *state, LO_HitResult* result, Bool requireCaret, CL_Layer *layer);
|
|
Bool lo_ProcessDoubleClick(MWContext *context, lo_TopState *top_state, lo_DocState *state, LO_HitResult* result, CL_Layer *layer);
|
|
Bool lo_ProcessAnchorClick(MWContext *context, lo_TopState *top_state, lo_DocState *state, LO_HitResult* result);
|
|
|
|
Bool
|
|
lo_SelectAnchor(MWContext* context, lo_DocState *state, LO_Element* eptr);
|
|
|
|
void
|
|
lo_SetSelection(MWContext *context, LO_Selection* selection, Bool extendingStart);
|
|
|
|
void
|
|
lo_FullSetSelection(MWContext *context, lo_DocState * state,
|
|
LO_Element* start, int32 start_pos,
|
|
LO_Element* end, int32 end_pos, Bool extendStart);
|
|
|
|
/* Get various selection parts, expressed as insert points. */
|
|
|
|
void
|
|
lo_GetAnchorPoint(MWContext *context, lo_DocState * state, LO_Element** pElement, int32* pPosition);
|
|
void
|
|
lo_GetExtensionPoint(MWContext *context, lo_DocState * state, LO_Element** pElement, int32* pPosition);
|
|
|
|
Bool lo_FindBestPositionInTable(MWContext *context, lo_DocState* state, LO_TableStruct* pTable, int32 iDesiredX,
|
|
int32 iDesiredY, Bool bForward, int32* pRetX, int32 *pRetY );
|
|
Bool lo_FindClosestUpDown_Cell(MWContext *pContext, lo_DocState* state,
|
|
LO_CellStruct* cell, int32 x, int32 y,
|
|
Bool bForward, int32* ret_x, int32* ret_y);
|
|
LO_Element *
|
|
lo_search_element_list_WideMatch(MWContext *context, LO_Element *eptr, LO_Element* eEndPtr, int32 x, int32 y,
|
|
Bool bForward);
|
|
|
|
Bool lo_EditableElement( int iType )
|
|
{
|
|
if( iType == LO_TEXT
|
|
|| iType == LO_TEXTBLOCK
|
|
|| iType == LO_IMAGE
|
|
|| iType == LO_HRULE
|
|
|| iType == LO_LINEFEED
|
|
|| iType == LO_FORM_ELE )
|
|
{
|
|
return TRUE;
|
|
}
|
|
else
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
PRIVATE
|
|
int32
|
|
lo_ElementToCharOffset2(MWContext *context, lo_DocState *state,
|
|
LO_Element *element, int32 x, int32* returnCharStart, int32* returnCharEnd)
|
|
{
|
|
|
|
LO_TextStruct *text_ele;
|
|
LO_TextInfo text_info;
|
|
int32 orig_len;
|
|
int32 xpos;
|
|
int32 cpos;
|
|
int32 start, end;
|
|
int16 charset;
|
|
int32 startX;
|
|
|
|
if ((element == NULL)||(element->type != LO_TEXT))
|
|
{
|
|
return(-1);
|
|
}
|
|
|
|
text_ele = (LO_TextStruct *)element;
|
|
if ((text_ele->text == NULL)||(text_ele->text_len <= 0))
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
startX = text_ele->x + text_ele->x_offset;
|
|
xpos = x - startX;
|
|
if (xpos < 0)
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
if (xpos >= text_ele->width && returnCharStart == NULL)
|
|
{
|
|
return lo_GetLastCharEndPosition(element);
|
|
}
|
|
|
|
start = 0;
|
|
end = lo_GetLastCharEndPosition(element);
|
|
|
|
/* Make a guess as to where to start searching, assuming characters are
|
|
* all roughly the same width.
|
|
*/
|
|
|
|
if ( text_ele->width > 0 )
|
|
{
|
|
cpos = xpos * text_ele->text_len / text_ele->width;
|
|
}
|
|
else
|
|
{
|
|
cpos = 0;
|
|
}
|
|
if (cpos > end)
|
|
{
|
|
cpos = end;
|
|
}
|
|
orig_len = text_ele->text_len;
|
|
|
|
charset = text_ele->text_attr->charset;
|
|
if (INTL_CharSetType(charset) == SINGLEBYTE)
|
|
{
|
|
while (start < end)
|
|
{
|
|
XP_ASSERT(cpos >= 0);
|
|
text_ele->text_len = (intn) cpos + 1;
|
|
FE_GetTextInfo(context, text_ele, &text_info);
|
|
if (xpos > text_info.max_width)
|
|
{
|
|
start = cpos + 1;
|
|
}
|
|
else
|
|
{
|
|
end = cpos;
|
|
}
|
|
cpos = (start + end) / 2;
|
|
}
|
|
|
|
/* Find character width by trickery */
|
|
text_ele->text_len = (intn) cpos;
|
|
FE_GetTextInfo(context, text_ele, &text_info);
|
|
if ( returnCharStart != NULL ) *returnCharStart = startX + text_info.max_width;
|
|
text_ele->text_len = (intn) cpos+1;
|
|
FE_GetTextInfo(context, text_ele, &text_info);
|
|
if ( returnCharEnd != NULL ) *returnCharEnd = startX + text_info.max_width;
|
|
}
|
|
else
|
|
{ /* slow multibyte finding, I feel sad we can't use the
|
|
beautiful single byte algorithm above
|
|
*/
|
|
int32 prev_xpos, last_xpos, last_pos;
|
|
char *tptr;
|
|
|
|
PA_LOCK(tptr, char *, text_ele->text);
|
|
cpos = 0;
|
|
prev_xpos = 0;
|
|
last_xpos = text_ele->width ;
|
|
last_pos = end ;
|
|
while ((start <= end) && (cpos <= end))
|
|
{
|
|
text_ele->text_len = (intn) cpos ;
|
|
FE_GetTextInfo(context, text_ele, &text_info);
|
|
if (xpos > text_info.max_width)
|
|
{
|
|
prev_xpos = text_info.max_width ;
|
|
start = text_ele->text_len;
|
|
}
|
|
else /* since it goes char by char, finding ends here */
|
|
{
|
|
last_xpos = text_info.max_width ;
|
|
end = cpos;
|
|
break ;
|
|
}
|
|
/* go to next char */
|
|
cpos = start +
|
|
(int32)INTL_CharLen(charset,
|
|
(unsigned char *)(tptr + start));
|
|
}
|
|
cpos = start ;
|
|
if (cpos > last_pos)
|
|
cpos = last_pos ;
|
|
|
|
PA_UNLOCK(text_ele->text);
|
|
|
|
if ( returnCharStart != NULL ) *returnCharStart = startX + prev_xpos;
|
|
if ( returnCharEnd != NULL) *returnCharEnd = startX + last_xpos;
|
|
}
|
|
|
|
text_ele->text_len = (intn) orig_len;
|
|
|
|
return(cpos);
|
|
}
|
|
|
|
/*
|
|
* For clients that don't care about the bounds of what they hit.
|
|
*/
|
|
PRIVATE
|
|
int32
|
|
lo_ElementToCharOffset(MWContext *context, lo_DocState *state,
|
|
LO_Element *element, int32 x)
|
|
{
|
|
return lo_ElementToCharOffset2(context, state, element, x, NULL, NULL);
|
|
}
|
|
|
|
void
|
|
LO_StartSelection(MWContext *context, int32 x, int32 y, CL_Layer *layer)
|
|
{
|
|
#if 0
|
|
int32 doc_id;
|
|
lo_TopState *top_state;
|
|
lo_DocState *state;
|
|
LO_Element *eptr;
|
|
int32 position;
|
|
int32 ret_x, ret_y;
|
|
|
|
/*
|
|
* Get the unique document ID, and retreive this
|
|
* documents layout state.
|
|
*/
|
|
doc_id = XP_DOCID(context);
|
|
top_state = lo_FetchTopState(doc_id);
|
|
if ((top_state == NULL)||(top_state->doc_state == NULL))
|
|
{
|
|
return;
|
|
}
|
|
state = top_state->doc_state;
|
|
|
|
LO_HighlightSelection(context, FALSE);
|
|
state->selection_start = NULL;
|
|
state->selection_start_pos = 0;
|
|
state->selection_end = NULL;
|
|
state->selection_end_pos = 0;
|
|
|
|
position = 0;
|
|
eptr = lo_XYToDocumentElement(context, state, x, y, FALSE, TRUE, TRUE,
|
|
&ret_x, &ret_y);
|
|
if (eptr != NULL)
|
|
{
|
|
position = lo_ElementToCharOffset(context, state, eptr, ret_x);
|
|
if (position < 0)
|
|
{
|
|
position = 0;
|
|
}
|
|
}
|
|
|
|
state->selection_new = eptr;
|
|
state->selection_new_pos = position;
|
|
#endif
|
|
|
|
LO_Click(context, x, y, ! EDT_IS_EDITOR(context),
|
|
layer); /* Only allow selections in editor */
|
|
}
|
|
|
|
/*
|
|
* Used for right-clicks.
|
|
*/
|
|
void LO_SelectObject( MWContext *context, int32 x, int32 y, CL_Layer *layer)
|
|
{
|
|
int32 doc_id;
|
|
lo_TopState *top_state;
|
|
lo_DocState *state;
|
|
LO_HitResult result;
|
|
|
|
/*
|
|
* Get the unique document ID, and retreive this
|
|
* documents layout state.
|
|
*/
|
|
doc_id = XP_DOCID(context);
|
|
top_state = lo_FetchTopState(doc_id);
|
|
if ((top_state == NULL)||(top_state->doc_state == NULL))
|
|
{
|
|
return;
|
|
}
|
|
state = top_state->doc_state;
|
|
|
|
LO_Hit(context, x, y, FALSE, &result, layer);
|
|
|
|
if ( ! lo_ProcessAnchorClick(context, top_state, state, &result ) ) {
|
|
lo_ProcessClick(context, top_state, state, &result, FALSE, layer );
|
|
}
|
|
}
|
|
|
|
|
|
void LO_StartSelectionFromElement( MWContext *context, LO_Element *eptr, int32 new_pos, CL_Layer *layer )
|
|
{
|
|
int32 doc_id;
|
|
lo_TopState *top_state;
|
|
lo_DocState *state;
|
|
|
|
/*
|
|
* Get the unique document ID, and retreive this
|
|
* documents layout state.
|
|
*/
|
|
doc_id = XP_DOCID(context);
|
|
top_state = lo_FetchTopState(doc_id);
|
|
if ((top_state == NULL)||(top_state->doc_state == NULL))
|
|
{
|
|
return;
|
|
}
|
|
state = top_state->doc_state;
|
|
|
|
if ( eptr ) {
|
|
if ( ! lo_EnsureEditableSearchPrev2(context, state, &eptr, &new_pos) )
|
|
{
|
|
XP_ASSERT(FALSE);
|
|
return;
|
|
}
|
|
LO_ASSERT_POSITION2(context, eptr, new_pos);
|
|
}
|
|
|
|
LO_HighlightSelection(context, FALSE);
|
|
|
|
state->selection_start = NULL;
|
|
state->selection_start_pos = 0;
|
|
state->selection_end = NULL;
|
|
state->selection_end_pos = 0;
|
|
|
|
state->selection_new = eptr;
|
|
state->selection_new_pos = new_pos;
|
|
state->selection_layer = layer;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
|
|
PRIVATE
|
|
void
|
|
LO_DUMP_SELECTIONSTATE(MWContext *context)
|
|
{
|
|
int32 doc_id;
|
|
lo_TopState *top_state;
|
|
lo_DocState *state;
|
|
|
|
/*
|
|
* Get the unique document ID, and retreive this
|
|
* documents layout state.
|
|
*/
|
|
doc_id = XP_DOCID(context);
|
|
top_state = lo_FetchTopState(doc_id);
|
|
if ((top_state == NULL)||(top_state->doc_state == NULL))
|
|
{
|
|
return;
|
|
}
|
|
state = top_state->doc_state;
|
|
|
|
XP_TRACE(("selection 0x%08x %d 0x%08x %d new 0x%08x %d extending_start %d\n",
|
|
state->selection_start,
|
|
state->selection_start_pos,
|
|
state->selection_end,
|
|
state->selection_end_pos,
|
|
state->selection_new,
|
|
state->selection_new_pos,
|
|
state->extending_start));
|
|
}
|
|
|
|
PRIVATE
|
|
void
|
|
LO_DUMP_INSERT_POINT(char* s, LO_Element* element, int32 position)
|
|
{
|
|
if ( element )
|
|
{
|
|
XP_TRACE(("%s %d:%d.%d ", s, element->type, element->lo_any.ele_id, position));
|
|
}
|
|
else
|
|
{
|
|
XP_TRACE(("%s - NIL", s));
|
|
}
|
|
}
|
|
|
|
PRIVATE
|
|
void
|
|
LO_DUMP_POSITION(char* s, LO_Position* position)
|
|
{
|
|
LO_DUMP_INSERT_POINT(s, position->element, position->position);
|
|
}
|
|
|
|
PRIVATE
|
|
void
|
|
LO_DUMP_SELECTION2(char* s, LO_Element* a, int32 ap, LO_Element* b, int32 bp)
|
|
{
|
|
/* const char* kElementCodes = "?tlhi?????????????????";*/
|
|
XP_TRACE(("%s %d:%d.%d %d:%d.%d ", s,
|
|
a->type, a->lo_any.ele_id, ap,
|
|
b->type, b->lo_any.ele_id, bp));
|
|
}
|
|
|
|
PRIVATE
|
|
void
|
|
LO_DUMP_SELECTION(char* s, LO_Selection* selection)
|
|
{
|
|
LO_DUMP_SELECTION2(s, selection->begin.element, selection->begin.position,
|
|
selection->end.element, selection->end.position);
|
|
}
|
|
|
|
PRIVATE
|
|
void
|
|
LO_DUMP_HIT_RESULT(LO_HitResult* result)
|
|
{
|
|
switch ( result->type )
|
|
{
|
|
case LO_HIT_UNKNOWN:
|
|
XP_TRACE(("Unknown"));
|
|
break;
|
|
case LO_HIT_LINE:
|
|
{
|
|
char* s;
|
|
switch ( result->lo_hitLine.region )
|
|
{
|
|
case LO_HIT_LINE_REGION_BEFORE:
|
|
{
|
|
s = "before line";
|
|
}
|
|
break;
|
|
case LO_HIT_LINE_REGION_AFTER:
|
|
{
|
|
s = "after line";
|
|
}
|
|
break;
|
|
default:
|
|
s = "LO_HIT_LINE Bad region";
|
|
break;
|
|
}
|
|
LO_DUMP_SELECTION(s, & result->lo_hitLine.selection);
|
|
}
|
|
break;
|
|
case LO_HIT_ELEMENT:
|
|
{
|
|
char* s;
|
|
switch ( result->lo_hitElement.region )
|
|
{
|
|
case LO_HIT_ELEMENT_REGION_BEFORE:
|
|
{
|
|
s = "before element";
|
|
}
|
|
break;
|
|
case LO_HIT_ELEMENT_REGION_MIDDLE:
|
|
{
|
|
s = "middle element";
|
|
}
|
|
break;
|
|
case LO_HIT_ELEMENT_REGION_AFTER:
|
|
{
|
|
s = "after element";
|
|
}
|
|
break;
|
|
default:
|
|
{
|
|
s = "LO_HIT_ELEMENT unknown region";
|
|
}
|
|
break;
|
|
}
|
|
LO_DUMP_POSITION(s, & result->lo_hitElement.position);
|
|
}
|
|
break;
|
|
default:
|
|
{
|
|
XP_TRACE(("LO_HIT unknown result "));
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
Bool
|
|
LO_IsSelected(MWContext *context)
|
|
{
|
|
int32 doc_id;
|
|
lo_TopState *top_state;
|
|
lo_DocState *state;
|
|
|
|
/*
|
|
* Get the unique document ID, and retreive this
|
|
* documents layout state.
|
|
*/
|
|
doc_id = XP_DOCID(context);
|
|
top_state = lo_FetchTopState(doc_id);
|
|
if ((top_state == NULL)||(top_state->doc_state == NULL))
|
|
{
|
|
return FALSE;
|
|
}
|
|
state = top_state->doc_state;
|
|
|
|
return (state->selection_start != NULL);
|
|
}
|
|
|
|
Bool
|
|
LO_IsSelectionStarted(MWContext *context)
|
|
{
|
|
int32 doc_id;
|
|
lo_TopState *top_state;
|
|
lo_DocState *state;
|
|
|
|
/*
|
|
* Get the unique document ID, and retreive this
|
|
* documents layout state.
|
|
*/
|
|
doc_id = XP_DOCID(context);
|
|
top_state = lo_FetchTopState(doc_id);
|
|
if ((top_state == NULL)||(top_state->doc_state == NULL))
|
|
{
|
|
return FALSE;
|
|
}
|
|
state = top_state->doc_state;
|
|
|
|
return (state->selection_new != NULL);
|
|
}
|
|
|
|
PRIVATE
|
|
void lo_JiggleToMakeEditable( MWContext *context,
|
|
lo_DocState *state,
|
|
LO_Element** ppElement,
|
|
int32 *pPosition,
|
|
Bool bForward)
|
|
{
|
|
/* If it's not an editable element, move to find one that is.
|
|
* We don't call the ensure routines because we don't want to
|
|
* select end-of-paragraph linefeeds here.
|
|
*
|
|
*/
|
|
LO_Element* element = *ppElement;
|
|
int32 position = *pPosition;
|
|
|
|
if ( (! EDT_IS_EDITOR(context)) || (element && element->lo_any.edit_element) ) {
|
|
return;
|
|
}
|
|
|
|
while( element && ! element->lo_any.edit_element ){
|
|
LO_Element* newElement;
|
|
int32 newPosition;
|
|
lo_bump_position(context, state, element, position, &newElement, &newPosition, bForward);
|
|
if ( element == newElement && position == newPosition ) {
|
|
/* We got stuck */
|
|
element = NULL;
|
|
break;
|
|
}
|
|
element = newElement;
|
|
position = newPosition;
|
|
}
|
|
if ( ! element ) {
|
|
/* Ran off end of document */
|
|
element = *ppElement;
|
|
position = *pPosition;
|
|
while( element && ! element->lo_any.edit_element ){
|
|
LO_Element* newElement;
|
|
int32 newPosition;
|
|
lo_bump_position(context, state, element, position, &newElement, &newPosition, !bForward);
|
|
if ( element == newElement && position == newPosition ) {
|
|
/* We got stuck */
|
|
element = NULL;
|
|
break;
|
|
}
|
|
element = newElement;
|
|
position = newPosition;
|
|
}
|
|
}
|
|
if ( element ) {
|
|
*ppElement = element;
|
|
*pPosition = lo_GetElementEdge(element, !bForward);
|
|
}
|
|
}
|
|
|
|
/* This is only called from the editor. As a convienience to the
|
|
* editor, we return the selection as a 1/2 open selection. That
|
|
* means we convert the selection end into an insert point.
|
|
*/
|
|
|
|
void LO_GetSelectionEndPoints( MWContext *context,
|
|
LO_Element** ppStart,
|
|
intn *pStartOffset,
|
|
LO_Element** ppEnd,
|
|
intn *pEndOffset,
|
|
Bool* pbFromStart,
|
|
Bool* pbSingleItemSelection )
|
|
{
|
|
int32 doc_id;
|
|
lo_TopState *top_state;
|
|
lo_DocState *state;
|
|
LO_Element* startElement;
|
|
int32 startPosition;
|
|
LO_Element* endElement;
|
|
int32 endPosition;
|
|
/*
|
|
* Get the unique document ID, and retreive this
|
|
* documents layout state.
|
|
*/
|
|
doc_id = XP_DOCID(context);
|
|
top_state = lo_FetchTopState(doc_id);
|
|
if ((top_state == NULL)||(top_state->doc_state == NULL))
|
|
{
|
|
return;
|
|
}
|
|
state = top_state->doc_state;
|
|
|
|
startElement = state->selection_start;
|
|
startPosition = state->selection_start_pos;
|
|
/* If it's not an editable element, move backward to find one that is.
|
|
* We don't call the ensure routines because we don't want to
|
|
* select end-of-paragraph linefeeds here.
|
|
*
|
|
* But now that we have tables, we need to bump.
|
|
*/
|
|
lo_JiggleToMakeEditable( context, state, &startElement, &startPosition, FALSE);
|
|
if( ppStart ) *ppStart = startElement;
|
|
if( pStartOffset ) *pStartOffset = startPosition;
|
|
|
|
|
|
/* Convert the selection end into an insert point. */
|
|
endElement = state->selection_end;
|
|
endPosition = state->selection_end_pos;
|
|
if ( endElement )
|
|
{
|
|
lo_ConvertSelectionEndToInsertPoint(context, state, &endElement, &endPosition);
|
|
}
|
|
|
|
/* If it's not an editable element, move forward to find one that is.
|
|
* We don't call the ensure routines because we don't want to
|
|
* select end-of-paragraph linefeeds here.
|
|
*/
|
|
lo_JiggleToMakeEditable( context, state, &endElement, &endPosition, TRUE);
|
|
|
|
if( ppEnd ) *ppEnd = endElement;
|
|
if( pEndOffset ) *pEndOffset = endPosition;
|
|
|
|
if( pbFromStart ) *pbFromStart = state->extending_start;
|
|
|
|
/*
|
|
* the editor needs to know if the select is just a single thing like a
|
|
* image or a HRULE.
|
|
*/
|
|
if( pbSingleItemSelection ) *pbSingleItemSelection = (
|
|
startElement
|
|
&& startElement->type != LO_TEXT
|
|
&& startElement->type != LO_TEXTBLOCK
|
|
&& startElement == endElement );
|
|
|
|
/*
|
|
* For lloyd's edification, if we have a single item seleciton, start_pos
|
|
* is 0 and end_pos is 0 or 1
|
|
*/
|
|
XP_ASSERT( pbSingleItemSelection && *pbSingleItemSelection
|
|
?
|
|
startPosition == 0
|
|
&& ( endPosition == 0
|
|
|| endPosition == 1 )
|
|
:
|
|
TRUE);
|
|
}
|
|
|
|
void LO_GetSelectionNewPoint( MWContext *context,
|
|
LO_Element** ppNew,
|
|
intn *pNewOffset)
|
|
{
|
|
int32 doc_id;
|
|
lo_TopState *top_state;
|
|
lo_DocState *state;
|
|
|
|
/*
|
|
* Get the unique document ID, and retreive this
|
|
* documents layout state.
|
|
*/
|
|
doc_id = XP_DOCID(context);
|
|
top_state = lo_FetchTopState(doc_id);
|
|
if ((top_state == NULL)||(top_state->doc_state == NULL))
|
|
{
|
|
return;
|
|
}
|
|
state = top_state->doc_state;
|
|
|
|
if( ppNew ) *ppNew = state->selection_new;
|
|
if( pNewOffset ) *pNewOffset = state->selection_new_pos;
|
|
}
|
|
|
|
static intn
|
|
lo_compare_selections(LO_Element *sel1, int32 pos1,
|
|
LO_Element *sel2, int32 pos2)
|
|
{
|
|
if ((sel1 == NULL)||(sel2 == NULL))
|
|
{
|
|
if (sel1 == sel2)
|
|
{
|
|
return(0);
|
|
}
|
|
else if (sel1 == NULL)
|
|
{
|
|
return(-1);
|
|
}
|
|
else
|
|
{
|
|
return(1);
|
|
}
|
|
}
|
|
|
|
if (sel1->lo_any.ele_id < sel2->lo_any.ele_id)
|
|
{
|
|
return(-1);
|
|
}
|
|
else if (sel1->lo_any.ele_id > sel2->lo_any.ele_id)
|
|
{
|
|
return(1);
|
|
}
|
|
else if (sel1->lo_any.ele_id == sel2->lo_any.ele_id)
|
|
{
|
|
if (pos1 < pos2)
|
|
{
|
|
return(-1);
|
|
}
|
|
else if (pos1 > pos2)
|
|
{
|
|
return(1);
|
|
}
|
|
else if (pos1 == pos2)
|
|
{
|
|
return(0);
|
|
}
|
|
}
|
|
#ifdef DEBUG
|
|
assert (FALSE);
|
|
#endif
|
|
return(-1);
|
|
}
|
|
|
|
PRIVATE
|
|
intn
|
|
lo_ComparePositions(LO_Position* a, LO_Position* b)
|
|
{
|
|
return lo_compare_selections( a->element, a->position, b->element, b->position);
|
|
}
|
|
|
|
PRIVATE
|
|
LO_Element*
|
|
lo_BoundaryJumpingNext(MWContext *context, lo_DocState *state, LO_Element *eptr)
|
|
{
|
|
int32 last_id;
|
|
LO_Element *cell_parent;
|
|
/*
|
|
* If no next element, see if we need to jump the cell wall.
|
|
*/
|
|
Bool success=FALSE;
|
|
while(!success)
|
|
{
|
|
LO_Element* next = eptr->lo_any.next;
|
|
last_id = eptr->lo_any.ele_id;
|
|
cell_parent = NULL;
|
|
while ( eptr && ! next )
|
|
{
|
|
LO_Element* oldEptr = eptr;
|
|
eptr = lo_JumpCellWall(context, state, eptr);
|
|
if ( ! eptr || eptr == oldEptr )
|
|
break; /* Ran off the end of the document */
|
|
next = eptr->lo_any.next;
|
|
}
|
|
eptr = next;
|
|
/*
|
|
* When we walk onto a cell,
|
|
* we need to walk into it if
|
|
* it isn't empty.
|
|
*/
|
|
if ((eptr != NULL)&&
|
|
(eptr->type == LO_CELL)&&
|
|
(eptr->lo_cell.cell_list != NULL))
|
|
{
|
|
cell_parent = eptr;
|
|
eptr = eptr->lo_cell.cell_list;
|
|
}
|
|
|
|
/*
|
|
* Heuristic: For some difficult tables, you may come back
|
|
* to the same cell you left instead of progressing.
|
|
* If so, try manually moving the cell forward.
|
|
*/
|
|
if ((eptr != NULL)&&(eptr->lo_any.ele_id == last_id)&&
|
|
(cell_parent != NULL))
|
|
{
|
|
LO_Element *guess;
|
|
|
|
guess = cell_parent->lo_any.next;
|
|
/*
|
|
* If our guessed next element after the parent cell is
|
|
* a non-empty cell, make the first element in that
|
|
* cell our new element.
|
|
*/
|
|
if ((guess != NULL)&&(guess->type == LO_CELL)&&
|
|
(guess->lo_cell.cell_list != NULL))
|
|
{
|
|
eptr = guess->lo_cell.cell_list;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* We don't want infinite loops.
|
|
* If the element ID hasn't progressed, something
|
|
* serious is wrong, and we should punt.
|
|
*/
|
|
if ((eptr != NULL)&&(eptr->lo_any.ele_id <= last_id))
|
|
{
|
|
#ifdef DEBUG
|
|
XP_TRACE(("Cell Jump loop avoidance 1\n"));
|
|
#endif /* DEBUG */
|
|
return NULL;
|
|
}
|
|
|
|
success=TRUE; /* unless we detect a LO_TEXTBLOCK */
|
|
|
|
/* HACK: Fix for bug 123318. Only do the following check for the editor. If you don't do it
|
|
for the editor, backspacing between lines gets hosed. If you do it for the browser,
|
|
selection goes into an infinite loop.
|
|
|
|
Earlier comment by mike/anthony:
|
|
check textblock until not textblock leave prev as null if you hit
|
|
null */
|
|
|
|
if (EDT_IS_EDITOR( context ) && (( 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;
|
|
|
|
/* HACK: Fix for bug 123318. Only do the following check for the editor. If you don't do it
|
|
for the editor, backspacing between lines gets hosed. If you do it for the browser,
|
|
selection goes into an infinite loop.
|
|
|
|
Earlier comment by mike/anthony:
|
|
check textblock until not textblock leave prev as null if you hit
|
|
null */
|
|
|
|
if (EDT_IS_EDITOR( context ) && (( 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)
|
|
{
|
|
XP_Bool bEditable = FALSE;
|
|
|
|
if( eptr )
|
|
{
|
|
bEditable = lo_EditableElement(eptr->type) &&
|
|
( (!EDT_IS_EDITOR(context) ) ||
|
|
(eptr->lo_any.edit_element != 0 &&
|
|
eptr->lo_any.edit_offset >= 0));
|
|
#if 0
|
|
/* This may fix arrow key problem, but it prevents
|
|
us from clicking on the empty text element in a table cell,
|
|
so we must fix it a different way. */
|
|
/* cmanske. Not sure why these are created,
|
|
but after backspacing from the beginning of line 2 of wrapped text,
|
|
then type a space to cause wrapping again, a 0-len lo_text element
|
|
is inserted. It stops caret navigation of left arrow from
|
|
the 2nd line up to the end of the first.
|
|
This fixes that, but now the caret moves 1 char too far into
|
|
the 1st line (should be after the last visible char and before the space,
|
|
but its before the last visible char. */
|
|
if( bEditable && eptr->type == LO_TEXT && eptr->lo_text.text_len == 0 )
|
|
bEditable = FALSE;
|
|
#endif
|
|
}
|
|
return bEditable;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
/* cmanske This is really misnamed for an Editor context.
|
|
It extends selection to the end of any line (i.e., soft break or no break),
|
|
not just to a hard break
|
|
*/
|
|
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;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Find a soft linefeed or end of text on the line
|
|
This allows the selection of a "line" to work inside
|
|
a table cell
|
|
*/
|
|
LO_Element* tptr = end->element;
|
|
do {
|
|
if( tptr->type == LO_LINEFEED || !tptr->lo_any.next )
|
|
break;
|
|
tptr = tptr->lo_any.next;
|
|
}
|
|
while( tptr->type != LO_LINEFEED );
|
|
|
|
XP_ASSERT(tptr);
|
|
end->element = tptr;
|
|
if( tptr->type == LO_LINEFEED )
|
|
{
|
|
end->position = 0;
|
|
}
|
|
else
|
|
{
|
|
end->position = lo_GetLastCharEndPosition(end->element);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
void
|
|
lo_ValidatePosition(MWContext *context, LO_Position* position)
|
|
{
|
|
XP_ASSERT( position );
|
|
if ( position ) {
|
|
lo_ValidatePosition2(context, position->element, position->position);
|
|
}
|
|
}
|
|
|
|
void
|
|
lo_ValidatePosition2(MWContext *context, LO_Element* element, int32 position)
|
|
{
|
|
int32 length;
|
|
XP_ASSERT( element );
|
|
XP_ASSERT( context );
|
|
if ( context && element )
|
|
{
|
|
length = lo_GetElementLength(element);
|
|
XP_ASSERT( 0 <= position );
|
|
XP_ASSERT( position <= length );
|
|
if ( EDT_IS_EDITOR(context) )
|
|
{
|
|
/* Either it's a line-feed with an eop, or it has an edit_element. */
|
|
if ( ! element->lo_any.edit_element )
|
|
{
|
|
if (element->type == LO_LINEFEED)
|
|
{
|
|
if ( ! lo_IsEndOfParagraph2(context, element, position) )
|
|
{
|
|
XP_TRACE(("lo_ValidatePosition2: illegal position: a line feed without an end-of-paragraph marker."));
|
|
#ifdef DEBUG_EDIT_LAYOUT
|
|
XP_ASSERT(FALSE);
|
|
#endif
|
|
}
|
|
}
|
|
else
|
|
{
|
|
#ifdef DEBUG_EDIT_LAYOUT
|
|
XP_ASSERT(FALSE);
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
lo_ValidateSelection(MWContext *context, LO_Selection* selection)
|
|
{
|
|
XP_ASSERT( selection );
|
|
lo_ValidateSelection2( context, selection->begin.element, selection->begin.position,
|
|
selection->end.element, selection->end.position );
|
|
}
|
|
|
|
void
|
|
lo_ValidateSelection2(MWContext *context, LO_Element* beginElement, int32 beginPosition, LO_Element* endElement, int32 endPosition)
|
|
{
|
|
lo_ValidatePosition2( context, beginElement, beginPosition );
|
|
lo_ValidatePosition2( context, endElement, endPosition );
|
|
XP_ASSERT( lo_compare_selections(beginElement, beginPosition,
|
|
endElement, endPosition) <= 0 );
|
|
}
|
|
|
|
#endif
|
|
|
|
/*
|
|
* An insert point is one edit position greater than a selection end. But there are
|
|
* two insert points for the edit position that falls between elements. One
|
|
* insert point is for the end of the previous element. The other is for the
|
|
* beginning of the next element. There are two corresponding selection ends.
|
|
*
|
|
* end of previous element:
|
|
* insertion point: element = <previous>, position = <previous.length>
|
|
* selection end: element = <previous>, position = <previous.length> - 1
|
|
*
|
|
* start of next element:
|
|
* insertion point: element = <next>, position = 0
|
|
* selection end: element = <previous>, position = <previous.length>
|
|
*/
|
|
|
|
PRIVATE
|
|
Bool lo_IsAtStartOfLine(MWContext* context, lo_DocState* state,
|
|
LO_Element* element, int32 position)
|
|
{
|
|
LO_Element* prev;
|
|
if ( position != 0 || ! element )
|
|
return FALSE;
|
|
prev = element->lo_any.prev;
|
|
if ( ! prev )
|
|
return TRUE;
|
|
if ( prev->type == LO_LINEFEED ||
|
|
prev->type == LO_BULLET )
|
|
return TRUE;
|
|
/* Numbered list test. */
|
|
if ( EDT_IS_EDITOR(context) && prev->type == LO_TEXT && prev->lo_text.edit_element == 0 )
|
|
return TRUE;
|
|
return FALSE;
|
|
}
|
|
|
|
Bool lo_ConvertInsertPointToSelectionEnd(MWContext* context, lo_DocState * state,
|
|
LO_Element** pElement, int32* pPosition)
|
|
{
|
|
LO_Element* eptr = *pElement;
|
|
int32 position = *pPosition;
|
|
|
|
/* Special case for insert points at beginning of lines. */
|
|
if ( lo_IsAtStartOfLine(context, state, eptr, position) )
|
|
{
|
|
/* Skip backwards to line feed that has end-of-paragraph set */
|
|
while ( eptr && lo_BoundaryJumpingPrev(context, state, eptr) ) {
|
|
lo_bump_position(context, state, eptr, position, &eptr, &position, FALSE);
|
|
if ( ! eptr )
|
|
{
|
|
break;
|
|
}
|
|
|
|
if ( eptr->type == LO_LINEFEED )
|
|
{
|
|
if ( eptr->lo_linefeed.break_type != LO_LINEFEED_BREAK_SOFT )
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
/* Linefeeds are editable, but the if test above filters them out. */
|
|
else if (lo_EditableElement(eptr->type) )
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
position = lo_GetLastCharBeginPosition(eptr);
|
|
}
|
|
else
|
|
if ( lo_BumpEditablePositionBackward(context, state, &eptr, &position) )
|
|
{
|
|
if ( *pPosition == 0 )
|
|
{
|
|
/* This is the "Start of next element" case. */
|
|
lo_BumpEditablePositionForward(context, state, &eptr, &position);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* If we can't go backwards, it means that the insert point was at the beginning of the document.
|
|
* This is an error condition, because there is no legal selection end that corresponds
|
|
* to this insert point.
|
|
*/
|
|
return FALSE;
|
|
}
|
|
|
|
/* It turns out that the end is the address of the last byte selected, not just the position of the
|
|
* first byte of the two-byte character. So we have to bump forward and subtract one.
|
|
*
|
|
* (If we're a denormalized selection end, we don't increment our position.
|
|
* that's what the "if" is for.)
|
|
*/
|
|
if ( position < lo_GetElementLength(eptr) )
|
|
{
|
|
position = lo_IncrementPosition(eptr, position) - 1;
|
|
}
|
|
*pElement = eptr;
|
|
*pPosition = position;
|
|
return TRUE;
|
|
}
|
|
|
|
Bool lo_ConvertSelectionEndToInsertPoint(MWContext* context, lo_DocState * state,
|
|
LO_Element** pElement, int32* pPosition)
|
|
{
|
|
LO_Element* eptr = *pElement;
|
|
int32 position = *pPosition;
|
|
int32 length = lo_GetElementLength(*pElement);
|
|
|
|
LO_ASSERT_POSITION2( context, eptr, position );
|
|
/* It turns out that the end is the address of the last byte selected, not just the position of the
|
|
* first byte of the two-byte character. So we have to add one and bump backward.
|
|
*/
|
|
if ( position >= length )
|
|
{
|
|
/* this is the end-of-previous-element case. */
|
|
XP_ASSERT( position == length );
|
|
position = lo_GetLastCharBeginPosition(*pElement);
|
|
|
|
/* If we can't go forward, it means that the selection end was beyond the end of the document.
|
|
* This is an error condition.
|
|
*/
|
|
if ( ! lo_BumpEditablePositionForward(context, state, &eptr, &position) )
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
position = lo_DecrementPosition(eptr, position + 1);
|
|
/* This maps 0..length-1 into 1..length, which is OK. */
|
|
position = lo_IncrementPosition(eptr, position);
|
|
}
|
|
*pElement = eptr;
|
|
*pPosition = position;
|
|
|
|
LO_ASSERT_POSITION2( context, eptr, position );
|
|
return TRUE;
|
|
}
|
|
|
|
Bool
|
|
lo_NormalizeSelectionPoint(MWContext *context, lo_DocState * state, LO_Element** pEptr, int32* pPosition)
|
|
{
|
|
/*
|
|
* If the insert point is off the end of the element, make it the 0th selection of the next element.
|
|
* If we can't normalize it, leave it alone.
|
|
*/
|
|
Bool result = TRUE;
|
|
if ( *pEptr != NULL )
|
|
{
|
|
int32 length = lo_GetElementLength(*pEptr);
|
|
int32 newPosition = *pPosition;
|
|
XP_ASSERT( 0 <= newPosition && newPosition <= length );
|
|
if ( newPosition >= length )
|
|
{
|
|
if ( length <= 0 )
|
|
{
|
|
/* There are some zero-length text elements in the browser, when
|
|
* tables are around. Also, the editor uses zero-length text
|
|
* elements for empty paragraphs and empty lines.
|
|
*/
|
|
newPosition = 0;
|
|
}
|
|
else {
|
|
/* Needs to be internationalized */
|
|
newPosition = lo_GetLastCharBeginPosition(*pEptr);
|
|
result = lo_BumpNormalizedEditablePosition(context, state, pEptr, &newPosition, TRUE);
|
|
}
|
|
if ( result ) {
|
|
*pPosition = newPosition;
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void
|
|
lo_NormalizeSelectionEnd(MWContext *context, lo_DocState * state, LO_Element** pEptr, int32* pPosition)
|
|
{
|
|
/*
|
|
* If the selection end point is off the end of the element, make it the last position of the element.
|
|
*/
|
|
if ( *pEptr != NULL )
|
|
{
|
|
int32 length = lo_GetElementLength(*pEptr);
|
|
XP_ASSERT( 0 <= *pPosition && *pPosition <= length );
|
|
if ( *pPosition >= length )
|
|
{
|
|
*pPosition = lo_GetLastCharEndPosition(*pEptr);
|
|
}
|
|
}
|
|
}
|
|
|
|
PRIVATE
|
|
intn
|
|
lo_FancyHalfCompareSelections(LO_Element* start, int32 startPosition, LO_Element* end, int32 endPosition)
|
|
{
|
|
int32 startID = start->lo_any.ele_id;
|
|
int32 endID = end->lo_any.ele_id;
|
|
if ( startID < endID )
|
|
{
|
|
if ( startID < (endID - 1) )
|
|
return -1; /* Has to be less */
|
|
if ( startPosition < lo_GetElementLength(start) )
|
|
return -1;
|
|
if ( endPosition > 0 )
|
|
return -1;
|
|
return 0;
|
|
}
|
|
XP_ASSERT(FALSE);
|
|
return -1;
|
|
}
|
|
|
|
PRIVATE
|
|
intn
|
|
lo_FancyCompareSelections(MWContext *context, lo_DocState * state,
|
|
LO_Element* start, int32 startPosition, LO_Element* end, int32 endPosition)
|
|
{
|
|
#if 0 /* We can't normalize past the end of the document. */
|
|
Bool startIsEOD = ! lo_NormalizeSelectionPoint(context, state, &start, &startPosition);
|
|
Bool endIsEOD = ! lo_NormalizeSelectionPoint(context, state, &end, &endPosition);
|
|
if ( startIsEOD && endIsEOD ) return 0;
|
|
else if ( startIsEOD ) return 1;
|
|
else if ( endIsEOD ) return -1;
|
|
|
|
return lo_compare_selections(start, startPosition, end, endPosition);
|
|
#endif
|
|
int32 startID = 0;
|
|
int32 endID = 0;
|
|
|
|
/* Can not perform if no start or end element */
|
|
if(!start || !end)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
/* Now safe to set IDs */
|
|
startID = start->lo_any.ele_id;
|
|
endID = end->lo_any.ele_id;
|
|
|
|
if ( startID < endID )
|
|
{
|
|
return lo_FancyHalfCompareSelections(start, startPosition, end, endPosition);
|
|
}
|
|
else if ( startID == endID )
|
|
{
|
|
if ( startPosition < endPosition )
|
|
return -1;
|
|
else if ( startPosition == endPosition )
|
|
return 0;
|
|
return 1;
|
|
}
|
|
else /* startID > endID */
|
|
{
|
|
return - lo_FancyHalfCompareSelections(end, endPosition, start, startPosition);
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
lo_StartNewSelection(MWContext *context, lo_DocState * state,
|
|
LO_Element* eptr, int32 position)
|
|
{
|
|
/*
|
|
* If the user hasn't moved the insertion point yet, start a selection.
|
|
*/
|
|
LO_Element* i_eptr = state->selection_new;
|
|
int32 i_position = state->selection_new_pos;
|
|
intn compare = lo_FancyCompareSelections(context, state, i_eptr, i_position, eptr, position);
|
|
if ( compare < 0 )
|
|
{
|
|
/* old < now, so they're dragging towards the end of the document */
|
|
lo_ConvertInsertPointToSelectionEnd(context, state, &eptr, &position);
|
|
state->extending_start = FALSE;
|
|
lo_FullSetSelection(context, state, i_eptr, i_position, eptr, position, FALSE);
|
|
}
|
|
else if ( compare == 0 )
|
|
{
|
|
/* The user hasn't moved the mouse far enough to select anything. */
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
/* Dragging towards start of document */
|
|
lo_ConvertInsertPointToSelectionEnd(context, state, &i_eptr, &i_position);
|
|
state->extending_start = TRUE;
|
|
lo_FullSetSelection(context, state, eptr, position, i_eptr, i_position, TRUE);
|
|
}
|
|
}
|
|
|
|
void
|
|
LO_ExtendSelection(MWContext *context, int32 x, int32 y)
|
|
{
|
|
int32 doc_id;
|
|
lo_TopState *top_state;
|
|
lo_DocState *state;
|
|
LO_Element *eptr;
|
|
int32 position;
|
|
LO_HitResult result;
|
|
|
|
/*
|
|
* Get the unique document ID, and retreive this
|
|
* documents layout state.
|
|
*/
|
|
doc_id = XP_DOCID(context);
|
|
top_state = lo_FetchTopState(doc_id);
|
|
if ((top_state == NULL)||(top_state->doc_state == NULL))
|
|
{
|
|
return;
|
|
}
|
|
state = top_state->doc_state;
|
|
|
|
LO_Hit(context, x, y, FALSE, &result, state->selection_layer);
|
|
|
|
position = 0;
|
|
|
|
eptr = NULL;
|
|
switch ( result.type )
|
|
{
|
|
case LO_HIT_LINE:
|
|
{
|
|
switch ( result.lo_hitLine.region )
|
|
{
|
|
case LO_HIT_LINE_REGION_BEFORE:
|
|
{
|
|
/* Insertion point before first element of line */
|
|
eptr = result.lo_hitLine.selection.begin.element;
|
|
position = result.lo_hitLine.selection.begin.position;
|
|
}
|
|
break;
|
|
case LO_HIT_LINE_REGION_AFTER:
|
|
{
|
|
/* Insertion point after last element of line */
|
|
eptr = result.lo_hitLine.selection.end.element;
|
|
position = result.lo_hitLine.selection.end.position;
|
|
lo_ConvertSelectionEndToInsertPoint(context, state, &eptr, &position);
|
|
if ( eptr->type == LO_LINEFEED ) position = 0; /* For editable line-feeds */
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
case LO_HIT_ELEMENT:
|
|
{
|
|
eptr = result.lo_hitElement.position.element;
|
|
position = result.lo_hitElement.position.position;
|
|
switch ( result.lo_hitElement.region )
|
|
{
|
|
case LO_HIT_ELEMENT_REGION_BEFORE:
|
|
break;
|
|
case LO_HIT_ELEMENT_REGION_MIDDLE:
|
|
{
|
|
/* The user wants this item selected.
|
|
*
|
|
* If the anchor point is before or at this item, the insert point should be
|
|
* after this item.
|
|
*
|
|
*/
|
|
LO_Element* anchorElement;
|
|
int32 anchorPosition;
|
|
lo_GetAnchorPoint(context, state, &anchorElement, &anchorPosition);
|
|
if ( lo_FancyCompareSelections(context, state, anchorElement, anchorPosition, eptr, position) <= 0 )
|
|
{
|
|
lo_BumpEditablePositionForward(context, state, &eptr, &position);
|
|
}
|
|
}
|
|
break;
|
|
case LO_HIT_ELEMENT_REGION_AFTER:
|
|
{
|
|
lo_BumpEditablePositionForward(context, state, &eptr, &position);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
if (eptr == NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
lo_ExtendSelectionToPosition2(context, top_state, state, eptr, position);
|
|
}
|
|
|
|
void
|
|
lo_ExtendSelectionToPosition2(MWContext *context, lo_TopState* top_state, lo_DocState *state, LO_Element* eptr, int32 position)
|
|
{
|
|
/*
|
|
* Are we starting a new selection?
|
|
*/
|
|
if (state->selection_new != NULL)
|
|
{
|
|
lo_StartNewSelection(context, state, eptr, position);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* If we somehow got here without having any selection or started
|
|
* selection, start it now where we are.
|
|
*/
|
|
if (state->selection_start == NULL)
|
|
{
|
|
state->selection_start = eptr;
|
|
state->selection_start_pos = position;
|
|
state->selection_end = state->selection_start;
|
|
state->selection_end_pos = state->selection_start_pos;
|
|
state->extending_start = FALSE;
|
|
}
|
|
|
|
/*
|
|
* By some horrendous bug the start was set and the end was not,
|
|
* and there was no newly started selection. Correct this now.
|
|
*/
|
|
if (state->selection_end == NULL)
|
|
{
|
|
state->selection_end = state->selection_start;
|
|
state->selection_end_pos = state->selection_start_pos;
|
|
state->extending_start = FALSE;
|
|
}
|
|
|
|
{
|
|
LO_Element* anchorElement;
|
|
int32 anchorPosition;
|
|
intn compare;
|
|
|
|
lo_GetAnchorPoint(context, state, &anchorElement, &anchorPosition);
|
|
compare = lo_FancyCompareSelections(context, state, eptr, position, anchorElement, anchorPosition );
|
|
|
|
if ( compare == 0 )
|
|
{
|
|
/* The anchor point might not be editable -- it might be the start of a line feed.
|
|
* So Jiggle it.
|
|
*/
|
|
lo_JiggleToMakeEditable(context, state, &anchorElement, &anchorPosition, FALSE);
|
|
lo_SetInsertPoint(context, top_state, anchorElement, anchorPosition, state->selection_layer);
|
|
}
|
|
else
|
|
{
|
|
LO_Element *changed_start, *changed_end;
|
|
int32 changed_start_pos, changed_end_pos;
|
|
Bool extendingStart;
|
|
if ( compare < 0 )
|
|
{
|
|
/* new position is less than anchor. So it's the start */
|
|
changed_start = eptr;
|
|
changed_start_pos = position;
|
|
changed_end = anchorElement;
|
|
changed_end_pos = anchorPosition;
|
|
extendingStart = TRUE;
|
|
}
|
|
else
|
|
{
|
|
/* new position is greater than anchor. So it's the end */
|
|
changed_start = anchorElement;
|
|
changed_start_pos = anchorPosition;
|
|
changed_end = eptr;
|
|
changed_end_pos = position;
|
|
extendingStart = FALSE;
|
|
}
|
|
lo_ConvertInsertPointToSelectionEnd(context, state, &changed_end, &changed_end_pos);
|
|
|
|
lo_FullSetSelection(context, state, changed_start, changed_start_pos,
|
|
changed_end, changed_end_pos, extendingStart);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
LO_EndSelection(MWContext *context)
|
|
{
|
|
int32 doc_id;
|
|
lo_TopState *top_state;
|
|
lo_DocState *state;
|
|
|
|
/*
|
|
* Get the unique document ID, and retreive this
|
|
* documents layout state.
|
|
*/
|
|
doc_id = XP_DOCID(context);
|
|
top_state = lo_FetchTopState(doc_id);
|
|
if ((top_state == NULL)||(top_state->doc_state == NULL))
|
|
{
|
|
return;
|
|
}
|
|
state = top_state->doc_state;
|
|
}
|
|
|
|
|
|
#ifdef NOT_USED
|
|
static void
|
|
lo_copy_color(LO_Color *from_color, LO_Color *to_color)
|
|
{
|
|
to_color->red = from_color->red;
|
|
to_color->green = from_color->green;
|
|
to_color->blue = from_color->blue;
|
|
}
|
|
|
|
#endif /* NOT_USED */
|
|
|
|
|
|
/*
|
|
* If this element is part of a cell, return that cell.
|
|
*/
|
|
LO_Element *
|
|
lo_JumpCellWall(MWContext *context, lo_DocState *state, LO_Element *eptr)
|
|
{
|
|
LO_Element *parent;
|
|
LO_Element *cell = NULL;
|
|
LO_Element *prev_eptr;
|
|
LO_Element *stop_eptr;
|
|
int32 x, y;
|
|
int32 ret_x, ret_y;
|
|
Bool guess_mode;
|
|
CL_Layer *layer;
|
|
|
|
/*
|
|
* We only turn this on if we are desparate and think
|
|
* we are in a cell with NO selectable elements!
|
|
*/
|
|
guess_mode = FALSE;
|
|
|
|
prev_eptr = NULL;
|
|
x = eptr->lo_any.x + eptr->lo_any.x_offset;
|
|
y = eptr->lo_any.y + eptr->lo_any.y_offset;
|
|
|
|
/*
|
|
* If this element is a zero width linefeed, it is unselectable, so
|
|
* we need to special case this to work off the previous element
|
|
* in the cell (if any).
|
|
*/
|
|
if (eptr->lo_any.width <= 0)
|
|
{
|
|
prev_eptr = eptr->lo_any.prev;
|
|
/*
|
|
* Gads, you have have zero width text followed by a
|
|
* zero width linefeed. Move back until we get a real
|
|
* width or can't go any furthur.
|
|
*/
|
|
while ((prev_eptr != NULL)&&(prev_eptr->lo_any.width <= 0))
|
|
{
|
|
prev_eptr = prev_eptr->lo_any.prev;
|
|
}
|
|
|
|
if (prev_eptr != NULL)
|
|
{
|
|
x = prev_eptr->lo_any.x + prev_eptr->lo_any.x_offset;
|
|
y = prev_eptr->lo_any.y + prev_eptr->lo_any.y_offset;
|
|
}
|
|
/*
|
|
* If there is no previous element to the zero-width
|
|
* one, guess in desperation by moving back one.
|
|
* Probably there are no other elements in this
|
|
* cell, go into guess_mode.
|
|
*/
|
|
else
|
|
{
|
|
guess_mode = TRUE;
|
|
/*
|
|
* We used to subtract 1 from the x pixel position, but
|
|
* that didn't work in all cases and had the potential to
|
|
* cause infinite loops. Now we don't and I feel a lot
|
|
* happier.
|
|
*/
|
|
}
|
|
}
|
|
|
|
/*
|
|
* figure out when to stop traversing inward.
|
|
* If we have a prev_eptr then that is where we stop,
|
|
* otherwise stop if we hit the starting element.
|
|
*/
|
|
if (prev_eptr != NULL)
|
|
{
|
|
stop_eptr = prev_eptr;
|
|
}
|
|
else
|
|
{
|
|
stop_eptr = eptr;
|
|
}
|
|
|
|
parent = NULL;
|
|
/* This deals with the case where the table is in a layer.
|
|
* We want to search within the corresponding block and
|
|
* not in the base document for the enclosing cell.
|
|
* BUGBUG This makes the assumption that if this table is
|
|
* in a layer, that layer is part of the current selection
|
|
* state. I don't know if this assumption is always true
|
|
* for all invocations of this function.
|
|
* joki: changing function to work based on layer key focus
|
|
* instead of selected layer so that form elements will be
|
|
* tabable in tables. 5/25/97.
|
|
*/
|
|
if (context->compositor && !(CL_IsKeyEventGrabber(context->compositor, NULL))) {
|
|
layer = CL_GetKeyEventGrabber(context->compositor);
|
|
|
|
if (layer && LO_GetIdFromLayer(context, layer) != LO_DOCUMENT_LAYER_ID) {
|
|
LO_CellStruct *layer_cell = lo_GetCellFromLayer(context, layer);
|
|
if (layer_cell)
|
|
cell = lo_XYToCellElement(context,
|
|
state,
|
|
layer_cell,
|
|
x, y, TRUE, FALSE, FALSE);
|
|
}
|
|
}
|
|
|
|
if (cell == NULL)
|
|
cell = lo_XYToDocumentElement(context, state,
|
|
x, y, TRUE, FALSE, FALSE,
|
|
&ret_x, &ret_y);
|
|
while ((cell != NULL)&&(cell != stop_eptr)&&(cell->type == LO_CELL))
|
|
{
|
|
parent = cell;
|
|
cell = lo_XYToCellElement(context, state, (LO_CellStruct *)cell,
|
|
x, y, TRUE, FALSE, FALSE);
|
|
}
|
|
|
|
/*
|
|
* If we've traversed to our stopping point.
|
|
*/
|
|
if (cell == stop_eptr)
|
|
{
|
|
/*
|
|
* If the stopping point has a cell parent, return it.
|
|
*/
|
|
if ((parent != NULL)&&(parent->type == LO_CELL))
|
|
{
|
|
return(parent);
|
|
}
|
|
/*
|
|
* Else we aren't in a cell, return the original element.
|
|
*/
|
|
else
|
|
{
|
|
return(eptr);
|
|
}
|
|
}
|
|
/*
|
|
* Else if we are in guess mode, we found a valid parent
|
|
* cell that appears to have no children, then we need to check
|
|
* if the first child of the parent is our stop_eptr, and if
|
|
* so, return the cell parent.
|
|
*/
|
|
else if ((guess_mode != FALSE)&&(cell == NULL)&&(parent != NULL)&&
|
|
(parent->type == LO_CELL))
|
|
{
|
|
if (parent->lo_cell.cell_list == stop_eptr)
|
|
{
|
|
return(parent);
|
|
}
|
|
|
|
/*
|
|
* If we get here we don't know what we found, so just
|
|
* return.
|
|
*/
|
|
return(eptr);
|
|
}
|
|
/* If we found a parent that's a cell, then return it.
|
|
* I think this case happens when we hit an element
|
|
* that has the same coordinates are the element we
|
|
* were given. An example would be a zero-length
|
|
* text element before a linefeed. Maybe we should
|
|
* change the search algorithm to skip zero-lenght elements?
|
|
*
|
|
*/
|
|
else if ( (parent != NULL) && (parent->type == LO_CELL))
|
|
{
|
|
return parent;
|
|
}
|
|
/*
|
|
* Else, we've missed the stopping point for some reason.
|
|
* Assume we aren't in a cell.
|
|
*/
|
|
else
|
|
{
|
|
return(eptr);
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
lo_SetSelect(MWContext *context, lo_DocState *state,
|
|
LO_Element *start, int32 start_pos, LO_Element *end, int32 end_pos,
|
|
Bool on)
|
|
{
|
|
LO_Element *eptr;
|
|
|
|
if ((start == NULL)||(end == NULL))
|
|
{
|
|
return;
|
|
}
|
|
|
|
eptr = start;
|
|
while ((eptr != NULL)&&(eptr->lo_any.ele_id <= end->lo_any.ele_id))
|
|
{
|
|
int32 last_id;
|
|
|
|
switch (eptr->type)
|
|
{
|
|
case LO_TEXT:
|
|
if (eptr->lo_text.text != NULL)
|
|
{
|
|
int32 p1, p2;
|
|
|
|
if (eptr == start)
|
|
{
|
|
p1 = start_pos;
|
|
}
|
|
else
|
|
{
|
|
p1 = 0;
|
|
}
|
|
|
|
if (eptr == end)
|
|
{
|
|
p2 = end_pos;
|
|
}
|
|
else
|
|
{
|
|
p2 = lo_GetLastCharEndPosition(eptr);
|
|
}
|
|
if (p2 < p1)
|
|
{
|
|
p2 = p1;
|
|
}
|
|
|
|
if (on != FALSE)
|
|
{
|
|
eptr->lo_text.ele_attrmask |= LO_ELE_SELECTED;
|
|
eptr->lo_text.sel_start = (intn) p1;
|
|
eptr->lo_text.sel_end = (intn) p2;
|
|
}
|
|
else
|
|
{
|
|
eptr->lo_text.ele_attrmask &= (~(LO_ELE_SELECTED));
|
|
eptr->lo_text.sel_start = -1;
|
|
eptr->lo_text.sel_end = -1;
|
|
}
|
|
}
|
|
break;
|
|
case LO_LINEFEED:
|
|
if (on != FALSE)
|
|
{
|
|
eptr->lo_linefeed.ele_attrmask |= LO_ELE_SELECTED;
|
|
eptr->lo_linefeed.sel_start = 0;
|
|
eptr->lo_linefeed.sel_end = 0;
|
|
}
|
|
else
|
|
{
|
|
eptr->lo_linefeed.ele_attrmask &= (~(LO_ELE_SELECTED));
|
|
eptr->lo_linefeed.sel_start = -1;
|
|
eptr->lo_linefeed.sel_end = -1;
|
|
}
|
|
break;
|
|
case LO_IMAGE:
|
|
#ifdef EDITOR
|
|
if ( EDT_IS_EDITOR(context) )
|
|
{
|
|
if (on != FALSE)
|
|
{
|
|
eptr->lo_image.ele_attrmask |= LO_ELE_SELECTED;
|
|
eptr->lo_image.sel_start = 0;
|
|
eptr->lo_image.sel_end = 0;
|
|
}
|
|
else
|
|
{
|
|
eptr->lo_image.ele_attrmask &= (~(LO_ELE_SELECTED));
|
|
eptr->lo_image.sel_start = -1;
|
|
eptr->lo_image.sel_end = -1;
|
|
}
|
|
}
|
|
break;
|
|
#endif /* EDITOR */
|
|
case LO_HRULE:
|
|
#ifdef EDITOR
|
|
if ( EDT_IS_EDITOR(context) )
|
|
{
|
|
if (on != FALSE)
|
|
{
|
|
eptr->lo_hrule.ele_attrmask |= LO_ELE_SELECTED;
|
|
eptr->lo_hrule.sel_start = 0;
|
|
eptr->lo_hrule.sel_end = 0;
|
|
}
|
|
else
|
|
{
|
|
eptr->lo_hrule.ele_attrmask &= (~(LO_ELE_SELECTED));
|
|
eptr->lo_hrule.sel_start = -1;
|
|
eptr->lo_hrule.sel_end = -1;
|
|
}
|
|
}
|
|
break;
|
|
#endif /* EDITOR */
|
|
case LO_FORM_ELE:
|
|
#ifdef EDITOR
|
|
if ( EDT_IS_EDITOR(context) )
|
|
{
|
|
if (on != FALSE)
|
|
{
|
|
eptr->lo_form.ele_attrmask |= LO_ELE_SELECTED;
|
|
eptr->lo_form.sel_start = 0;
|
|
eptr->lo_form.sel_end = 0;
|
|
}
|
|
else
|
|
{
|
|
eptr->lo_form.ele_attrmask &= (~(LO_ELE_SELECTED));
|
|
eptr->lo_form.sel_start = -1;
|
|
eptr->lo_form.sel_end = -1;
|
|
}
|
|
}
|
|
break;
|
|
#endif /* EDITOR */
|
|
case LO_BULLET:
|
|
case LO_SUBDOC:
|
|
case LO_TABLE:
|
|
default:
|
|
break;
|
|
}
|
|
|
|
last_id = eptr->lo_any.ele_id;
|
|
|
|
/*
|
|
* Jump cell boundries if there is one between start
|
|
* and end.
|
|
*/
|
|
if ((eptr->lo_any.next == NULL)&&(eptr != end))
|
|
{
|
|
eptr = lo_JumpCellWall(context, state, eptr);
|
|
}
|
|
|
|
eptr = eptr->lo_any.next;
|
|
|
|
/*
|
|
* When we walk onto a cell, we need to walk into
|
|
* it if it isn't empty.
|
|
*/
|
|
if ((eptr != NULL)&&(eptr->type == LO_CELL)&&
|
|
(eptr->lo_cell.cell_list != NULL))
|
|
{
|
|
eptr = eptr->lo_cell.cell_list;
|
|
}
|
|
|
|
/*
|
|
* We don't want infinite loops.
|
|
* If the element ID hasn't progressed, something
|
|
* serious is wrong, and we should punt.
|
|
*/
|
|
if ((eptr != NULL)&&(eptr->lo_any.ele_id <= last_id))
|
|
{
|
|
#ifdef DEBUG
|
|
XP_TRACE(("Selection loop avoidance 1\n"));
|
|
#endif /* DEBUG */
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
typedef enum {
|
|
HL_STARTED,
|
|
HL_NOT_STARTED,
|
|
HL_COMPLETED
|
|
} LO_HighlightState;
|
|
|
|
/* Highlight a range of elements from within a list of layout
|
|
elements. Return values:
|
|
|
|
HL_STARTED At least one element highlighted from list, but
|
|
last element in range was not encountered on list.
|
|
|
|
HL_NOT_STARTED Specified range did not contain any of the range
|
|
of elements specified in the arguments.
|
|
|
|
HL_COMPLETED Specified range of elements has been highlighted. */
|
|
static LO_HighlightState
|
|
lo_HighlightElementList(MWContext *context,
|
|
int32 base_x, int32 base_y,
|
|
LO_Element *list,
|
|
LO_Element *start, int32 start_pos,
|
|
LO_Element *end, int32 end_pos,
|
|
CL_Layer *layer,
|
|
Bool on)
|
|
{
|
|
LO_Element *eptr;
|
|
int16 charset;
|
|
int32 p1, p2;
|
|
LO_HighlightState highlight_state;
|
|
|
|
if (list == NULL)
|
|
return HL_COMPLETED;
|
|
|
|
/* Locate first element to be highlighted in list */
|
|
eptr = list;
|
|
while (eptr && eptr != start) {
|
|
if (eptr->type == LO_CELL) {
|
|
int32 x_offset = 0;
|
|
int32 y_offset = 0;
|
|
CL_Layer *cell_layer = layer;
|
|
|
|
/* LO_CELLs can be either ordinary table cells or
|
|
containers for in-flow layers */
|
|
if (eptr->lo_cell.cell_inflow_layer) {
|
|
cell_layer = eptr->lo_cell.cell_inflow_layer;
|
|
lo_GetLayerXYShift(cell_layer, &x_offset, &y_offset);
|
|
}
|
|
|
|
if (eptr->lo_cell.cell_list) {
|
|
highlight_state=lo_HighlightElementList(context,
|
|
base_x - x_offset,
|
|
base_y - y_offset,
|
|
eptr->lo_cell.cell_list,
|
|
start, start_pos,
|
|
end, end_pos,
|
|
cell_layer, on);
|
|
if (highlight_state == HL_COMPLETED)
|
|
return HL_COMPLETED;
|
|
if (highlight_state == HL_STARTED) {
|
|
eptr = eptr->lo_any.next;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
eptr = eptr->lo_any.next;
|
|
}
|
|
if (!eptr)
|
|
return HL_NOT_STARTED;
|
|
|
|
/* Paint highlighted elements */
|
|
while ((eptr != NULL) && (eptr->lo_any.ele_id <= end->lo_any.ele_id))
|
|
{
|
|
int32 last_id;
|
|
|
|
switch (eptr->type)
|
|
{
|
|
case LO_TEXT:
|
|
if (eptr->lo_text.text == NULL)
|
|
break;
|
|
|
|
if (eptr == start)
|
|
p1 = start_pos;
|
|
else
|
|
p1 = 0;
|
|
|
|
if (eptr == end)
|
|
p2 = end_pos;
|
|
else
|
|
p2 = lo_GetLastCharEndPosition(eptr);
|
|
|
|
if (p2 < p1)
|
|
p2 = p1;
|
|
|
|
if ( p2 >= eptr->lo_text.text_len )
|
|
p2 = lo_GetLastCharEndPosition(eptr);
|
|
|
|
if (on != FALSE)
|
|
{
|
|
eptr->lo_text.ele_attrmask |= LO_ELE_SELECTED;
|
|
eptr->lo_text.sel_start = (intn) p1;
|
|
eptr->lo_text.sel_end = (intn) p2;
|
|
}
|
|
else
|
|
{
|
|
eptr->lo_text.ele_attrmask &= (~(LO_ELE_SELECTED));
|
|
eptr->lo_text.sel_start = -1;
|
|
eptr->lo_text.sel_end = -1;
|
|
}
|
|
|
|
charset = ((LO_TextStruct *) eptr)->text_attr->charset;
|
|
if ((eptr == start) || ((eptr == end) &&
|
|
INTL_CharSetType(charset) != SINGLEBYTE))
|
|
{
|
|
/* ugly processing for multibyte here */
|
|
char *string;
|
|
int n;
|
|
|
|
PA_LOCK(string, char *, eptr->lo_text.text);
|
|
|
|
/*
|
|
* find beginning of first character
|
|
*/
|
|
switch (n = INTL_NthByteOfChar(charset, string, (int)(p1+1)))
|
|
{
|
|
case 0:
|
|
case 1:
|
|
break;
|
|
default:
|
|
p1 -= (n - 1);
|
|
if (p1 < 0)
|
|
p1 = 0;
|
|
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* find end of last character
|
|
*/
|
|
switch (n = INTL_NthByteOfChar(charset, string, (int)(p2+1)))
|
|
{
|
|
case 0:
|
|
break;
|
|
default:
|
|
p2 -= (n - 1);
|
|
if (p2 < 0)
|
|
p2 = 0;
|
|
|
|
/* FALL THROUGH */
|
|
case 1:
|
|
p2 += INTL_IsLeadByte(charset, string[p2]);
|
|
if (p2 > lo_GetLastCharEndPosition(eptr))
|
|
{
|
|
p2 = lo_GetLastCharEndPosition(eptr);
|
|
}
|
|
break;
|
|
}
|
|
|
|
PA_UNLOCK(eptr->lo_text.text);
|
|
}
|
|
|
|
if (p1 <= p2)
|
|
{
|
|
lo_DisplaySubtext(context,
|
|
(LO_TextStruct *)eptr, p1, p2,
|
|
!on,
|
|
layer);
|
|
}
|
|
break;
|
|
|
|
case LO_LINEFEED:
|
|
if (on != FALSE)
|
|
{
|
|
eptr->lo_linefeed.ele_attrmask |= LO_ELE_SELECTED;
|
|
eptr->lo_linefeed.sel_start = 0;
|
|
eptr->lo_linefeed.sel_end = 0;
|
|
}
|
|
else
|
|
{
|
|
eptr->lo_linefeed.ele_attrmask &= (~(LO_ELE_SELECTED));
|
|
eptr->lo_linefeed.sel_start = -1;
|
|
eptr->lo_linefeed.sel_end = -1;
|
|
}
|
|
|
|
lo_RefreshElement(eptr, layer, FALSE);
|
|
break;
|
|
|
|
case LO_HRULE:
|
|
#ifdef EDITOR
|
|
if ( EDT_IS_EDITOR(context) )
|
|
{
|
|
if (on != FALSE)
|
|
{
|
|
eptr->lo_hrule.ele_attrmask |= LO_ELE_SELECTED;
|
|
eptr->lo_hrule.sel_start = 0;
|
|
eptr->lo_hrule.sel_end = 0;
|
|
}
|
|
else
|
|
{
|
|
eptr->lo_hrule.ele_attrmask &= (~(LO_ELE_SELECTED));
|
|
eptr->lo_hrule.sel_start = -1;
|
|
eptr->lo_hrule.sel_end = -1;
|
|
}
|
|
|
|
lo_RefreshElement(eptr, layer, FALSE);
|
|
}
|
|
break;
|
|
#endif
|
|
case LO_FORM_ELE:
|
|
#ifdef EDITOR
|
|
if ( EDT_IS_EDITOR(context) )
|
|
{
|
|
if (on != FALSE)
|
|
{
|
|
eptr->lo_form.ele_attrmask |= LO_ELE_SELECTED;
|
|
eptr->lo_form.sel_start = 0;
|
|
eptr->lo_form.sel_end = 0;
|
|
}
|
|
else
|
|
{
|
|
eptr->lo_form.ele_attrmask &= (~(LO_ELE_SELECTED));
|
|
eptr->lo_form.sel_start = -1;
|
|
eptr->lo_form.sel_end = -1;
|
|
}
|
|
|
|
lo_DisplayFormElement(context, (LO_FormElementStruct *)eptr);
|
|
}
|
|
break;
|
|
#endif
|
|
case LO_IMAGE:
|
|
#ifdef EDITOR
|
|
if ( EDT_IS_EDITOR(context) )
|
|
{
|
|
if (on != FALSE)
|
|
{
|
|
eptr->lo_image.ele_attrmask |= LO_ELE_SELECTED;
|
|
eptr->lo_image.sel_start = 0;
|
|
eptr->lo_image.sel_end = 0;
|
|
}
|
|
else
|
|
{
|
|
eptr->lo_image.ele_attrmask &= (~(LO_ELE_SELECTED));
|
|
eptr->lo_image.sel_start = -1;
|
|
eptr->lo_image.sel_end = -1;
|
|
}
|
|
|
|
{
|
|
LO_ImageStruct *lo_image = (LO_ImageStruct*)eptr;
|
|
XP_Rect rect;
|
|
|
|
rect.left = 0;
|
|
rect.top = 0;
|
|
rect.right = lo_image->width;
|
|
rect.bottom = lo_image->height;
|
|
|
|
CL_UpdateLayerRect(context->compositor, lo_image->layer,
|
|
&rect, (PRBool)FALSE);
|
|
}
|
|
}
|
|
#endif
|
|
break;
|
|
|
|
case LO_CELL:
|
|
{
|
|
LO_CellStruct *cell = &eptr->lo_cell;
|
|
int32 x_offset = 0;
|
|
int32 y_offset = 0;
|
|
CL_Layer *cell_layer = layer;
|
|
|
|
/* LO_CELLs can be either ordinary table cells or
|
|
containers for in-flow layers */
|
|
if (cell->cell_inflow_layer) {
|
|
cell_layer = cell->cell_inflow_layer;
|
|
lo_GetLayerXYShift(cell_layer, &x_offset, &y_offset);
|
|
}
|
|
|
|
if (!eptr->lo_cell.cell_list)
|
|
break;
|
|
|
|
highlight_state = lo_HighlightElementList(context,
|
|
base_x - x_offset,
|
|
base_y - y_offset,
|
|
eptr->lo_cell.cell_list,
|
|
eptr->lo_cell.cell_list, 0,
|
|
end, end_pos,
|
|
cell_layer, on);
|
|
if (highlight_state == HL_COMPLETED) {
|
|
return HL_COMPLETED;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case LO_SUBDOC:
|
|
case LO_TABLE:
|
|
case LO_BULLET:
|
|
default:
|
|
break;
|
|
}
|
|
|
|
last_id = eptr->lo_any.ele_id;
|
|
|
|
eptr = eptr->lo_any.next;
|
|
|
|
/*
|
|
* We don't want infinite loops.
|
|
* If the element ID hasn't progressed, something
|
|
* serious is wrong, and we should punt.
|
|
*/
|
|
if ((eptr != NULL)&&(eptr->lo_any.ele_id <= last_id))
|
|
{
|
|
#ifdef DEBUG
|
|
XP_TRACE(("Selection loop avoidance 2\n"));
|
|
#endif /* DEBUG */
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* We ran off the list without reaching the last element in the
|
|
selection range, so tell our caller that there's more work to do. */
|
|
if (!eptr)
|
|
return HL_STARTED;
|
|
|
|
/* We reached the last element in the selection range. We're done. */
|
|
return HL_COMPLETED;
|
|
}
|
|
|
|
|
|
void
|
|
lo_HighlightSelect(MWContext *context, lo_DocState *state,
|
|
LO_Element *start, int32 start_pos,
|
|
LO_Element *end, int32 end_pos,
|
|
Bool on)
|
|
{
|
|
LO_Element **line_array, *list;
|
|
LO_CellStruct *layer_cell;
|
|
|
|
if ((start == NULL) || (end == NULL))
|
|
return;
|
|
|
|
/*
|
|
* Do a synchronous composite to flush any other drawing
|
|
* request. Since we're going to replace the painter_func
|
|
* of state->selection_layer, we don't want to ignore other
|
|
* requests that might require this layer to draw. This call
|
|
* must happen before the text element is modified in any way.
|
|
*/
|
|
if (context->compositor)
|
|
CL_CompositeNow(context->compositor);
|
|
|
|
/* The stinkin' editor doesn't set the selection layer yet. */
|
|
if (!state->selection_layer)
|
|
state->selection_layer = CL_FindLayer(context->compositor, LO_BODY_LAYER_NAME);
|
|
|
|
layer_cell = lo_GetCellFromLayer(context, state->selection_layer);
|
|
if (layer_cell == NULL) {
|
|
XP_LOCK_BLOCK(line_array, LO_Element**, state->line_array );
|
|
if (line_array)
|
|
list = line_array[0];
|
|
else
|
|
list = NULL;
|
|
XP_UNLOCK_BLOCK( state->line_array );
|
|
} else
|
|
list = layer_cell->cell_list;
|
|
|
|
lo_HighlightElementList(context, state->base_x, state->base_y, list,
|
|
start, start_pos, end, end_pos,
|
|
state->selection_layer, on);
|
|
|
|
/* On the Mac, we don't run timeouts during a selection, so the
|
|
compositor won't run. Therefore we need to force any new
|
|
selection to paint now. */
|
|
if (context->compositor)
|
|
CL_CompositeNow(context->compositor);
|
|
}
|
|
|
|
void
|
|
LO_HighlightSelection(MWContext *context, Bool on)
|
|
{
|
|
int32 doc_id;
|
|
lo_TopState *top_state;
|
|
lo_DocState *state;
|
|
LO_Element *start, *end;
|
|
int32 start_pos, end_pos;
|
|
|
|
/*
|
|
* Get the unique document ID, and retreive this
|
|
* documents layout state.
|
|
*/
|
|
doc_id = XP_DOCID(context);
|
|
top_state = lo_FetchTopState(doc_id);
|
|
if ((top_state == NULL)||(top_state->doc_state == NULL))
|
|
{
|
|
return;
|
|
}
|
|
state = top_state->doc_state;
|
|
|
|
if ((state->selection_start == NULL)||(state->selection_end == NULL))
|
|
{
|
|
return;
|
|
}
|
|
|
|
start = state->selection_start;
|
|
start_pos = state->selection_start_pos;
|
|
end = state->selection_end;
|
|
end_pos = state->selection_end_pos;
|
|
|
|
lo_HighlightSelect(context, state, start, start_pos, end, end_pos,
|
|
on);
|
|
}
|
|
|
|
|
|
void
|
|
LO_ClearSelection(MWContext *context)
|
|
{
|
|
int32 doc_id;
|
|
lo_TopState *top_state;
|
|
lo_DocState *state;
|
|
|
|
/*
|
|
* Get the unique document ID, and retreive this
|
|
* documents layout state.
|
|
*/
|
|
doc_id = XP_DOCID(context);
|
|
top_state = lo_FetchTopState(doc_id);
|
|
if ((top_state == NULL)||(top_state->doc_state == NULL))
|
|
{
|
|
return;
|
|
}
|
|
state = top_state->doc_state;
|
|
|
|
LO_HighlightSelection(context, FALSE);
|
|
state->selection_start = NULL;
|
|
state->selection_start_pos = 0;
|
|
state->selection_end = NULL;
|
|
state->selection_end_pos = 0;
|
|
state->selection_layer = NULL;
|
|
}
|
|
|
|
|
|
PRIVATE
|
|
XP_Block
|
|
lo_SelectionToText(MWContext *context, lo_DocState *state,
|
|
LO_Element *start, int32 start_pos, LO_Element *end, int32 end_pos)
|
|
{
|
|
LO_Element *eptr;
|
|
LO_TextStruct tmp_text;
|
|
LO_TextInfo text_info;
|
|
LO_TextAttr tmp_attr;
|
|
int32 space_width;
|
|
int32 length;
|
|
Bool indent_counts;
|
|
PA_Block sbuff;
|
|
XP_Block buff;
|
|
char *str;
|
|
char *tptr;
|
|
/* int16 charset;*/
|
|
|
|
if ((start == NULL)||(end == NULL))
|
|
{
|
|
return(NULL);
|
|
}
|
|
|
|
/*
|
|
* All this work is to get the width of a " " in the default
|
|
* fixed font.
|
|
*/
|
|
memset (&tmp_text, 0, sizeof (tmp_text));
|
|
sbuff = PA_ALLOC(1);
|
|
if (sbuff == NULL)
|
|
{
|
|
return(NULL);
|
|
}
|
|
PA_LOCK(str, char *, sbuff);
|
|
str[0] = ' ';
|
|
PA_UNLOCK(sbuff);
|
|
tmp_text.text = sbuff;
|
|
tmp_text.text_len = 1;
|
|
/*
|
|
* Fill in default font information.
|
|
*/
|
|
lo_SetDefaultFontAttr(state, &tmp_attr, context);
|
|
tmp_attr.fontmask |= LO_FONT_FIXED;
|
|
tmp_text.text_attr = lo_FetchTextAttr(state, &tmp_attr);
|
|
FE_GetTextInfo(context, &tmp_text, &text_info);
|
|
PA_FREE(sbuff);
|
|
space_width = text_info.max_width;
|
|
if (space_width <= 0)
|
|
{
|
|
space_width = 2;
|
|
}
|
|
|
|
indent_counts = FALSE;
|
|
length = 0;
|
|
eptr = start;
|
|
while ((eptr != NULL)&&(eptr->lo_any.ele_id <= end->lo_any.ele_id))
|
|
{
|
|
int32 last_id;
|
|
|
|
switch (eptr->type)
|
|
{
|
|
case LO_TEXT:
|
|
if (eptr->lo_text.text != NULL)
|
|
{
|
|
if (indent_counts != FALSE)
|
|
{
|
|
int32 width;
|
|
|
|
width = eptr->lo_text.x +
|
|
eptr->lo_text.x_offset -
|
|
state->win_left;
|
|
width = (width + space_width - 1) / space_width;
|
|
if (width < 0)
|
|
{
|
|
width = 0;
|
|
}
|
|
length += width;
|
|
indent_counts = FALSE;
|
|
}
|
|
|
|
if ((eptr == start)||(eptr == end))
|
|
{
|
|
int32 p1, p2;
|
|
|
|
if (eptr == start)
|
|
{
|
|
p1 = start_pos;
|
|
}
|
|
else
|
|
{
|
|
p1 = 0;
|
|
}
|
|
|
|
if (eptr == end)
|
|
{
|
|
char *string;
|
|
|
|
PA_LOCK(string, char *, eptr->lo_text.text);
|
|
/* charset = eptr->lo_text.text_attr->charset;
|
|
* p2 = end_pos + INTL_IsLeadByte(charset, string[end_pos]);
|
|
*
|
|
* p2 already point to the end of the selection
|
|
* calling INTL_IsLeadByte(charset, string[end_pos]) is wrong here.
|
|
*/
|
|
p2 = end_pos;
|
|
PA_UNLOCK(eptr->lo_text.text);
|
|
if (p2 > lo_GetLastCharEndPosition(eptr))
|
|
{
|
|
p2 = lo_GetLastCharEndPosition(eptr);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
p2 = lo_GetLastCharEndPosition(eptr);
|
|
}
|
|
if (p2 < p1)
|
|
{
|
|
p2 = p1;
|
|
}
|
|
|
|
length += (p2 - p1 + 1);
|
|
}
|
|
else
|
|
{
|
|
length += eptr->lo_text.text_len;
|
|
}
|
|
}
|
|
break;
|
|
case LO_LINEFEED:
|
|
length += LINEBREAK_LEN;
|
|
indent_counts = TRUE;
|
|
break;
|
|
case LO_HRULE:
|
|
case LO_FORM_ELE:
|
|
case LO_BULLET:
|
|
case LO_IMAGE:
|
|
case LO_SUBDOC:
|
|
case LO_TABLE:
|
|
default:
|
|
break;
|
|
}
|
|
|
|
last_id = eptr->lo_any.ele_id;
|
|
|
|
/*
|
|
* Jump cell boundries if there is one between start
|
|
* and end.
|
|
*/
|
|
if ((eptr->lo_any.next == NULL)&&(eptr != end))
|
|
{
|
|
eptr = lo_JumpCellWall(context, state, eptr);
|
|
}
|
|
|
|
eptr = eptr->lo_any.next;
|
|
|
|
/*
|
|
* When we walk onto a cell, we need to walk into
|
|
* it if it isn't empty.
|
|
*/
|
|
if ((eptr != NULL)&&(eptr->type == LO_CELL)&&
|
|
(eptr->lo_cell.cell_list != NULL))
|
|
{
|
|
eptr = eptr->lo_cell.cell_list;
|
|
}
|
|
|
|
/*
|
|
* We don't want infinite loops.
|
|
* If the element ID hasn't progressed, something
|
|
* serious is wrong, and we should punt.
|
|
*/
|
|
if ((eptr != NULL)&&(eptr->lo_any.ele_id <= last_id))
|
|
{
|
|
#ifdef DEBUG
|
|
XP_TRACE(("Selection loop avoidance 3\n"));
|
|
#endif /* DEBUG */
|
|
break;
|
|
}
|
|
}
|
|
length++;
|
|
|
|
#ifdef XP_WIN16
|
|
if (length > SIZE_LIMIT)
|
|
{
|
|
length = SIZE_LIMIT;
|
|
}
|
|
#endif /* XP_WIN16 */
|
|
|
|
buff = XP_ALLOC_BLOCK(length * sizeof(char));
|
|
if (buff == NULL)
|
|
{
|
|
return(NULL);
|
|
}
|
|
XP_LOCK_BLOCK(str, char *, buff);
|
|
|
|
tptr = str;
|
|
indent_counts = FALSE;
|
|
length = 0;
|
|
eptr = start;
|
|
while ((eptr != NULL)&&(eptr->lo_any.ele_id <= end->lo_any.ele_id))
|
|
{
|
|
int32 last_id;
|
|
|
|
switch (eptr->type)
|
|
{
|
|
case LO_TEXT:
|
|
if (eptr->lo_text.text != NULL)
|
|
{
|
|
char *text;
|
|
|
|
if (indent_counts != FALSE)
|
|
{
|
|
int32 width;
|
|
int32 i;
|
|
|
|
width = eptr->lo_text.x +
|
|
eptr->lo_text.x_offset -
|
|
state->win_left;
|
|
width = (width + space_width - 1) / space_width;
|
|
if (width < 0)
|
|
{
|
|
width = 0;
|
|
}
|
|
length += width;
|
|
#ifdef XP_WIN16
|
|
if (length > SIZE_LIMIT)
|
|
{
|
|
length -= width;
|
|
break;
|
|
}
|
|
#endif /* XP_WIN16 */
|
|
for (i=0; i<width; i++)
|
|
{
|
|
XP_BCOPY(" ", tptr, 1);
|
|
tptr++;
|
|
}
|
|
indent_counts = FALSE;
|
|
}
|
|
|
|
if ((eptr == start)||(eptr == end))
|
|
{
|
|
int32 p1, p2, len;
|
|
int32 maxPos = lo_GetLastCharEndPosition(eptr);
|
|
|
|
if (eptr == start)
|
|
{
|
|
p1 = start_pos;
|
|
}
|
|
else
|
|
{
|
|
p1 = 0;
|
|
}
|
|
|
|
if (eptr == end)
|
|
{
|
|
char *string;
|
|
|
|
PA_LOCK(string, char *, eptr->lo_text.text);
|
|
/* charset = eptr->lo_text.text_attr->charset;
|
|
* p2 = end_pos + INTL_IsLeadByte(charset, string[end_pos]);
|
|
*
|
|
* p2 already point to the end of the selection
|
|
* calling INTL_IsLeadByte(charset, string[end_pos]) is wrong here.
|
|
*/
|
|
p2 = end_pos;
|
|
PA_UNLOCK(eptr->lo_text.text);
|
|
if (p2 > maxPos)
|
|
{
|
|
p2 = maxPos;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
p2 = maxPos;
|
|
}
|
|
if (p2 < p1)
|
|
{
|
|
p2 = p1;
|
|
}
|
|
|
|
len = p2 - p1 + 1;
|
|
length += len;
|
|
#ifdef XP_WIN16
|
|
if (length > SIZE_LIMIT)
|
|
{
|
|
length -= len;
|
|
break;
|
|
}
|
|
#endif /* XP_WIN16 */
|
|
PA_LOCK(text, char *, eptr->lo_text.text);
|
|
XP_BCOPY((char *)(text + p1), tptr, len);
|
|
tptr = (char *)(tptr + len);
|
|
PA_UNLOCK(eptr->lo_text.text);
|
|
}
|
|
else
|
|
{
|
|
length += eptr->lo_text.text_len;
|
|
#ifdef XP_WIN16
|
|
if (length > SIZE_LIMIT)
|
|
{
|
|
length -= eptr->lo_text.text_len;
|
|
break;
|
|
}
|
|
#endif /* XP_WIN16 */
|
|
PA_LOCK(text, char *, eptr->lo_text.text);
|
|
XP_BCOPY(text, tptr, eptr->lo_text.text_len);
|
|
tptr = (char *)(tptr + eptr->lo_text.text_len);
|
|
PA_UNLOCK(eptr->lo_text.text);
|
|
}
|
|
}
|
|
break;
|
|
case LO_LINEFEED:
|
|
length += LINEBREAK_LEN;
|
|
#ifdef XP_WIN16
|
|
if (length > SIZE_LIMIT)
|
|
{
|
|
length -= LINEBREAK_LEN;
|
|
break;
|
|
}
|
|
#endif /* XP_WIN16 */
|
|
XP_BCOPY(LINEBREAK, tptr, LINEBREAK_LEN);
|
|
tptr = (char *)(tptr + LINEBREAK_LEN);
|
|
indent_counts = TRUE;
|
|
break;
|
|
case LO_HRULE:
|
|
case LO_FORM_ELE:
|
|
case LO_BULLET:
|
|
case LO_IMAGE:
|
|
case LO_SUBDOC:
|
|
case LO_TABLE:
|
|
default:
|
|
break;
|
|
}
|
|
|
|
last_id = eptr->lo_any.ele_id;
|
|
|
|
/*
|
|
* Jump cell boundries if there is one between start
|
|
* and end.
|
|
*/
|
|
if ((eptr->lo_any.next == NULL)&&(eptr != end))
|
|
{
|
|
eptr = lo_JumpCellWall(context, state, eptr);
|
|
}
|
|
|
|
eptr = eptr->lo_any.next;
|
|
|
|
/*
|
|
* When we walk onto a cell, we need to walk into
|
|
* it if it isn't empty.
|
|
*/
|
|
if ((eptr != NULL)&&(eptr->type == LO_CELL)&&
|
|
(eptr->lo_cell.cell_list != NULL))
|
|
{
|
|
eptr = eptr->lo_cell.cell_list;
|
|
}
|
|
|
|
/*
|
|
* We don't want infinite loops.
|
|
* If the element ID hasn't progressed, something
|
|
* serious is wrong, and we should punt.
|
|
*/
|
|
if ((eptr != NULL)&&(eptr->lo_any.ele_id <= last_id))
|
|
{
|
|
#ifdef DEBUG
|
|
XP_TRACE(("Selection loop avoidance 4\n"));
|
|
#endif /* DEBUG */
|
|
break;
|
|
}
|
|
}
|
|
str[length] = '\0';
|
|
length++;
|
|
XP_UNLOCK_BLOCK(buff);
|
|
|
|
return(buff);
|
|
}
|
|
|
|
|
|
XP_Block
|
|
LO_GetSelectionText(MWContext *context)
|
|
{
|
|
int32 doc_id;
|
|
lo_TopState *top_state;
|
|
lo_DocState *state;
|
|
LO_Element *start, *end;
|
|
int32 start_pos, end_pos;
|
|
XP_Block buff;
|
|
|
|
/*
|
|
* Get the unique document ID, and retreive this
|
|
* documents layout state.
|
|
*/
|
|
doc_id = XP_DOCID(context);
|
|
top_state = lo_FetchTopState(doc_id);
|
|
if ((top_state == NULL)||(top_state->doc_state == NULL))
|
|
{
|
|
return(NULL);
|
|
}
|
|
state = top_state->doc_state;
|
|
|
|
if ((state->selection_start == NULL)||(state->selection_end == NULL))
|
|
{
|
|
return(NULL);
|
|
}
|
|
|
|
start = state->selection_start;
|
|
start_pos = state->selection_start_pos;
|
|
end = state->selection_end;
|
|
end_pos = state->selection_end_pos;
|
|
|
|
/* Make sure the selection is normalized */
|
|
lo_NormalizeSelectionPoint(context, state, &start, &start_pos);
|
|
lo_NormalizeSelectionEnd(context, state, &end, &end_pos);
|
|
|
|
buff = lo_SelectionToText(context, state, start, start_pos,
|
|
end, end_pos);
|
|
|
|
return(buff);
|
|
}
|
|
|
|
|
|
Bool
|
|
LO_HaveSelection(MWContext *context)
|
|
{
|
|
int32 doc_id;
|
|
lo_TopState *top_state;
|
|
lo_DocState *state;
|
|
|
|
/*
|
|
* Get the unique document ID, and retreive this
|
|
* documents layout state.
|
|
*/
|
|
doc_id = XP_DOCID(context);
|
|
top_state = lo_FetchTopState(doc_id);
|
|
if ((top_state == NULL)||(top_state->doc_state == NULL))
|
|
{
|
|
return(FALSE);
|
|
}
|
|
state = top_state->doc_state;
|
|
|
|
if ((state->selection_start == NULL)||(state->selection_end == NULL))
|
|
{
|
|
return(FALSE);
|
|
}
|
|
else
|
|
{
|
|
return(TRUE);
|
|
}
|
|
}
|
|
|
|
/* return true if there is a current selection */
|
|
|
|
PRIVATE
|
|
Bool
|
|
lo_GetSelection(MWContext *context, LO_Selection* selection)
|
|
{
|
|
int32 beginPosition;
|
|
int32 endPosition;
|
|
CL_Layer *sel_layer;
|
|
/* LO_GetSelectionEndPoints uses int32. LO_Selection uses intn. So we need to
|
|
* do the conversion here.
|
|
*/
|
|
|
|
LO_GetSelectionEndpoints(context, &selection->begin.element, &selection->end.element,
|
|
&beginPosition, &endPosition, &sel_layer);
|
|
selection->begin.position = (intn) beginPosition;
|
|
selection->end.position = (intn) endPosition;
|
|
return selection->begin.element != NULL && selection->end.element != NULL;
|
|
}
|
|
|
|
void
|
|
LO_GetSelectionEndpoints(MWContext *context, LO_Element **start, LO_Element **end,
|
|
int32 *start_pos, int32 *end_pos, CL_Layer **sel_layer)
|
|
{
|
|
int32 doc_id;
|
|
lo_TopState *top_state;
|
|
lo_DocState *state;
|
|
|
|
*start = NULL;
|
|
*end = NULL;
|
|
*start_pos = 0;
|
|
*end_pos = 0;
|
|
*sel_layer = NULL;
|
|
|
|
/*
|
|
* Get the unique document ID, and retreive this
|
|
* documents layout state.
|
|
*/
|
|
doc_id = XP_DOCID(context);
|
|
top_state = lo_FetchTopState(doc_id);
|
|
if ((top_state == NULL)||(top_state->doc_state == NULL))
|
|
{
|
|
return;
|
|
}
|
|
state = top_state->doc_state;
|
|
|
|
if ((state->selection_start == NULL)||(state->selection_end == NULL))
|
|
{
|
|
return;
|
|
}
|
|
|
|
*start = state->selection_start;
|
|
*start_pos = state->selection_start_pos;
|
|
*end = state->selection_end;
|
|
*end_pos = state->selection_end_pos;
|
|
*sel_layer = state->selection_layer;
|
|
}
|
|
|
|
/* Returns TRUE if there was anything to select. If there is no data, or if
|
|
* we're in editor mode and there is no editable data, then FALSE is returned.
|
|
*/
|
|
|
|
Bool
|
|
LO_SelectAll(MWContext *context)
|
|
{
|
|
int32 doc_id;
|
|
lo_TopState *top_state;
|
|
lo_DocState *state;
|
|
LO_Selection selection;
|
|
|
|
/*
|
|
* Get the unique document ID, and retreive this
|
|
* documents layout state.
|
|
*/
|
|
doc_id = XP_DOCID(context);
|
|
top_state = lo_FetchTopState(doc_id);
|
|
if ((top_state == NULL)||(top_state->doc_state == NULL))
|
|
{
|
|
return FALSE;
|
|
}
|
|
state = top_state->doc_state;
|
|
|
|
LO_HighlightSelection(context, FALSE);
|
|
selection.begin.element = NULL;
|
|
selection.begin.position = 0;
|
|
selection.end.element = NULL;
|
|
selection.end.position = 0;
|
|
|
|
if ( ! ( lo_FindDocumentEdge(context, state, &selection.begin, TRUE, FALSE)
|
|
&& lo_FindDocumentEdge(context, state, &selection.end, TRUE, TRUE) ) )
|
|
{
|
|
return FALSE;
|
|
}
|
|
#if 0
|
|
lo_ConvertInsertPointToSelectionEnd(context, state, &selection.end.element, &selection.end.position);
|
|
#endif
|
|
LO_ASSERT_SELECTION(context, &selection);
|
|
/*
|
|
* Nothing to select.
|
|
*/
|
|
if (selection.begin.element == NULL)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
state->selection_start = selection.begin.element;
|
|
state->selection_start_pos = selection.begin.position;
|
|
state->selection_end = selection.end.element;
|
|
state->selection_end_pos = selection.end.position;
|
|
|
|
#if 0
|
|
if ( ! lo_NormalizeSelection(context) ){
|
|
return FALSE;
|
|
}
|
|
#endif
|
|
LO_HighlightSelection(context, TRUE);
|
|
return TRUE;
|
|
}
|
|
|
|
#ifdef EDITOR
|
|
|
|
/* This routine normalizes a selection so that it only selects editable elements.
|
|
* It returns TRUE if the resulting selection is not empty.
|
|
*/
|
|
Bool
|
|
lo_NormalizeSelection(MWContext *context)
|
|
{
|
|
Bool result;
|
|
int32 doc_id;
|
|
lo_TopState *top_state;
|
|
lo_DocState *state;
|
|
intn comparisonResult;
|
|
|
|
#ifdef DEBUG
|
|
lo_VerifyLayout(context);
|
|
#endif /* DEBUG */
|
|
result = FALSE;
|
|
/*
|
|
* Get the unique document ID, and retreive this
|
|
* document's layout state.
|
|
*/
|
|
doc_id = XP_DOCID(context);
|
|
top_state = lo_FetchTopState(doc_id);
|
|
if ((top_state == NULL)||(top_state->doc_state == NULL))
|
|
{
|
|
return FALSE;
|
|
}
|
|
state = top_state->doc_state;
|
|
if ( ! ( lo_EnsureEditableSearchNext2(context, state, &state->selection_start, & state->selection_start_pos)
|
|
&& lo_EnsureEditableSearchPrev2(context, state, &state->selection_end, & state->selection_end_pos) ) )
|
|
{
|
|
XP_ASSERT(FALSE);
|
|
return FALSE;
|
|
}
|
|
/*
|
|
* Ensure that the start position is before the end position
|
|
*/
|
|
comparisonResult = lo_compare_selections(
|
|
state->selection_start, state->selection_start_pos,
|
|
state->selection_end, state->selection_end_pos );
|
|
result = comparisonResult <= 0;
|
|
if ( comparisonResult > 0 )
|
|
{
|
|
/*
|
|
* It's not a legal selection. This can happen if we're editing and
|
|
* the selected element is not editable. In this case we null out the selection
|
|
*/
|
|
state->selection_start = NULL;
|
|
state->selection_end = NULL;
|
|
state->selection_start_pos = 0;
|
|
state->selection_end_pos = 0;
|
|
}
|
|
return result;
|
|
}
|
|
#endif
|
|
|
|
Bool
|
|
lo_EnsureEditableSearchNext(MWContext *context, lo_DocState* state, LO_Element** eptr)
|
|
{
|
|
int32 position = 0;
|
|
return lo_EnsureEditableSearch2(context, state, eptr, &position, TRUE);
|
|
}
|
|
|
|
Bool
|
|
lo_EnsureEditableSearchNext2(MWContext *context, lo_DocState* state, LO_Element** eptr,
|
|
int32* ePositionPtr)
|
|
{
|
|
return lo_EnsureEditableSearch2(context, state, eptr, ePositionPtr, TRUE);
|
|
}
|
|
|
|
Bool
|
|
lo_EnsureEditableSearchPrev(MWContext *context, lo_DocState* state, LO_Element** eptr)
|
|
{
|
|
int32 position = lo_GetMaximumInsertPointPosition(*eptr);
|
|
return lo_EnsureEditableSearch2(context, state, eptr, &position, FALSE);
|
|
}
|
|
|
|
Bool
|
|
lo_EnsureEditableSearchPrev2(MWContext *context, lo_DocState* state, LO_Element** eptr, int32* ePositionPtr)
|
|
{
|
|
return lo_EnsureEditableSearch2(context, state, eptr, ePositionPtr, FALSE);
|
|
}
|
|
|
|
/* Returns TRUE if the result is editable. Doesn't change position if can't find editable. */
|
|
|
|
Bool
|
|
lo_EnsureEditableSearch(MWContext *context, lo_DocState* state, LO_Position* p, Bool forward)
|
|
{
|
|
return lo_EnsureEditableSearch2(context, state, &p->element, &p->position, forward);
|
|
}
|
|
|
|
PRIVATE
|
|
Bool
|
|
lo_BumpToNextElement(MWContext *context, lo_DocState* state, LO_Element** eptr, int32* ePositionPtr, Bool forward)
|
|
{
|
|
LO_Element* element = *eptr;
|
|
int32 position = *ePositionPtr;
|
|
if ( forward )
|
|
{
|
|
element = lo_BoundaryJumpingNext(context, state, element);
|
|
}
|
|
else
|
|
{
|
|
element = lo_BoundaryJumpingPrev(context, state, element);
|
|
}
|
|
|
|
if ( ! element ) return FALSE;
|
|
|
|
if ( forward )
|
|
{
|
|
position = 0;
|
|
}
|
|
else
|
|
{
|
|
position = lo_GetMaximumInsertPointPosition(element);
|
|
}
|
|
|
|
*eptr = element;
|
|
*ePositionPtr = position;
|
|
return TRUE;
|
|
}
|
|
|
|
Bool
|
|
lo_EnsureEditableSearch2(MWContext *context, lo_DocState* state, LO_Element** eptr, int32* ePositionPtr, Bool forward)
|
|
{
|
|
Bool moved = FALSE;
|
|
LO_Element* element = *eptr;
|
|
int32 position = *ePositionPtr;
|
|
|
|
if ( ! element ) return FALSE;
|
|
|
|
while ( ! lo_IsValidEditableInsertPoint2(context, state, element, position ) )
|
|
{
|
|
if ( ! lo_BumpToNextElement(context, state, &element, &position, forward ) )
|
|
{
|
|
return FALSE;
|
|
}
|
|
moved = TRUE;
|
|
}
|
|
|
|
if ( moved )
|
|
{
|
|
*eptr = element;
|
|
*ePositionPtr = position;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
Bool
|
|
lo_IsValidEditableInsertPoint2(MWContext *context, lo_DocState* state, LO_Element* eptr, int32 position)
|
|
{
|
|
return lo_ValidEditableElement(context, eptr)
|
|
&& position >= 0 && position <= lo_GetMaximumInsertPointPosition(eptr);
|
|
}
|
|
|
|
#ifdef EDITOR
|
|
/*
|
|
* This routine has too much knowledge of the layout engine. Need to revisit
|
|
*/
|
|
void LO_PositionCaret(MWContext *context, int32 x, int32 y, CL_Layer *layer)
|
|
{
|
|
LO_Click(context, x, y, TRUE, layer);
|
|
}
|
|
|
|
void LO_DoubleClick(MWContext *context, int32 x, int32 y, CL_Layer *layer)
|
|
{
|
|
int32 doc_id;
|
|
lo_TopState *top_state;
|
|
lo_DocState *state;
|
|
LO_HitResult result;
|
|
|
|
/*
|
|
* Get the unique document ID, and retreive this
|
|
* documents layout state.
|
|
*/
|
|
doc_id = XP_DOCID(context);
|
|
top_state = lo_FetchTopState(doc_id);
|
|
if ((top_state == NULL)||(top_state->doc_state == NULL))
|
|
{
|
|
return;
|
|
}
|
|
state = top_state->doc_state;
|
|
|
|
LO_Hit(context, x, y, FALSE, &result, layer);
|
|
|
|
lo_ProcessDoubleClick(context, top_state, state, &result, layer);
|
|
}
|
|
|
|
|
|
#endif
|
|
|
|
PRIVATE
|
|
void lo_GetHorizontalBounds(LO_Element* eptr, int32* returnBegin, int32* returnEnd)
|
|
{
|
|
int32 width = eptr->lo_any.width;
|
|
/*
|
|
* Images need to account for border width
|
|
*/
|
|
if (eptr->type == LO_IMAGE)
|
|
{
|
|
width = width + (2 * eptr->lo_image.border_width);
|
|
}
|
|
if (width <= 0)
|
|
{
|
|
width = 1;
|
|
}
|
|
*returnBegin = eptr->lo_any.x + eptr->lo_any.x_offset;
|
|
*returnEnd = *returnBegin + width;
|
|
}
|
|
|
|
int32
|
|
lo_GetTextAttrMask(LO_Element* eptr)
|
|
{
|
|
int32 mask = 0;
|
|
LO_TextAttr* textAttr = 0;
|
|
if ( ! eptr )
|
|
{
|
|
XP_ASSERT(FALSE);
|
|
return mask;
|
|
}
|
|
switch ( eptr->type ) {
|
|
case LO_TEXT:
|
|
textAttr = eptr->lo_text.text_attr;
|
|
break;
|
|
case LO_IMAGE:
|
|
textAttr = eptr->lo_image.text_attr;
|
|
break;
|
|
case LO_LINEFEED:
|
|
textAttr = eptr->lo_linefeed.text_attr;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
if ( textAttr ) {
|
|
mask = textAttr->attrmask;
|
|
}
|
|
return mask;
|
|
}
|
|
|
|
int32
|
|
lo_GetElementLength(LO_Element* eptr)
|
|
{
|
|
int32 length;
|
|
if ( ! eptr )
|
|
{
|
|
XP_ASSERT(FALSE);
|
|
return 1;
|
|
}
|
|
switch ( eptr->type ) {
|
|
case LO_TEXT:
|
|
length = eptr->lo_text.text_len;
|
|
break;
|
|
case LO_TEXTBLOCK:
|
|
case LO_DESCTITLE:
|
|
length = 0; /*MJUDGE 2-5-98*/
|
|
break;
|
|
case LO_HRULE:
|
|
case LO_IMAGE:
|
|
case LO_LINEFEED:
|
|
default:
|
|
length = 1;
|
|
break;
|
|
}
|
|
return length;
|
|
}
|
|
|
|
LO_AnchorData*
|
|
lo_GetAnchorData(LO_Element* eptr)
|
|
{
|
|
LO_AnchorData* result = 0;
|
|
if ( ! eptr )
|
|
{
|
|
XP_ASSERT(FALSE);
|
|
return result;
|
|
}
|
|
switch ( eptr->type ) {
|
|
case LO_TEXT:
|
|
result = eptr->lo_text.anchor_href;
|
|
break;
|
|
case LO_IMAGE:
|
|
result = eptr->lo_image.anchor_href;
|
|
break;
|
|
case LO_LINEFEED:
|
|
result = eptr->lo_linefeed.anchor_href;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
LO_Element*
|
|
lo_GetNeighbor(LO_Element* element, Bool forward)
|
|
{
|
|
LO_Element* result = NULL;
|
|
if ( element ) {
|
|
if ( forward )
|
|
result = element->lo_any.next;
|
|
else
|
|
result = element->lo_any.prev;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
int32
|
|
lo_GetElementEdge(LO_Element* element, Bool forward)
|
|
{
|
|
int32 result = 0;
|
|
if ( element ) {
|
|
if ( forward )
|
|
result = lo_GetElementLength(element);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
int32
|
|
lo_GetMaximumInsertPointPosition(LO_Element* eptr)
|
|
{
|
|
if ( ! eptr )
|
|
{
|
|
XP_ASSERT(FALSE);
|
|
return 0;
|
|
}
|
|
#if 0
|
|
if ( eptr->type == LO_LINEFEED )
|
|
{
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
return lo_GetElementLength(eptr);
|
|
}
|
|
#endif
|
|
return lo_GetElementLength(eptr);
|
|
}
|
|
|
|
int32
|
|
lo_IncrementPosition(LO_Element* eptr, int32 position)
|
|
{
|
|
int32 length = lo_GetElementLength(eptr);
|
|
if ( position < length )
|
|
{
|
|
if (eptr->type == LO_TEXT)
|
|
{
|
|
unsigned char *string;
|
|
|
|
PA_LOCK(string, unsigned char *, eptr->lo_text.text);
|
|
position = INTL_NextCharIdx(eptr->lo_text.text_attr->charset,
|
|
string, position);
|
|
PA_UNLOCK(eptr->lo_text.text);
|
|
}
|
|
else
|
|
position++;
|
|
}
|
|
if ( position > length ) {
|
|
position = length;
|
|
}
|
|
return position;
|
|
}
|
|
|
|
int32
|
|
lo_DecrementPosition(LO_Element* eptr, int32 position)
|
|
{
|
|
if ( position > 0 )
|
|
{
|
|
if (eptr->type == LO_TEXT)
|
|
{
|
|
unsigned char *string;
|
|
|
|
PA_LOCK(string, unsigned char *, eptr->lo_text.text);
|
|
position = INTL_PrevCharIdx(eptr->lo_text.text_attr->charset,
|
|
string, position);
|
|
PA_UNLOCK(eptr->lo_text.text);
|
|
}
|
|
else
|
|
position--;
|
|
}
|
|
if ( position < 0 ) {
|
|
position = 0;
|
|
}
|
|
return position;
|
|
}
|
|
|
|
int32
|
|
lo_GetLastCharBeginPosition(LO_Element* eptr)
|
|
{
|
|
return lo_DecrementPosition(eptr,
|
|
lo_GetElementLength(eptr));
|
|
}
|
|
|
|
int32
|
|
lo_GetLastCharEndPosition(LO_Element* eptr)
|
|
{
|
|
return lo_GetElementLength(eptr) - 1;
|
|
}
|
|
|
|
void lo_HitLine(MWContext *context, lo_DocState *state, int32 x, int32 y, Bool requireCaret,
|
|
LO_HitResult* result);
|
|
/* cmanske: added y param for snapping to closest cell in the editor */
|
|
void lo_HitLine2(MWContext *context, lo_DocState *state, LO_Element* element, int32 position,
|
|
int32 x, int32 y, 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 && eptr->type != LO_TABLE ) {
|
|
/* 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, y, 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;
|
|
}
|
|
/* How can this be? */
|
|
if( begin == 0 )
|
|
continue;
|
|
|
|
/* 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, int32 y, LO_HitResult* result)
|
|
{
|
|
LO_Element* begin;
|
|
LO_Element* end;
|
|
#ifdef EDITOR
|
|
LO_Element* tptr;
|
|
#endif
|
|
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;
|
|
#ifdef EDITOR
|
|
if( EDT_IS_EDITOR(context) && end->type == LO_TABLE )
|
|
{
|
|
tptr = end;
|
|
if( x > end->lo_table.x )
|
|
{
|
|
/* We are after the table
|
|
* Find the last cell in a row that spans the y value
|
|
* Begin at the end of the table
|
|
*/
|
|
|
|
while( tptr->lo_any.next && tptr->lo_any.next->type != LO_LINEFEED )
|
|
tptr = tptr->lo_any.next;
|
|
|
|
do {
|
|
if( tptr->type == LO_CELL )
|
|
{
|
|
/* Find a cell that is in same (last) column
|
|
* and the y location is within the row of that cell.
|
|
*/
|
|
if( y >= tptr->lo_cell.y )
|
|
{
|
|
end = tptr;
|
|
break;
|
|
}
|
|
}
|
|
tptr = tptr->lo_any.prev;
|
|
}
|
|
while( tptr && tptr->type != LO_TABLE ); /* Stop when we hit beginning of the table */
|
|
|
|
/* Find the last element in the cell */
|
|
begin = end->lo_cell.cell_list;
|
|
if( begin == 0 )
|
|
return;
|
|
|
|
while( begin->lo_any.next )
|
|
begin = begin->lo_any.next;
|
|
|
|
/* Back up to find an editable element */
|
|
if ( ! lo_EnsureEditableSearchPrev(context, state, &begin) )
|
|
return;
|
|
|
|
result->lo_hitLine.region = LO_HIT_LINE_REGION_AFTER;
|
|
result->lo_hitLine.selection.begin.position = lo_GetMaximumInsertPointPosition(begin);
|
|
}
|
|
else
|
|
{
|
|
/* We are before the table
|
|
* Find the cell that spans the y value
|
|
* Start at beginning cell of table
|
|
*/
|
|
while( tptr->type != LO_CELL )
|
|
{
|
|
tptr = tptr->lo_any.next;
|
|
if( tptr == 0 || tptr->type == LO_LINEFEED )
|
|
/* No cells in table? */
|
|
goto LO_HIT_CONTINUE;
|
|
|
|
if( tptr->type == LO_CELL )
|
|
break;
|
|
}
|
|
|
|
do {
|
|
if( tptr->type == LO_CELL )
|
|
{
|
|
/* Find a cell that is in the first column
|
|
* and the y location is within the row of that cell.
|
|
*/
|
|
if( y < (tptr->lo_cell.y + tptr->lo_cell.height + tptr->lo_cell.inter_cell_space) )
|
|
{
|
|
end = tptr;
|
|
break;
|
|
}
|
|
}
|
|
tptr = tptr->lo_any.next;
|
|
}
|
|
while( tptr && tptr->type != LO_LINEFEED ); /* Stop when we hit end of the table */
|
|
|
|
/* Find the first element in the cell */
|
|
begin = end->lo_cell.cell_list;
|
|
if( begin == 0 )
|
|
return;
|
|
|
|
/* Find an editable element */
|
|
if ( ! lo_EnsureEditableSearchNext(context, state, &begin) )
|
|
return;
|
|
|
|
result->lo_hitLine.region = LO_HIT_LINE_REGION_BEFORE;
|
|
result->lo_hitLine.selection.begin.position = 0;
|
|
}
|
|
if( end->type == LO_CELL )
|
|
{
|
|
/* New hit type: For single click after the table,
|
|
* this is the same as LO_HIT_LINE,
|
|
* and it will position caret at end of the line
|
|
* For double-click, it signals processing same as single click
|
|
* For single or double click before the table, it will select the table
|
|
*/
|
|
result->lo_hitLine.type = LO_HIT_TABLE_LINE;
|
|
|
|
/* Set the result data - make begin and end the same */
|
|
result->lo_hitLine.selection.begin.element = begin;
|
|
result->lo_hitLine.selection.end.element = begin;
|
|
result->lo_hitLine.selection.end.position = result->lo_hitLine.selection.begin.position;
|
|
return;
|
|
}
|
|
}
|
|
#endif
|
|
LO_HIT_CONTINUE:
|
|
/* Search forward to find the end of line */
|
|
for ( ;
|
|
end;
|
|
end = end->lo_any.next)
|
|
{
|
|
if ( end->type == LO_LINEFEED ) break;
|
|
}
|
|
|
|
if ( ! end )
|
|
{
|
|
return;
|
|
}
|
|
/* Search backwards to find the beginning of the line. */
|
|
for ( begin = end->lo_any.prev;
|
|
begin;
|
|
begin = begin->lo_any.prev)
|
|
{
|
|
if ( begin->type == LO_LINEFEED )
|
|
{
|
|
#ifdef MQUOTE
|
|
/* We have the case of an editable linefeed on a line by itself when the only
|
|
thing on the line are bullets. I.e. from a <mquote> tag. */
|
|
if ( lo_OnlyBulletsBetween(begin,end) )
|
|
#else
|
|
if ( begin->lo_any.next == end )
|
|
#endif
|
|
{
|
|
/* editable linefeed on a line by itself. */
|
|
result->type = LO_HIT_LINE;
|
|
if ( x < end->lo_any.x ) {
|
|
result->lo_hitLine.region = LO_HIT_LINE_REGION_BEFORE;
|
|
} else {
|
|
result->lo_hitLine.region = LO_HIT_LINE_REGION_AFTER;
|
|
}
|
|
result->lo_hitLine.selection.begin.element = end;
|
|
result->lo_hitLine.selection.begin.position = 0;
|
|
result->lo_hitLine.selection.end = result->lo_hitLine.selection.begin;
|
|
return;
|
|
}
|
|
begin = begin->lo_any.next;
|
|
break;
|
|
}
|
|
if ( ! begin->lo_any.prev )
|
|
break; /* Start of document */
|
|
}
|
|
|
|
if ( ! begin )
|
|
{
|
|
/* Must be a line-feed at the beginning of the document */
|
|
begin = end;
|
|
}
|
|
|
|
|
|
/* Except for cases where the entire line is a line feed, don't select the end line-feed. */
|
|
if ( begin->type != LO_LINEFEED && end->type == LO_LINEFEED ) {
|
|
end = end->lo_any.prev;
|
|
}
|
|
|
|
if ( ( begin->type == LO_TABLE ) ||
|
|
(begin->type == LO_CELL && begin->lo_cell.cell_inflow_layer) )
|
|
{
|
|
/* If this is a table or an inflow layer, give up.
|
|
We don't understand them, yet. */
|
|
return;
|
|
}
|
|
|
|
/* Make the end-points editable */
|
|
if ( ! lo_EnsureEditableSearchNext(context, state, & begin) ) {
|
|
/* This is the last, unselectable line of a document in edit mode.
|
|
* Select the previous line.
|
|
*/
|
|
if ( lo_EnsureEditableSearchPrev(context, state, & begin) ){
|
|
lo_HitLine2(context, state, begin, 0, 0, 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
|
|
{
|
|
XP_ASSERT ( result->type >= LO_HIT_UNKNOWN && result->type <= LO_HIT_TABLE_LINE );
|
|
}
|
|
|
|
#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);
|
|
}
|
|
|
|
/* Similar to lo_Click, but doesn't process click
|
|
Returns TRUE if line would be selected if user clicked here
|
|
We only use this in Editor, so we can assume "layer" is NULL
|
|
*/
|
|
Bool LO_CanSelectLine(MWContext *context, int32 x, int32 y)
|
|
{
|
|
LO_HitResult result;
|
|
LO_Element *last_eptr;
|
|
|
|
/* LO_Hit will snap Y value to an element in the last line,
|
|
* but we want to return FALSE if cursor is in region
|
|
* below the last line
|
|
*/
|
|
last_eptr = LO_getFirstLastElement(context, FALSE);
|
|
if( (last_eptr == NULL) ||
|
|
(y >= last_eptr->lo_any.y +
|
|
last_eptr->lo_any.y_offset +
|
|
last_eptr->lo_any.height) )
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
LO_Hit(context, x, y, FALSE, &result, NULL);
|
|
|
|
return (result.lo_hitLine.region == LO_HIT_LINE_REGION_BEFORE);
|
|
}
|
|
|
|
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_TABLE_LINE:
|
|
{
|
|
#ifdef EDITOR
|
|
if( result->lo_hitLine.region == LO_HIT_LINE_REGION_BEFORE )
|
|
{
|
|
/* Click before a table
|
|
* Analogous to clicking before a line selects the line,
|
|
* clicking before a table selects the entire table
|
|
*/
|
|
LO_TableStruct *table = lo_GetParentTable(context, result->lo_hitLine.selection.end.element);
|
|
|
|
if( table )
|
|
{
|
|
edt_ForceTableSelection(context, table);
|
|
}
|
|
}
|
|
#endif /* EDITOR */
|
|
|
|
/* Set insert point to the the supplied element
|
|
(we trust it was already set to an editable element) */
|
|
lo_SetInsertPoint(context, top_state, result->lo_hitLine.selection.begin.element,
|
|
result->lo_hitLine.selection.begin.position, layer);
|
|
break;
|
|
}
|
|
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
|
|
{
|
|
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;
|
|
#if 0
|
|
/* Unfortunately, we hit this even when clicking within a text region - find another way to select line in a cell */
|
|
if( requireCaret || lo_GetParentTable(context, eptr) == 0 )
|
|
{
|
|
/* Not in a table cell or we need a caret
|
|
Just set insert point at beginning of element
|
|
*/
|
|
lo_SetInsertPoint(context, top_state, eptr, position,
|
|
layer);
|
|
|
|
return FALSE;
|
|
}
|
|
else
|
|
{
|
|
/* Inside a cell: Select the line
|
|
For some reason (unknown to cmanske)
|
|
the lo_hitLine.selection.end element is not
|
|
filled in correctly -- just use the beginning element
|
|
*/
|
|
result->lo_hitLine.selection.end = result->lo_hitLine.selection.begin;
|
|
lo_ExtendToIncludeHardBreak(context, state, & result->lo_hitLine.selection);
|
|
lo_SetSelection(context, & result->lo_hitLine.selection, FALSE);
|
|
return TRUE;
|
|
}
|
|
#endif
|
|
}
|
|
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 and modified
|
|
* 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_TABLE_LINE:
|
|
if( result.lo_hitLine.region == LO_HIT_LINE_REGION_BEFORE
|
|
|| result.lo_hitLine.region == LO_HIT_LINE_REGION_AFTER )
|
|
{
|
|
eptr = result.lo_hitLine.selection.begin.element;
|
|
position = result.lo_hitLine.selection.begin.position;
|
|
}
|
|
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 <mquote> */
|
|
#else
|
|
} while ( caret.element && caret.element->type == LO_LINEFEED
|
|
&& caret.element->lo_linefeed.break_type == LO_LINEFEED_BREAK_SOFT);
|
|
#endif
|
|
|
|
|
|
if ( ! caret.element )
|
|
return FALSE;
|
|
caret.position = 0;
|
|
if ( ! forward && caret.element ){
|
|
if ( ! (caret.element->type == LO_LINEFEED
|
|
&& caret.element->lo_linefeed.break_type == LO_LINEFEED_BREAK_HARD ) ){
|
|
caret.position = lo_GetMaximumInsertPointPosition(caret.element);
|
|
}
|
|
}
|
|
{
|
|
/* Bump by one position if crossing from one line of wrapped text to another.
|
|
* This is because the editor can't represent the difference between one edge
|
|
* and the next for wrapped text. *sigh*
|
|
*/
|
|
if ( caret.element && where->element
|
|
&& caret.element->type == LO_TEXT && where->element->type == LO_TEXT
|
|
&& caret.element->lo_any.edit_element == where->element->lo_any.edit_element )
|
|
{
|
|
if (! lo_BumpEditablePosition ( context, state, &caret.element, &caret.position, forward ) )
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
/* Skip over junk at edge of line. (For example, bullets.) */
|
|
if ( ! lo_EnsureEditableSearch2(context, state, &caret.element, &caret.position, forward) )
|
|
{
|
|
/* Either the end of the document, or the end of a cell. */
|
|
#if 0
|
|
if (! lo_BumpEditablePosition ( context, state, &caret.element, &caret.position, forward ) )
|
|
return FALSE;
|
|
#endif
|
|
return FALSE;
|
|
}
|
|
XP_ASSERT(caret.element != NULL);
|
|
*where = caret;
|
|
return TRUE;
|
|
}
|
|
|
|
/* Return TRUE if we actually found an edge. FALSE if we're off the start or end of the document. */
|
|
|
|
PRIVATE
|
|
Bool
|
|
lo_FindEdgeOfWord(MWContext* context, lo_DocState *state, LO_Position* where, LO_Position* wordEdge,
|
|
Bool bSelect, Bool forward, Bool bIncludeSpacesAtEndOfWord)
|
|
{
|
|
/*
|
|
* Search to find the edge of the word.
|
|
* word = { single_word | alpha_word | punct_word | space_word }
|
|
* single_word = LO_CC_SINGLE (e.g. chinese character for the season "autumn".)
|
|
* alpha_word = LO_CC_ALPHA + LO_CC_SPACE * (e.g. "foo10 ")
|
|
* punct_word = LO_CC_PUNCT + LO_CC_SPACE * (e.g. "---() ")
|
|
* space_word = LO_CC_SPACE + (e.g. " ")
|
|
*/
|
|
|
|
intn characterClass;
|
|
*wordEdge = *where;
|
|
|
|
/* Check for end of document */
|
|
|
|
if ( lo_IsEdgeOfDocument(context, state, wordEdge, forward) )
|
|
{
|
|
lo_FindDocumentEdge(context, state, wordEdge, bSelect, forward);
|
|
return TRUE;
|
|
}
|
|
|
|
if ( lo_IsLineEdge(context, state, where->element, where->position, FALSE, forward) )
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
if ( !forward && lo_IsLineEdge(context, state, where->element, where->position, FALSE, !forward) )
|
|
{
|
|
/* Starting at end of line. Move backward. */
|
|
lo_BumpEditablePosition(context, state, &wordEdge->element, &wordEdge->position, forward);
|
|
}
|
|
|
|
characterClass = lo_CharacterClassOf(context, state, wordEdge);
|
|
if ( characterClass == LO_CC_SINGLE ) {
|
|
if ( forward )
|
|
{
|
|
lo_BumpEditablePositionForward(context, state, &wordEdge->element, &wordEdge->position);
|
|
}
|
|
}
|
|
else {
|
|
/* Match more of this class */
|
|
if ( ! forward )
|
|
{
|
|
/* Skip over spaces */
|
|
if ( lo_SkipOver(context, state, wordEdge, LO_CC_SPACE, forward ) != LO_SO_NEW_CHARACTER_CLASS )
|
|
{
|
|
goto exit;
|
|
}
|
|
characterClass = lo_CharacterClassOf(context, state, wordEdge);
|
|
}
|
|
if ( characterClass != LO_CC_SPACE )
|
|
{
|
|
if ( lo_SkipOver(context, state, wordEdge, characterClass, forward) != LO_SO_NEW_CHARACTER_CLASS )
|
|
goto exit;
|
|
}
|
|
if ( forward )
|
|
{
|
|
if ( bIncludeSpacesAtEndOfWord )
|
|
{
|
|
/* Skip over spaces */
|
|
if ( lo_SkipOver(context, state, wordEdge, LO_CC_SPACE, forward ) != LO_SO_NEW_CHARACTER_CLASS )
|
|
goto exit;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* If we didn't hit the edge of the document, we are now one element further than we want to be. So back up one position. */
|
|
lo_BumpEditablePositionForward(context, state, &wordEdge->element, &wordEdge->position);
|
|
}
|
|
}
|
|
|
|
exit:
|
|
/* The edge might be a line feed. If so, move to an editable position. */
|
|
lo_EnsureEditableSearch(context, state, wordEdge, forward);
|
|
return TRUE;
|
|
}
|
|
|
|
PRIVATE
|
|
Bool
|
|
lo_IsEmptyText(LO_Element* eptr)
|
|
{
|
|
if ( eptr && eptr->type == LO_TEXT &&
|
|
lo_GetElementLength(eptr) == 0 )
|
|
{
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
PRIVATE
|
|
Bool
|
|
lo_FindLineEdge(MWContext* context, lo_DocState *state, LO_Position* where, LO_Position* lineEdge, Bool select, Bool forward)
|
|
{
|
|
/* Special case for hard breaks at end of paragraph */
|
|
|
|
Bool bHardBreakAtEndOfParagraph;
|
|
|
|
if ( lo_IsEdgeOfDocument(context, state, lineEdge, forward) )
|
|
{
|
|
lo_FindDocumentEdge(context, state, lineEdge, select, forward);
|
|
return TRUE;
|
|
}
|
|
|
|
bHardBreakAtEndOfParagraph = lo_IsHardBreak2(context, where->element, 0)
|
|
|| (lo_IsEmptyText(where->element) && lo_IsHardBreak2(context, where->element->lo_any.next, 0));
|
|
|
|
if ( bHardBreakAtEndOfParagraph && forward) {
|
|
/* "where" is the correct edge */
|
|
*lineEdge = *where;
|
|
}
|
|
else {
|
|
LO_HitResult result;
|
|
lo_HitLine2(context, state, where->element, where->position,
|
|
where->element->lo_any.x, where->element->lo_any.y, &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;
|
|
|
|
#ifdef EDITOR
|
|
case LO_HIT_TABLE_LINE:
|
|
/* Double click before or after a line is the same as single click */
|
|
lo_ProcessClick(context, top_state, state, result, FALSE, layer);
|
|
break;
|
|
#endif
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
wordEdge = caret;
|
|
}
|
|
else
|
|
{
|
|
result = TRUE;
|
|
if ( !bForward )
|
|
{
|
|
/* We are searching backwards,
|
|
* we want the character before the character we're currently on.
|
|
*/
|
|
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 )
|
|
{
|
|
/* In the Editor, the last line is an invisible
|
|
* line with an HRULE. We must scan backwards
|
|
* to get the last editable element
|
|
*/
|
|
if( EDT_IS_EDITOR(context) )
|
|
lo_EnsureEditableSearchPrev(context, state, &eptr);
|
|
}
|
|
else
|
|
{
|
|
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;
|
|
case LO_BUILTIN : /* 28 */
|
|
break;
|
|
default : /* something wrong!! */
|
|
bIsTabable = FALSE;
|
|
break;
|
|
}
|
|
return( bIsTabable );
|
|
}
|
|
|
|
|
|
/*
|
|
get first(last) if current element is NULL.
|
|
*/
|
|
Bool
|
|
LO_getNextTabableElement( MWContext *context, LO_TabFocusData *pCurrentFocus, int forward )
|
|
{
|
|
|
|
lo_DocState *state;
|
|
LO_Element *currentElement;
|
|
lo_MapAreaRec *tempArea;
|
|
LO_AnchorData *pLastAnchorData;
|
|
|
|
if( NULL == ( state = LO_getDocState(context) ) )
|
|
return( FALSE );
|
|
|
|
/*
|
|
A text may have been fragmented into multiple lines because the text folding.
|
|
If last tab focus is a LO_Text, remember its anchor to skip continuious text fragments.
|
|
|
|
When the focus is drawn, all continuious fragments will hav efocus, See
|
|
CWinCX::setTextTabFocusDrawFlag() in cmd\winfe\cxwin.cpp file.
|
|
*/
|
|
|
|
pLastAnchorData = NULL; /* assuming no need to skip continuious text fragments. */
|
|
|
|
currentElement = pCurrentFocus->pElement;
|
|
/* find the first candidate, */
|
|
if( currentElement == NULL ) {
|
|
/* find the first(last) element in the Doc as candidate, */
|
|
pCurrentFocus->pElement = LO_getFirstLastElement( context, forward ); /* forward is getting first */
|
|
|
|
/*TODO go down to cell_list if pElement has subtree */
|
|
|
|
} else {
|
|
if( pCurrentFocus->pElement->lo_any.type == LO_TEXT )
|
|
pLastAnchorData = pCurrentFocus->pAnchor; /* need to skip continuious text fragments. */
|
|
|
|
/* if the last element is a image map, try go to next area in the map */
|
|
if( currentElement->lo_any.type == LO_IMAGE ) {
|
|
if( forward ) {
|
|
pCurrentFocus->mapAreaIndex++;
|
|
tempArea = LO_getTabableMapArea(context, (LO_ImageStruct *)currentElement, pCurrentFocus->mapAreaIndex );
|
|
if( tempArea ) {
|
|
pCurrentFocus->pAnchor = tempArea->anchor;
|
|
return( TRUE );
|
|
}
|
|
} else { /* if( forward ) */
|
|
pCurrentFocus->mapAreaIndex--;
|
|
if( pCurrentFocus->mapAreaIndex > 0 ) { /* otherwise no need to search. */
|
|
tempArea = LO_getTabableMapArea(context, (LO_ImageStruct *)currentElement, pCurrentFocus->mapAreaIndex );
|
|
if( tempArea ) {
|
|
pCurrentFocus->pAnchor = tempArea->anchor;
|
|
return( TRUE );
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
/* advance the pointer, use the next(previous) as first candidate.
|
|
pElement = lo_GetNeighbor( currentElement, forward);
|
|
pElement = currentElement;
|
|
*/
|
|
lo_TraverseElement(context, state, &(pCurrentFocus->pElement), forward);
|
|
}
|
|
|
|
/*todo if not forward, get last area. */
|
|
pCurrentFocus->mapAreaIndex = 0; /* assumming no focus area */
|
|
pCurrentFocus->pAnchor = NULL;
|
|
while ( pCurrentFocus->pElement != NULL ) {
|
|
if( LO_isTabableElement(context, pCurrentFocus) ) {
|
|
|
|
if( pLastAnchorData == NULL /* no need to skip continuious text fragments. */
|
|
|| pCurrentFocus->pElement->lo_any.type != LO_TEXT
|
|
|| pLastAnchorData != pCurrentFocus->pAnchor )
|
|
return( TRUE );
|
|
/* else it is a text fragment with the same anchor as the last tabFocus text.
|
|
In such case, continue search.
|
|
*/
|
|
}
|
|
|
|
lo_TraverseElement(context, state, &(pCurrentFocus->pElement), forward);
|
|
}
|
|
|
|
return( FALSE );
|
|
} /* LO_getNextTabableElement */
|
|
|
|
/* NO_TAB_NAVIGATION */
|
|
|
|
|
|
|