netscape-revival
lib/layout/edtbuf.cpp
/* -*- Mode: C++; tab-width: 4; tabs-indent-mode: nil -*- */
//
// Editor save stuff. LWT 06/01/95
// this should be on the branch
//
#ifdef EDITOR
#include "editor.h"
#include "gui.h"
#ifdef XP_WIN32
#include <crtdbg.h>
#endif
// XP Strings
extern "C" {
#include "xpgetstr.h"
extern int XP_EDITOR_AUTO_SAVE;
}
// Defining DEBUG_AUTO_SAVE will make the document AutoSave every six seconds.
//#define DEBUG_AUTO_SAVE
// Test if ele
#define ED_IS_NOT_SCRIPT(pEle) (0 == (pEle->Text()->m_tf & (TF_SERVER | TF_SCRIPT)))
// Semantic sugar for debugging. Validates the tree on construction and
// destruction. Place in every method that munges the tree.
//
// VALIDATE_TREE(this);
#ifdef DEBUG
class CTreeValidater {
CEditBuffer* m_pBuffer;
public:
CTreeValidater(CEditBuffer* pBuffer)
: m_pBuffer(pBuffer)
{
m_pBuffer->ValidateTree();
}
~CTreeValidater() {
m_pBuffer->ValidateTree();
}
};
class CSuppressPhantomInsertPointCheck {
CEditBuffer* m_pBuffer;
public:
CSuppressPhantomInsertPointCheck(CEditBuffer* pBuffer)
: m_pBuffer(pBuffer)
{
m_pBuffer->SuppressPhantomInsertPointCheck(TRUE);
}
~CSuppressPhantomInsertPointCheck() {
m_pBuffer->SuppressPhantomInsertPointCheck(FALSE);
}
};
// The local variable names will cause problems if you try to
// use these macros twice in the same scope. However,
// they are nescessary to ensure that some compilers (MS VC 2.2)
// keep the object alive for the duration of the scope, instead
// of constructing and destructing it at the point of declaration.
// I think this may be a bug in the compiler -- but I'd have
// to check the ANSI standard to make sure.
#define VALIDATE_TREE(BUFFER) CTreeValidater CTreeValidater_x_zzx1(BUFFER)
#define SUPPRESS_PHANTOMINSERTPOINTCHECK(BUFFER) \
CSuppressPhantomInsertPointCheck CSuppressPhantomInsertPointCheck_x_zzx2(BUFFER)
#else
#define VALIDATE_TREE(BUFFER)
#define SUPPRESS_PHANTOMINSERTPOINTCHECK(BUFFER)
#endif
// Automated test routine hook
#ifdef DEBUG
class CEditTestManager {
public:
CEditTestManager(CEditBuffer* pBuffer);
XP_Bool Key(char key);
XP_Bool Backspace();
XP_Bool ReturnKey();
protected:
void DumpLoElements();
void VerifyLoElements();
void DumpDocumentContents();
XP_Bool ArrowTest();
XP_Bool LArrowTest(XP_Bool bSelect);
XP_Bool RArrowTest(XP_Bool bSelect);
XP_Bool UArrowTest(XP_Bool bSelect);
XP_Bool DArrowTest(XP_Bool bSelect);
XP_Bool EveryNavigationKeyEverywhereTest();
XP_Bool NavigateChunkCrashTest();
XP_Bool NavChunkCrashTest(XP_Bool bSelect, int chunk, XP_Bool bDirection);
XP_Bool DeleteKeyTest();
XP_Bool BackspaceKeyTest();
XP_Bool ZeroDocumentCursorTest();
XP_Bool OneCharDocumentCursorTest();
XP_Bool OneDayTest(int32 rounds);
XP_Bool BoldTest(int32 rounds);
void GetWholeDocumentSelection(CPersistentEditSelection& selection);
void DumpMemoryDelta();
void JRITest();
private:
CEditBuffer* m_pBuffer;
XP_Bool m_bTesting;
int m_iTriggerIndex;
static const char* m_kTrigger;
#ifdef XP_WIN32
_CrtMemState m_state;
#endif
};
const char* CEditTestManager::m_kTrigger = "$$$Test";
CEditTestManager::CEditTestManager(CEditBuffer* pBuffer)
: m_pBuffer(pBuffer),
m_iTriggerIndex(0),
m_bTesting(FALSE)
{
#ifdef XP_WIN32
_CrtMemCheckpoint( &m_state ); // In theorey, avoid measuring the data before we were created.
#endif
}
void CEditTestManager::DumpMemoryDelta() {
m_pBuffer->Trim(); // Clear out undo/redo log to simplify memory statistics.
#ifdef XP_WIN32
_CrtMemDumpAllObjectsSince( &m_state );
_CrtMemCheckpoint( &m_state );
#else
XP_TRACE(("Not supported on this OS.\n"));
#endif
}
XP_Bool CEditTestManager::Key(char key) {
XP_Bool result = m_bTesting;
if ( ! m_bTesting ){
if ( key == m_kTrigger[m_iTriggerIndex] ) {
m_iTriggerIndex++;
if ( m_kTrigger[m_iTriggerIndex] == '\0' ) {
m_bTesting = TRUE;
XP_TRACE(("Testing on! Type # of test, or Q to quit."));
}
}
else {
m_iTriggerIndex = 0;
}
}
else {
int bQuitTesting = FALSE;
m_bTesting = FALSE; // So when the tests type, it doesn't cause a recursion into the test code.
intn bTestResult = -1;
if ( key >= 'A' && key <= 'Z' ) key = key + ('a' - 'A');
switch (key) {
case 'q':
bQuitTesting = TRUE;
XP_TRACE(("Quitting test mode."));
break;
case 'a':
DumpLoElements();
break;
case 'b':
VerifyLoElements();
break;
case 'c':
DumpDocumentContents();
break;
case 'd':
DumpMemoryDelta();
break;
#ifdef EDITOR_JAVA
case 'j':
JRITest();
break;
#endif
case '0':
bTestResult = EveryNavigationKeyEverywhereTest();
break;
case '1':
bTestResult = ArrowTest();
break;
case '2':
bTestResult = BackspaceKeyTest();
break;
case '3':
bTestResult = DeleteKeyTest();
break;
case '4':
bTestResult = ZeroDocumentCursorTest();
break;
case '5':
bTestResult = OneCharDocumentCursorTest();
break;
case '6':
bTestResult = BoldTest(10); // OneDayTest(1);
break;
case '7':
m_pBuffer->m_bSkipValidation = TRUE;
bTestResult = OneDayTest(100);
m_pBuffer->m_bSkipValidation = FALSE;
break;
case '?':
#ifdef EDITOR_JAVA
XP_TRACE(("? - type this help\n"
"Q - quit.\n"
"a - print lo-elements.\n"
"b - verify lo-elements.\n"
"c - print document contents (elements, undo history, etc.)\n"
"d - dump memory usage delta.\n"
"j - java run time interface test.\n"
));
#else
XP_TRACE(("? - type this help\n"
"Q - quit.\n"
"a - print lo-elements.\n"
"b - verify lo-elements.\n"
"c - print document contents (elements, undo history, etc.)\n"
"d - dump memory usage delta.\n"
));
#endif // EDITOR_JAVA
XP_TRACE((
"0 - navigation key crash test.\n"
"1 - arrow completeness test.\n"
"2 - destructive backspace test.\n"
"3 - destructive delete test.\n"
"4 - empty document cursor test.\n"
"5 - one character document cursor test.\n"
"6 - one day document test - simulate editing for one whole day 1 cycle.\n"
"7 - one day document test - simulate editing for one whole day 500 cycle.\n"
));
break;
default:
XP_TRACE(("Type ? for help, Type # of test, or Q to quit."));
break;
}
if ( ! bQuitTesting )
{
m_bTesting = TRUE;
}
if ( bTestResult >= 0 ){
XP_TRACE(("Test %s", bTestResult ? "Passed": "Failed"));
}
}
return result;
}
void CEditTestManager::DumpLoElements() {
lo_PrintLayout(m_pBuffer->m_pContext);
}
void CEditTestManager::VerifyLoElements() {
lo_VerifyLayout(m_pBuffer->m_pContext);
}
void CEditTestManager::DumpDocumentContents(){
CStreamOutMemory buffer;
m_pBuffer->printState.Reset( &buffer, m_pBuffer );
m_pBuffer->DebugPrintTree( m_pBuffer->m_pRoot );
m_pBuffer->DebugPrintState(buffer);
// have to dump one line at a time. XP_TRACE has a 512 char limit on Windows.
char* b = buffer.GetText();
while ( *b != '\0' ) {
char* b2 = b;
while ( *b2 != '\0' && *b2 != '\n'){
b2++;
}
char old = *b2;
*b2 = '\0';
XP_TRACE(("%s", b));
*b2 = old;
b = b2 + 1;
if ( old == '\0' ) break;
}
}
XP_Bool CEditTestManager::ZeroDocumentCursorTest(){
XP_TRACE(("ZeroDocumentCursorTest"));
// [selection][chunk][direction][edge]
const ElementIndex kExpectedResults[2][EDT_NA_COUNT][2][2] =
{ // no selection
{
{{ 0, 0}, {0, 0} }, // EDT_NA_CHARACTER
{{ 0, 0}, {0, 0} }, // EDT_NA_WORD
{{ 0, 0}, {0, 0} }, // EDT_NA_LINEEDGE
{{ 0, 0}, {0, 0} }, // EDT_NA_DOCUMENT
{{ 0, 0}, {0, 0} } // EDT_NA_UPDOWN
},
// selection
{
{{ 0, 0}, {0, 1} }, // EDT_NA_CHARACTER
{{ 0, 0}, {0, 1} }, // EDT_NA_WORD
{{ 0, 0}, {0, 1} }, // EDT_NA_LINEEDGE
{{ 0, 0}, {0, 1} }, // EDT_NA_DOCUMENT
{{ 0, 0}, {0, 1} } // EDT_NA_UPDOWN
}
};
const char* kChunkName[EDT_NA_COUNT] = {
"character", "word", "line edge", "document", "updown"};
XP_Bool result = TRUE;
// Clear everything from existing document
EDT_SelectAll(m_pBuffer->m_pContext);
EDT_DeleteChar(m_pBuffer->m_pContext);
CPersistentEditInsertPoint zero(0,FALSE);
// Verify the cursor does the right thing for every possible cursor
for ( int select = 0; select < 2; select++ ) {
for ( int chunk = 0; chunk < EDT_NA_COUNT; chunk++ ) {
for ( int direction = 0; direction < 2; direction++ ) {
m_pBuffer->SetInsertPoint(zero);
m_pBuffer->NavigateChunk(select, chunk, direction);
CPersistentEditSelection selection;
m_pBuffer->GetSelection(selection);
for ( int edge = 0; edge < 2; edge++ ) {
ElementIndex expected = kExpectedResults[select][chunk][direction][edge];
ElementIndex actual = selection.GetEdge(edge)->m_index;
if ( expected != actual ){
result = FALSE;
XP_TRACE(("selection: %s chunk: %s direction: %s edge: %s. Expected %d got %d",
select ? "TRUE" : "FALSE",
kChunkName[chunk],
direction ? "forward" : "back",
edge ? "end" : "start",
expected,
actual));
}
}
}
}
}
return result;
}
XP_Bool CEditTestManager::OneCharDocumentCursorTest(){
XP_TRACE(("OneCharDocumentCursorTest"));
// [startPos][selection][chunk][direction][edge]
const ElementIndex kExpectedResults[2][2][EDT_NA_COUNT][2][2] =
{
// position 0
{ // no selection
{
{{ 0, 0}, {1, 1} }, // EDT_NA_CHARACTER
{{ 0, 0}, {1, 1} }, // EDT_NA_WORD
{{ 0, 0}, {1, 1} }, // EDT_NA_LINEEDGE
{{ 0, 0}, {1, 1} }, // EDT_NA_DOCUMENT
{{ 0, 0}, {0, 0} } // EDT_NA_UPDOWN
},
// selection
{
{{ 0, 0}, {0, 1} }, // EDT_NA_CHARACTER
{{ 0, 0}, {0, 1} }, // EDT_NA_WORD
{{ 0, 0}, {0, 2} }, // EDT_NA_LINEEDGE
{{ 0, 0}, {0, 2} }, // EDT_NA_DOCUMENT
{{ 0, 0}, {0, 2} } // EDT_NA_UPDOWN
}
},
// position 1
{ // no selection
{
{{ 0, 0}, {1, 1} }, // EDT_NA_CHARACTER
{{ 0, 0}, {1, 1} }, // EDT_NA_WORD
{{ 0, 0}, {1, 1} }, // EDT_NA_LINEEDGE
{{ 0, 0}, {1, 1} }, // EDT_NA_DOCUMENT
{{ 1, 1}, {1, 1} } // EDT_NA_UPDOWN
},
// selection
{
{{ 0, 1}, {1, 2} }, // EDT_NA_CHARACTER
{{ 0, 1}, {1, 2} }, // EDT_NA_WORD
{{ 0, 1}, {1, 2} }, // EDT_NA_LINEEDGE
{{ 0, 1}, {1, 2} }, // EDT_NA_DOCUMENT
{{ 0, 1}, {1, 2} } // EDT_NA_UPDOWN
}
}
};
const char* kChunkName[EDT_NA_COUNT] = {
"character", "word", "line edge", "document", "updown"};
XP_Bool result = TRUE;
// Clear everything from existing document
EDT_SelectAll(m_pBuffer->m_pContext);
EDT_DeleteChar(m_pBuffer->m_pContext);
m_pBuffer->InsertChar( 'X', FALSE );
// Verify the cursor does the right thing for every possible cursor
for ( int startPos = 0; startPos < 2; startPos++ ){
CPersistentEditInsertPoint p(startPos,FALSE);
for ( int select = 0; select < 2; select++ ) {
for ( int chunk = 0; chunk < EDT_NA_COUNT; chunk++ ) {
for ( int direction = 0; direction < 2; direction++ ) {
m_pBuffer->SetInsertPoint(p);
m_pBuffer->NavigateChunk(select, chunk, direction);
CPersistentEditSelection selection;
m_pBuffer->GetSelection(selection);
for ( int edge = 0; edge < 2; edge++ ) {
ElementIndex expected = kExpectedResults[startPos][select][chunk][direction][edge];
ElementIndex actual = selection.GetEdge(edge)->m_index;
if ( expected != actual ){
result = FALSE;
XP_TRACE(("position: %d selection: %s chunk: %s direction: %s edge: %s. Expected %d got %d",
startPos,
select ? "TRUE" : "FALSE",
kChunkName[chunk],
direction ? "forward" : "back",
edge ? "end" : "start",
expected,
actual));
}
}
}
}
}
}
return result;
}
XP_Bool CEditTestManager::OneDayTest(int32 rounds) {
char* kTitle = "One Day Test\n";
XP_TRACE(("%s", kTitle));
EDT_SelectAll(m_pBuffer->m_pContext);
EDT_DeleteChar(m_pBuffer->m_pContext);
EDT_PasteText(m_pBuffer->m_pContext, kTitle);
for( int32 i = 0; i < rounds; i++ ) {
XP_TRACE(("Round %d", i));
const char* kTestString = "All work and no play makes Jack a dull boy.\nRedrum.\n";
char c;
for ( int j = 0;
(c = kTestString[j]) != '\0';
j++ ) {
if ( c == '\n' || c == '\r') {
EDT_ReturnKey(m_pBuffer->m_pContext);
}
else {
EDT_KeyDown(m_pBuffer->m_pContext, c,0,0);
}
}
// And delete a little.
for ( int k = 0; k < 8; k++ ) {
EDT_DeletePreviousChar(m_pBuffer->m_pContext);
}
}
XP_TRACE(("End of %s.", kTitle));
return TRUE;
}
XP_Bool CEditTestManager::BoldTest(int32 rounds) {
char* kTitle = "Bold Test\n";
XP_TRACE(("%s", kTitle));
EDT_SelectAll(m_pBuffer->m_pContext);
EDT_DeleteChar(m_pBuffer->m_pContext);
EDT_PasteText(m_pBuffer->m_pContext, kTitle);
EDT_SelectAll(m_pBuffer->m_pContext);
for( int32 i = 0; i < rounds; i++ ) {
XP_TRACE(("Round %d", i));
EDT_CharacterData* normal = EDT_GetCharacterData( m_pBuffer->m_pContext );
EDT_CharacterData* bold = EDT_GetCharacterData( m_pBuffer->m_pContext );
bold->mask = TF_BOLD;
bold->values = TF_BOLD;
EDT_SetCharacterData(m_pBuffer->m_pContext, bold);
EDT_SetCharacterData(m_pBuffer->m_pContext, normal);
EDT_FreeCharacterData(normal);
EDT_FreeCharacterData(bold);
}
XP_TRACE(("End of %s.", kTitle));
return TRUE;
}
XP_Bool CEditTestManager::EveryNavigationKeyEverywhereTest() {
// Does navigation work from every position?
XP_Bool result;
result = NavigateChunkCrashTest();
result &= UArrowTest(FALSE);
result &= UArrowTest(TRUE);
result &= DArrowTest(FALSE);
result &= DArrowTest(TRUE);
XP_TRACE(("Done.\n"));
return result;
}
XP_Bool CEditTestManager::ArrowTest() {
// Does the navigation move smoothly through the document?
XP_Bool result;
result = LArrowTest(FALSE);
result |= LArrowTest(TRUE);
result |= RArrowTest(FALSE);
result |= RArrowTest(TRUE);
#if 0
// Too many false positives -- need to know about up/down at first/last line
result |= UArrowTest(FALSE);
result |= DArrowTest(FALSE);
#endif
result |= UArrowTest(TRUE);
result |= DArrowTest(TRUE);
XP_TRACE(("Done.\n"));
return result;
}
void CEditTestManager::GetWholeDocumentSelection(CPersistentEditSelection& selection){
CEditSelection wholeDocument;
m_pBuffer->m_pRoot->GetAll(wholeDocument);
selection = m_pBuffer->EphemeralToPersistent(wholeDocument);
// Ignore the last 2 positions -- it's the EOF marker
selection.m_end.m_index -= 2;
}
XP_Bool CEditTestManager::NavigateChunkCrashTest(){
XP_Bool result = TRUE;
for ( int select = 0; select < 2; select++ ) {
for ( int chunk = 0; chunk < EDT_NA_COUNT; chunk++ ) {
for ( int direction = 0; direction < 2; direction++ ) {
result &= NavChunkCrashTest(select, chunk, direction);
}
}
}
return result;
}
XP_Bool CEditTestManager::NavChunkCrashTest(XP_Bool bSelect, int chunk, XP_Bool bDirection){
XP_Bool bResult = TRUE;
CPersistentEditSelection w;
GetWholeDocumentSelection(w);
CPersistentEditInsertPoint p;
XP_TRACE(("NavChunkCrashTest bSelect = %d chunk = %d bDirection = %d\n", bSelect, chunk, bDirection));
for ( p = w.m_start;
p.m_index < w.m_end.m_index;
p.m_index++ ) {
m_pBuffer->SetInsertPoint(p);
m_pBuffer->NavigateChunk(bSelect, chunk, bDirection);
}
return bResult;
}
XP_Bool CEditTestManager::RArrowTest(XP_Bool bSelect){
XP_Bool result = TRUE;
CPersistentEditSelection w;
GetWholeDocumentSelection(w);
CPersistentEditInsertPoint p;
XP_TRACE(("Right Arrow%s\n", bSelect ? " with shift key" : ""));
for ( p = w.m_start;
p.m_index < w.m_end.m_index;
p.m_index++ ) {
m_pBuffer->SetInsertPoint(p);
m_pBuffer->NavigateChunk(bSelect, LO_NA_CHARACTER, TRUE);
CPersistentEditSelection s2;
m_pBuffer->GetSelection(s2);
if ( (bSelect == s2.IsInsertPoint()) ||
s2.m_end.m_index != p.m_index + 1 ) {
XP_TRACE(("%d should be %d was %d\n", p.m_index,
p.m_index + 1, s2.m_end.m_index));
result = FALSE;
}
}
return result;
}
XP_Bool CEditTestManager::LArrowTest(XP_Bool bSelect){
XP_Bool result = TRUE;
CPersistentEditSelection w;
GetWholeDocumentSelection(w);
CPersistentEditInsertPoint p(0,TRUE);
XP_TRACE(("Left Arrow Test%s\n", bSelect ? " with shift key" : ""));
for ( p.m_index = w.m_end.m_index - 1;
p.m_index > w.m_start.m_index;
p.m_index-- ) {
CEditSelection startSelection;
CPersistentEditSelection s2;
p.m_bStickyAfter = TRUE;
m_pBuffer->SetInsertPoint(p);
m_pBuffer->GetSelection(startSelection);
if ( startSelection.m_start.GapWithBothSidesAllowed() && ! bSelect ) {
// XP_TRACE(("%d - gap with both sides allowed.", p.m_index));
// Test that SetInsertPoint did the right thing.
m_pBuffer->GetSelection(s2);
if ( ! s2.IsInsertPoint() || !s2.m_start.IsEqualUI(p) ) {
XP_TRACE(("SetInsertPoint at %d should be %d.%d was %d.%d\n", p.m_index,
p.m_index, p.m_bStickyAfter,
s2.m_start.m_index, s2.m_start.m_bStickyAfter));
result = FALSE;
}
// Test hump over soft break
m_pBuffer->NavigateChunk(bSelect, LO_NA_CHARACTER, FALSE);
m_pBuffer->GetSelection(s2);
CPersistentEditInsertPoint expected = p;
expected.m_bStickyAfter = FALSE;
if ( ! s2.IsInsertPoint() || !s2.m_start.IsEqualUI(expected) ) {
XP_TRACE(("gap at %d should be %d.%d was %d.%d\n", p.m_index,
expected.m_index, expected.m_bStickyAfter,
s2.m_start.m_index, s2.m_start.m_bStickyAfter));
result = FALSE;
}
p.m_bStickyAfter = FALSE;
m_pBuffer->SetInsertPoint(p);
}
m_pBuffer->NavigateChunk(bSelect, LO_NA_CHARACTER, FALSE);
m_pBuffer->GetSelection(s2);
if ( bSelect == s2.IsInsertPoint() ) {
XP_TRACE(("%d Wrong type of selection. Expected %d.%d was %d.%d\n", p.m_index,
p.m_index, p.m_bStickyAfter,
s2.m_start.m_index, s2.m_start.m_bStickyAfter));
result = FALSE;
}
else if ( s2.m_start.m_index != p.m_index - 1 ) {
XP_TRACE(("%d wrong edge position. Should be %d.%d was %d.%d\n", p.m_index,
p.m_index-1, p.m_bStickyAfter,
s2.m_start.m_index, s2.m_start.m_bStickyAfter));
result = FALSE;
}
}
return result;
}
XP_Bool CEditTestManager::UArrowTest(XP_Bool bSelect){
XP_Bool result = TRUE;
CPersistentEditSelection w;
GetWholeDocumentSelection(w);
CPersistentEditInsertPoint p;
XP_TRACE(("Up Arrow Test%s\n", bSelect ? " with shift key" : ""));
for ( p = w.m_end.m_index - 1;
p.m_index > w.m_start.m_index;
p.m_index-- ) {
m_pBuffer->SetInsertPoint(p);
CPersistentEditSelection s;
m_pBuffer->GetSelection(s);
m_pBuffer->ClearMove();
m_pBuffer->UpDown(bSelect, FALSE);
CPersistentEditSelection s2;
m_pBuffer->GetSelection(s2);
if ( s2 == s ) {
XP_TRACE(("%d didn't change selection\n", p.m_index));
result = FALSE;
}
}
return result;
}
XP_Bool CEditTestManager::DArrowTest(XP_Bool bSelect){
XP_Bool result = TRUE;
CPersistentEditSelection w;
GetWholeDocumentSelection(w);
CPersistentEditInsertPoint p;
XP_TRACE(("Down Arrow Test%s\n", bSelect ? " with shift key" : ""));
for ( p = w.m_start.m_index;
p.m_index < w.m_end.m_index;
p.m_index++ ) {
m_pBuffer->SetInsertPoint(p);
CPersistentEditSelection s;
m_pBuffer->GetSelection(s);
m_pBuffer->ClearMove();
m_pBuffer->UpDown(bSelect, TRUE);
CPersistentEditSelection s2;
m_pBuffer->GetSelection(s2);
if ( s2 == s ) {
XP_TRACE(("%d didn't change selection\n", p.m_index));
result = FALSE;
}
}
return result;
}
XP_Bool CEditTestManager::BackspaceKeyTest(){
XP_Bool result = TRUE;
CPersistentEditSelection w;
GetWholeDocumentSelection(w);
CPersistentEditInsertPoint p;
XP_TRACE(("DeleteKeyTest"));
p = w.m_end;
p.m_index--;
m_pBuffer->SetInsertPoint(p);
for ( p = w.m_start.m_index;
p.m_index < w.m_end.m_index;
p.m_index++ ) {
m_pBuffer->DeletePreviousChar();
}
return result;
}
XP_Bool CEditTestManager::DeleteKeyTest(){
XP_Bool result = TRUE;
CPersistentEditSelection w;
GetWholeDocumentSelection(w);
CPersistentEditInsertPoint p;
XP_TRACE(("DeleteKeyTest"));
m_pBuffer->SetInsertPoint(w.m_start);
for ( p = w.m_start.m_index;
p.m_index < w.m_end.m_index;
p.m_index++ ) {
m_pBuffer->DeleteNextChar();
}
return result;
}
XP_Bool CEditTestManager::Backspace() {
XP_Bool result = m_bTesting;
if ( ! m_bTesting ){
m_iTriggerIndex--;
if (m_iTriggerIndex < 0 ) m_iTriggerIndex = 0;
}
return m_bTesting;
}
XP_Bool CEditTestManager::ReturnKey() {
if ( ! m_bTesting ){
m_iTriggerIndex = 0;
}
return m_bTesting;
}
#ifdef EDITOR_JAVA
#include <jri.h>
extern "C" { JRIEnv* npn_getJavaEnv(void); }
void CEditTestManager::JRITest(){
XP_TRACE(("JRITest"));
JRIEnv* env = npn_getJavaEnv();
if ( ! env ) return; /* No Java. */
JRI_ExceptionClear(env);
// To Do: Define a java class for editor plug-ins.
struct java_lang_Class* integerClazz = JRI_FindClass(env, "java/lang/Integer");
if ( JRI_ExceptionOccurred(env))
return;
JRIMethodID integerClazzmethodID = JRI_GetStaticMethodID(env, integerClazz, "Integer", "Ijava/lang/Integer;");
if ( JRI_ExceptionOccurred(env))
return;
jref integer = JRI_NewObject(env, integerClazz, integerClazzmethodID);
if ( JRI_ExceptionOccurred(env))
return;
}
#endif // EDITOR_JAVA
#endif // DEBUG
//-----------------------------------------------------------------------------
// CParseState / CPrintState
//-----------------------------------------------------------------------------
CParseState::CParseState()
:m_pNextText(0),m_bInTitle(FALSE), m_bInHead(FALSE),m_bInJavaScript(FALSE),
m_pJavaScript(0){
}
CParseState::~CParseState() {
delete m_pNextText;
}
void CParseState::Reset(){
bLastWasSpace = TRUE; // at the beginning of the document
// we should ignore leading spaces
m_baseFontSize = 3;
m_formatTypeStack.Reset();
m_formatTextStack.Reset();
m_bInHead = FALSE;
m_bInTitle = FALSE;
m_bInJavaScript = FALSE;
m_pJavaScript = 0;
delete m_pNextText;
// force the compiler to use the non-stream constructor
m_pNextText = new CEditTextElement((CEditElement*)0,0);
}
//-----------------------------------------------------------------------------
// CPrintState
//-----------------------------------------------------------------------------
void CPrintState::Reset( IStreamOut* pOut, CEditBuffer *pBuffer ){
m_pOut = pOut;
m_iCharPos = 0;
m_bTextLast = FALSE;
m_iLevel = 0;
m_pBuffer = pBuffer;
}
//-----------------------------------------------------------------------------
// CEditLinkManager
//-----------------------------------------------------------------------------
EDT_HREFData* ED_Link::GetData(){
EDT_HREFData* pData = EDT_NewHREFData();
pData->pURL = XP_STRDUP( hrefStr );
if( pExtra ){
pData->pExtra = XP_STRDUP( pExtra );
}
else {
pData->pExtra = 0;
}
return pData;
}
CEditLinkManager::CEditLinkManager(){
}
ED_Link* CEditLinkManager::MakeLink( char *pHREF, char *pExtra, intn iRefCount ){
ED_Link *pNewLink = XP_NEW( ED_Link );
pNewLink->iRefCount = iRefCount;
pNewLink->pLinkManager = this;
pNewLink->hrefStr = XP_STRDUP( pHREF );
if( pExtra ){
pNewLink->pExtra = XP_STRDUP( pExtra );
}
else {
pNewLink->pExtra = 0;
}
return pNewLink;
}
void CEditLinkManager::AdjustAllLinks( char *pOldURL, char* pNewURL ){
int i = 0;
while( i < m_links.Size() ){
ED_Link *pLink;
if( (pLink = m_links[i]) != 0 ){
if( pLink->hrefStr
&& pLink->hrefStr[0] != '#'
&& pLink->hrefStr[0] != '`' ){
char *pNewHref;
char *pStr = NET_MakeAbsoluteURL( pOldURL, pLink->hrefStr );
int iRet = NET_MakeRelativeURL( pNewURL,
pStr,
&pNewHref );
XP_FREE( pStr );
// if the url is cross device or the same device but not with
// absolute pathing (starts with a slash)
// then perform the path conversion.
if( iRet == NET_URL_NOT_ON_SAME_DEVICE
|| ( iRet == NET_URL_SAME_DEVICE
&& pLink->hrefStr[0] != '/') ){
if( pLink->hrefStr != 0 ){
XP_FREE( pLink->hrefStr );
}
pLink->hrefStr = XP_STRDUP( pNewHref );
}
}
}
i++;
}
}
ED_LinkId CEditLinkManager::Add( char *pHREF, char *pExtra ){
int i = 0;
int lastFree = -1;
while( i < m_links.Size() ){
if( m_links[i] != 0 ){
// need a more intelegent way of comparing HREFs
if( XP_STRCMP( m_links[i]->hrefStr, pHREF) == 0
&& (!!pExtra == !!m_links[i]->pExtra)
&& ((pExtra &&
XP_STRCMP( pExtra, m_links[i]->pExtra ) == 0 )
|| pExtra == 0 )
){
m_links[i]->iRefCount++;
return m_links[i];
}
}
else {
lastFree = i;
}
i++;
}
ED_Link *pNewLink = MakeLink( pHREF, pExtra );
//
// Store it.
//
if( lastFree != -1 ){
m_links[lastFree] = pNewLink;
}
else {
lastFree = m_links.Add( pNewLink );
}
pNewLink->linkOffset = lastFree;
return pNewLink;
}
void CEditLinkManager::Free(ED_LinkId id){
if( --id->iRefCount == 0 ){
m_links[ id->linkOffset ] = 0;
XP_FREE( id );
}
}
//-----------------------------------------------------------------------------
// CEditBuffer
//-----------------------------------------------------------------------------
CEditElement* CEditBuffer::CreateElement(PA_Tag* pTag, CEditElement *pParent){
CEditElement *pElement;
// this is a HACK. We don't understand the tag but we want
// to make sure that we can re-gurgitate it to layout.
// Save the parameter data so we can spew it back out.
char *locked_buff;
PA_LOCK(locked_buff, char *, pTag->data);
if( locked_buff && *locked_buff != '>'){
pElement = new CEditElement(pParent,pTag->type,locked_buff);
}
else {
pElement = new CEditElement(pParent,pTag->type);
}
PA_UNLOCK(pTag->data);
return pElement;
}
CParseState* CEditBuffer::GetParseState() {
if ( m_parseStateStack.IsEmpty() ) {
PushParseState();
}
return m_parseStateStack.Top();
}
void CEditBuffer::PushParseState(){
CParseState* state = new CParseState();
state->Reset();
m_parseStateStack.Push(state);
}
void CEditBuffer::PopParseState(){
if ( m_parseStateStack.IsEmpty() ) {
XP_ASSERT(FALSE);
}
else {
CParseState* state = m_parseStateStack.Pop();
delete state;
}
}
void CEditBuffer::ResetParseStateStack(){
while ( ! m_parseStateStack.IsEmpty() ) {
PopParseState();
}
}
//
// stolen from layout
//
#define DEF_TAB_WIDTH 8
#define REMOVECHAR(p) XP_MEMMOVE( p, p+1, XP_STRLEN(p) );
void edt_ExpandTab(char *p, intn n){
if( n != 1 ){
XP_MEMMOVE( p+n, p+1, XP_STRLEN(p));
}
XP_MEMSET( p, ' ', n);
}
//
// Parse the text into a series of tags. Each tag is at most one line in
// length.
//
intn CEditBuffer::NormalizePreformatText(pa_DocData *pData, PA_Tag* pTag,
intn status ){
char *pText;
intn retVal = OK_CONTINUE;
m_bInPreformat = TRUE;
PA_LOCK(pText, char *, pTag->data);
//
// Calculate how big a buffer we are going to need to normalize this
// preformatted text.
//
int iTabCount = 0;
int iLen = 0;
int iNeedLen;
int iSpace;
while( *pText ){
if( *pText == TAB ){
iTabCount++;
}
pText++;
iLen++;
}
PA_UNLOCK(pTag->data);
iNeedLen = iLen + iTabCount * DEF_TAB_WIDTH+1;
pTag->data = (PA_Block) PA_REALLOC( pTag->data, iNeedLen );
pTag->data_len = iNeedLen;
PA_LOCK(pText, char *, pTag->data);
// LTNOTE: total hack, but make sure the buffer is 0 terminated... Probably has
// bad internation ramifications.. Oh Well...
// JHPNOTE: I'll say this is a total hack. We used to write at [data_len], which
// smashed beyond allocated memory. As far as I can see there's no need to write here,
// since the string is known to be zero terminated now, and all the operations
// below keep the string zero terminated. Never the less, in the interest of keeping
// tradition alive (and because I don't want to take the chance of introducing a
// bug, I'll leave the correct, but unnescessary, code in place.
pText[iNeedLen-1] = 0;
pText[iLen] = 0;
char* pBuf = pText;
//
// Now actually Normalize the preformatted text
//
while( *pText ){
switch( *pText ){
// Look for CF LF, if you see it, just treat it as LF
case CR:
if( pText[1] == LF ){
REMOVECHAR(pText);
break;
}
else {
*pText = LF;
}
case LF:
pText++;
m_preformatLinePos = 0;
break;
case VTAB:
case FF:
REMOVECHAR(pText);
break;
case TAB:
iSpace = DEF_TAB_WIDTH - (m_preformatLinePos % DEF_TAB_WIDTH);
edt_ExpandTab( pText, iSpace );
pText += iSpace;
m_preformatLinePos += iSpace;
break;
default:
pText++;
m_preformatLinePos++;
}
}
XP_Bool bBreak = FALSE;
while( *pBuf && retVal == OK_CONTINUE ){
pText = pBuf;
bBreak = FALSE;
// scan for end of line.
while( *pText && *pText != '\n' ){
pText++;
}
PA_Tag *pNewTag = XP_NEW( PA_Tag );
XP_BZERO( pNewTag, sizeof( PA_Tag ) );
pNewTag->type = P_TEXT;
char save = *pText;
*pText = 0;
edt_SetTagData(pNewTag, pBuf );
*pText = save;
// include the new line
if( *pText == '\n' ){
pText++;
bBreak = TRUE;
}
pBuf = pText;
retVal = EDT_ProcessTag( pData, pNewTag, status );
if( bBreak ){
pNewTag = XP_NEW( PA_Tag );
XP_BZERO( pNewTag, sizeof( PA_Tag ) );
pNewTag->type = P_LINEBREAK;
retVal = EDT_ProcessTag( pData, pNewTag, status );
}
}
PA_UNLOCK(pTag->data);
m_bInPreformat = FALSE;
return ( retVal == OK_CONTINUE) ? OK_IGNORE : retVal ;
}
static char *anchorHrefParams[] = {
PARAM_HREF,
0
};
static char *bodyParams[] = {
PARAM_BACKGROUND,
PARAM_BGCOLOR,
PARAM_TEXT,
PARAM_LINK,
PARAM_ALINK,
PARAM_VLINK,
0
};
void NormalizeEOLsInString(char* pStr){
int state = 0;
char* pOut = pStr;
for(;; ) {
char c = *pStr++;
if ( c == '\0' ) break;
int charClass = 0;
if ( c == '\r' ) charClass = 1;
else if ( c == '\n') charClass = 2;
switch ( state ) {
case 0: /* Normal */
switch ( charClass ) {
case 0: /* Normal char */
*pOut++ = c;
break;
case 1: /* CR */
state = 1;
*pOut++ = '\n';
break;
case 2: /* LF */
state = 2;
*pOut++ = '\n';
break;
}
break;
case 1: /* Just saw a CR */
switch ( charClass ) {
case 0: /* Normal char */
*pOut++ = c;
state = 0;
break;
case 1: /* CR */
*pOut++ = '\n';
break;
case 2: /* LF */
state = 0;
/* Swallow it, back to normal */
break;
}
break;
case 2: /* Just saw a LF */
switch ( charClass ) {
case 0: /* Normal char */
*pOut++ = c;
state = 0;
break;
case 1: /* CR */
/* Swallow it, back to normal */
state = 0;
break;
case 2: /* LF */
*pOut++ = '\n';
break;
}
break;
}
}
*pOut = '\0';
}
void NormalizeEOLsInTag(PA_Tag* pTag){
// Convert the end-of-lines into \n characters.
if( pTag->data ){
char* pStr;
PA_LOCK(pStr, char*, pTag->data);
NormalizeEOLsInString(pStr);
pTag->data_len = strlen( pStr );
PA_UNLOCK( pTag->data );
}
}
XP_Bool IsDocTypeTag(PA_Tag* pTag){
// Is this a DocType tag?
XP_Bool result = FALSE;
if ( pTag->type == P_UNKNOWN ) {
if( pTag->data ){
char* pStr;
const char* kDocType = "!DOCTYPE";
unsigned int docTypeLen = XP_STRLEN(kDocType);
PA_LOCK(pStr, char*, pTag->data);
if ( XP_STRLEN(pStr) >= docTypeLen && XP_STRNCASECMP(pStr, kDocType, docTypeLen) == 0 ) {
result = TRUE;
}
PA_UNLOCK( pTag->data );
}
}
return result;
}
intn CEditBuffer::ParseTag(pa_DocData *pData, PA_Tag* pTag, intn status){
char *pStr;
CEditElement* pElement = 0;
CEditElement *pContainer;
PA_Block buff;
TagType t;
intn retVal = OK_CONTINUE;
NormalizeEOLsInTag(pTag);
/* P_STRIKE is a synonym for P_STRIKEOUT. Since pre-3.0 versions of
* Navigator don't recognize P_STRIKE ( "<S>" ), we switch it to
* P_STRIKEOUT ( "<STRIKE>" ). Also, it would complicate the
* editor internals to track two versions of the same tag.
* We test here so that we catch both the beginning and end tag.
*/
if ( pTag->type == P_STRIKE ) {
pTag->type = P_STRIKEOUT;
}
//
// LTNOTE: make this a seperate routine.
// If this is an end tag, match backwardly to the appropriate tag.
//
if( pTag->is_end && !BitSet( edt_setUnsupported, pTag->type ) &&
pTag->type != P_UNKNOWN ){
CEditElement *pPop = m_pCreationCursor;
if( pTag->type == P_HEAD ){
GetParseState()->m_bInHead = FALSE;
return retVal;
}
if( pTag->type == P_TITLE ){
GetParseState()->m_bInTitle = FALSE;
return retVal;
}
if( pTag->type == P_SCRIPT && GetParseState()->m_bInJavaScript ){
GetParseState()->m_bInJavaScript = FALSE;
return retVal;
}
if( BitSet( edt_setTextContainer, pTag->type ) || BitSet( edt_setList, pTag->type ) ){
GetParseState()->bLastWasSpace = TRUE;
}
while( pPop && pTag->type != pPop->GetType() ){
pPop = pPop->GetParent();
}
// if this is character formatting, pop the stack.
if( pTag->type == P_CENTER | pTag->type == P_DIVISION ){
if( !GetParseState()->m_formatAlignStack.IsEmpty() ){
GetParseState()->m_formatAlignStack.Pop();
}
}
else if( BitSet( edt_setCharFormat, pTag->type ) && !GetParseState()->m_formatTypeStack.IsEmpty()){
// The navigator is forgiving about the nesting of style end tags, e.g. </b> vs. </i>.
// So you can say, for example,
// <b>foo<i>bar</b>baz</i> and it will work as if you had said:
// <b>foo<i>bar</i>baz</b>.
// LTNOTE: what the fuck. This crashes sometimes??
//delete GetParseState()->m_pNextText;
GetParseState()->m_pNextText = 0;
t = GetParseState()->m_formatTypeStack.Pop();
GetParseState()->m_pNextText = GetParseState()->m_formatTextStack.Pop();
// </a> pops all the HREFs. So if you say:
// <a href="a">foo<a href="b">bar</a>baz, it's as if you said:
// <a href="a">foo</a><a href="b">bar</a>baz
// For fun, try this:
// <a href="a">foo<a name="b">bar</a>baz
// The 'bar' will look like it is a link, but it won't act like a link if you
// press on it.
// Anyway, we emulate this mess by clearing the hrefs out of the format text stack when we
// close any P_ANCHOR tag
if ( pTag->type == P_ANCHOR ) {
if ( GetParseState()->m_pNextText ) {
GetParseState()->m_pNextText->SetHREF( ED_LINK_ID_NONE );
}
TXP_PtrStack_CEditTextElement& textStack = GetParseState()->m_formatTextStack;
for ( int i = 0; i < textStack.Size(); i++) {
if ( textStack[i] ) {
textStack[i]->SetHREF( ED_LINK_ID_NONE );
}
}
}
}
else if( pPop && pTag->type == pPop->GetType() && pPop->GetParent() != 0 ) {
m_pCreationCursor = pPop->GetParent();
}
else{
// We have an error. Ignore it for now.
//DebugBreak();
}
if ( pTag->type == P_TABLE_HEADER || pTag->type == P_TABLE_DATA || pTag->type == P_CAPTION ) {
PopParseState();
}
}
else {
//
// make sure all solo tags are placed in a paragraph.
//
if( pTag->type != P_TEXT
&& (BitSet( edt_setSoloTags, pTag->type )
|| BitSet( edt_setUnsupported, pTag->type ) )
&& !BitSet( edt_setTextContainer, m_pCreationCursor->GetType() )
&& m_pCreationCursor->FindContainer() == 0 ){
//m_pCreationCursor = new CEditElement(m_pCreationCursor,P_PARAGRAPH);
m_pCreationCursor = CEditContainerElement::NewDefaultContainer( m_pCreationCursor,
GetCurrentAlignment() );
}
switch(pTag->type){
case P_TEXT:
if( pTag->data ){
PA_LOCK(pStr, char*, pTag->data);
if( GetParseState()->m_bInTitle ){
AppendTitle( pStr );
PA_UNLOCK( pTag->data );
return retVal;
}
else if( GetParseState()->m_bInJavaScript ){
GetParseState()->m_pJavaScript->Write( pStr, XP_STRLEN( pStr ) );
PA_UNLOCK( pTag->data );
return OK_IGNORE;
}
else if( !BitSet( edt_setFormattedText, m_pCreationCursor->GetType() )
&& !(GetParseState()->m_pNextText->m_tf & (TF_SERVER|TF_SCRIPT))
&& !m_pCreationCursor->InFormattedText() ){
NormalizeText( pStr );
}
else {
if( !m_bInPreformat ){
//
// calls EDT_ProcessTag iteratively
//
return NormalizePreformatText( pData, pTag, status );
}
}
// we probably need to adjust the length in the parse tag.
// it works without it, but I'm not sure why.
// if the text didn't get reduced away.
// Strip white space in tables
XP_Bool bGoodText = *pStr && ! (( XP_IS_SPACE(*pStr) && pStr[1] == '\0' ) &&
BitSet( edt_setIgnoreWhiteSpace, m_pCreationCursor->GetType()));
if( bGoodText ){
// if this text is not within a container, make one.
if( !BitSet( edt_setTextContainer, m_pCreationCursor->GetType() )
&& m_pCreationCursor->FindContainer() == 0 ){
m_pCreationCursor = CEditContainerElement::NewDefaultContainer( m_pCreationCursor,
GetCurrentAlignment() );
}
CEditTextElement *pNew = GetParseState()->m_pNextText->CopyEmptyText(m_pCreationCursor);
pNew->SetText( pStr );
// create a new text element. It adds itself as a child
pElement = pNew;
}
else {
retVal = OK_IGNORE;
}
PA_UNLOCK( pTag->data );
}
break;
//
// Paragraphs are always inserted at the level of the current
// container.
//
case P_HEADER_1:
case P_HEADER_2:
case P_HEADER_3:
case P_HEADER_4:
case P_HEADER_5:
case P_HEADER_6:
case P_DESC_TITLE:
case P_ADDRESS:
case P_LIST_ITEM:
case P_DESC_TEXT:
case P_PARAGRAPH:
ContainerCase:
pContainer = m_pCreationCursor->FindContainerContainer();
if( pContainer ){
m_pCreationCursor = pContainer;
}
else {
m_pCreationCursor = m_pRoot;
}
m_pCreationCursor = pElement = new CEditContainerElement( m_pCreationCursor,
pTag, GetCurrentAlignment() );
break;
case P_UNUM_LIST:
case P_NUM_LIST:
case P_DESC_LIST:
case P_MENU:
case P_DIRECTORY:
case P_BLOCKQUOTE:
pContainer = m_pCreationCursor->FindContainerContainer();
if( pContainer ){
m_pCreationCursor = pContainer;
}
else {
m_pCreationCursor = m_pRoot;
}
m_pCreationCursor = pElement = new CEditListElement( m_pCreationCursor, pTag);
break;
case P_BODY:
ParseBodyTag(pTag);
break;
case P_HEAD:
GetParseState()->m_bInHead = TRUE;
break;
// character formatting
case P_SCRIPT:
if( GetParseState()->m_bInHead ){
if( GetParseState()->m_pJavaScript == 0 ){
GetParseState()->m_pJavaScript = new CStreamOutMemory();
// Save the <SCRIPT> tag and its parameters
char* kScriptTag = "<SCRIPT";
GetParseState()->m_pJavaScript->Write(kScriptTag, XP_STRLEN(kScriptTag));
char *locked_buff;
PA_LOCK(locked_buff, char *, pTag->data );
GetParseState()->m_pJavaScript->Write(locked_buff, XP_STRLEN(locked_buff));
PA_UNLOCK(pTag->data);
}
GetParseState()->m_bInJavaScript = TRUE;
return OK_IGNORE;
}
// intentionally fall through
case P_SERVER:
// For either P_SCRIPT or P_SERVER
if( ! GetParseState()->m_bInHead ){
// Save tag parameters.
if ( GetParseState()->m_pNextText ) {
char *locked_buff;
PA_LOCK(locked_buff, char *, pTag->data );
GetParseState()->m_pNextText->SetScriptExtra(locked_buff);
PA_UNLOCK(pTag->data);
}
}
m_preformatLinePos = 0;
// intentionally fall through.
case P_BOLD:
case P_STRONG:
case P_ITALIC:
case P_EMPHASIZED:
case P_VARIABLE:
case P_CITATION:
case P_FIXED:
case P_CODE:
case P_KEYBOARD:
case P_SAMPLE:
case P_SUPER:
case P_SUB:
case P_STRIKEOUT:
case P_STRIKE:
case P_UNDERLINE:
case P_BLINK:
GetParseState()->m_formatTextStack.Push(GetParseState()->m_pNextText);
GetParseState()->m_formatTypeStack.Push(pTag->type);
GetParseState()->m_pNextText = GetParseState()->m_pNextText->CopyEmptyText();
GetParseState()->m_pNextText->m_tf |= edt_TagType2TextFormat(pTag->type);
break;
case P_SMALL:
GetParseState()->m_formatTextStack.Push(GetParseState()->m_pNextText);
GetParseState()->m_formatTypeStack.Push(pTag->type);
GetParseState()->m_pNextText = GetParseState()->m_pNextText->CopyEmptyText();
if( GetParseState()->m_pNextText->m_tf & TF_FONT_SIZE ){
GetParseState()->m_pNextText->SetFontSize(GetParseState()->m_pNextText->GetFontSize()-1);
}
else {
GetParseState()->m_pNextText->SetFontSize(m_pCreationCursor->GetDefaultFontSize()-1);
}
break;
case P_BIG:
GetParseState()->m_formatTextStack.Push(GetParseState()->m_pNextText);
GetParseState()->m_formatTypeStack.Push(pTag->type);
GetParseState()->m_pNextText = GetParseState()->m_pNextText->CopyEmptyText();
if( GetParseState()->m_pNextText->m_tf & TF_FONT_SIZE ){
GetParseState()->m_pNextText->SetFontSize(GetParseState()->m_pNextText->GetFontSize()+1);
}
else {
GetParseState()->m_pNextText->SetFontSize(m_pCreationCursor->GetDefaultFontSize()+1);
}
break;
case P_BASEFONT:
GetParseState()->m_baseFontSize = edt_FetchParamInt(pTag, PARAM_SIZE, 3);
GetParseState()->m_pNextText->SetFontSize(GetParseState()->m_baseFontSize);
break;
case P_FONT:
{
GetParseState()->m_formatTextStack.Push(GetParseState()->m_pNextText);
GetParseState()->m_formatTypeStack.Push(pTag->type);
GetParseState()->m_pNextText = GetParseState()->m_pNextText->CopyEmptyText();
buff = PA_FetchParamValue(pTag, PARAM_SIZE);
if (buff != NULL) {
char *size_str;
PA_LOCK(size_str, char *, buff);
GetParseState()->m_pNextText->SetFontSize(LO_ChangeFontSize(
GetParseState()->m_baseFontSize, size_str ));
PA_UNLOCK(buff);
PA_FREE(buff);
}
buff = PA_FetchParamValue(pTag, PARAM_FACE);
if (buff != NULL)
{
char* str;
PA_LOCK(str, char *, buff);
GetParseState()->m_pNextText->SetFontFace(str);
PA_UNLOCK(buff);
PA_FREE(buff);
}
ED_Color c = edt_FetchParamColor( pTag, PARAM_COLOR );
if( c.IsDefined() ){
GetParseState()->m_pNextText->SetColor( c );
}
}
break;
case P_ANCHOR:
// push the formatting stack in any case so we properly pop
// it in the future.
GetParseState()->m_formatTextStack.Push(GetParseState()->m_pNextText);
GetParseState()->m_formatTypeStack.Push(pTag->type);
GetParseState()->m_pNextText = GetParseState()->m_pNextText->CopyEmptyText();
pStr = edt_FetchParamString(pTag, PARAM_HREF);
if( pStr != NULL ){
// collect extra stuff..
// LTNOTE:
char* pExtra = edt_FetchParamExtras( pTag, anchorHrefParams );
ED_LinkId id = linkManager.Add( pStr, pExtra );
GetParseState()->m_pNextText->SetHREF( id );
linkManager.Free( id ); // reference counted.
XP_FREE( pStr );
}
pStr = edt_FetchParamString(pTag, PARAM_NAME );
if( pStr ){
// We don't catch this case above because by default
// an anchor is a character formatting
if( m_pCreationCursor->FindContainer() == 0 ){
m_pCreationCursor = CEditContainerElement::NewDefaultContainer(
m_pCreationCursor, GetCurrentAlignment() );
}
pElement = new CEditTargetElement(m_pCreationCursor, pTag);
m_pCreationCursor = pElement->GetParent();
XP_FREE( pStr );
}
break;
case P_IMAGE:
case P_NEW_IMAGE:
{
pElement = new CEditImageElement(m_pCreationCursor, pTag,
GetParseState()->m_pNextText->GetHREF());
m_pCreationCursor = pElement->GetParent();
GetParseState()->bLastWasSpace = FALSE;
break;
}
case P_HRULE:
{
pElement = new CEditHorizRuleElement(m_pCreationCursor, pTag);
m_pCreationCursor = pElement->GetParent();
GetParseState()->bLastWasSpace = TRUE;
break;
}
case P_PLAIN_TEXT:
case P_PLAIN_PIECE:
case P_LISTING_TEXT:
case P_PREFORMAT:
pTag->type = P_PREFORMAT;
m_preformatLinePos = 0;
goto ContainerCase;
//
// pass these tags on so the document looks right
//
case P_CENTER:
GetParseState()->m_formatAlignStack.Push(ED_ALIGN_CENTER);
pContainer = m_pCreationCursor->FindContainer();
if( pContainer ){
pContainer->Container()->AlignIfEmpty( ED_ALIGN_CENTER );
}
break;
case P_DIVISION:
{
ED_Alignment eAlign = edt_FetchParamAlignment( pTag, ED_ALIGN_LEFT );
GetParseState()->m_formatAlignStack.Push( eAlign );
pContainer = m_pCreationCursor->FindContainer();
if( pContainer ){
pContainer->Container()->AlignIfEmpty( eAlign );
}
//PA_FREE( pTag );
//return OK_IGNORE;
break;
}
case P_LINEBREAK:
{
pElement = new CEditBreakElement(m_pCreationCursor, pTag);
m_pCreationCursor = pElement->GetParent();
GetParseState()->bLastWasSpace = TRUE;
break;
}
case P_TITLE:
GetParseState()->m_bInTitle = TRUE;
break;
case P_META:
ParseMetaTag( pTag );
break;
// If you add something to this case, also add it to edt_setUnsupported
case P_TEXTAREA:
case P_SELECT:
case P_OPTION:
case P_INPUT:
case P_EMBED:
case P_NOEMBED:
case P_JAVA_APPLET:
case P_FORM:
case P_MAP:
case P_AREA:
case P_KEYGEN:
case P_BASE:
case P_HYPE:
case P_LINK:
case P_SUBDOC:
case P_CELL:
case P_WORDBREAK:
case P_NOBREAK:
case P_COLORMAP:
case P_INDEX:
case P_PARAM:
case P_SPACER:
case P_UNKNOWN:
case P_NOSCRIPT:
case P_CERTIFICATE:
case P_PAYORDER:
#ifndef SUPPORT_MULTICOLUMN
case P_MULTICOLUMN:
#endif
if ( IsDocTypeTag(pTag) ) {
break; // Ignore document's theorey about it's document type.
}
if( !m_pCreationCursor->IsContainer() && m_pCreationCursor->FindContainer() == 0 ){
m_pCreationCursor = CEditContainerElement::NewDefaultContainer(
m_pCreationCursor, GetCurrentAlignment() );
}
m_pCreationCursor = pElement = new CEditIconElement( m_pCreationCursor,
pTag->is_end ?
EDT_ICON_UNSUPPORTED_END_TAG
:
EDT_ICON_UNSUPPORTED_TAG,
pTag );
m_pCreationCursor->Icon()->MorphTag( pTag );
m_pCreationCursor = m_pCreationCursor->GetParent();
break;
case P_TABLE:
pContainer = m_pCreationCursor->FindContainerContainer();
if( pContainer ){
m_pCreationCursor = pContainer;
}
else {
m_pCreationCursor = m_pRoot;
}
m_pCreationCursor = pElement = new CEditTableElement( m_pCreationCursor, pTag, GetCurrentAlignment() );
break;
case P_TABLE_ROW:
{
CEditTableElement* pTable = m_pCreationCursor->GetTable();
if ( ! pTable ){
// We might be in a TD that someone forgot to close.
CEditTableCellElement* pCell = m_pCreationCursor->GetTableCell();
if ( pCell ){
pTable = pCell->GetTable();
}
}
if ( pTable ) {
m_pCreationCursor = pElement = new CEditTableRowElement( pTable, pTag );
}
else {
XP_TRACE(("Ignoring table row. Not in table."));
}
}
break;
case P_TABLE_HEADER:
case P_TABLE_DATA:
{
CEditTableElement* pTable = m_pCreationCursor->GetTable();
if ( ! pTable ){
// We might be in a TD that someone forgot to close.
CEditTableCellElement* pCell = m_pCreationCursor->GetTableCell();
if ( pCell ){
m_pCreationCursor = pCell->GetParent();
pTable = m_pCreationCursor->GetTable();
}
}
if ( ! pTable ) {
XP_TRACE(("Ignoring table cell. Not in table."));
}
else {
CEditTableRowElement* pTableRow = m_pCreationCursor->GetTableRow();
if ( ! pTableRow ){
// They forgot to put in a table row.
// Create one for them.
pTableRow = new CEditTableRowElement();
pTableRow->InsertAsLastChild(pTable);
}
m_pCreationCursor = pElement = new CEditTableCellElement( pTableRow, pTag );
PushParseState();
}
}
break;
case P_CAPTION:
{
CEditTableElement* pTable = m_pCreationCursor->GetTable();
if ( pTable ) {
m_pCreationCursor = pElement = new CEditCaptionElement( pTable, pTag );
PushParseState();
}
else {
XP_TRACE(("Ignoring caption. Not in a table."));
}
}
break;
#ifdef SUPPORT_MULTICOLUMN
case P_MULTICOLUMN:
pContainer = m_pCreationCursor->FindContainerContainer();
if( pContainer ){
m_pCreationCursor = pContainer;
}
else {
m_pCreationCursor = m_pRoot;
}
m_pCreationCursor = pElement = new CEditMultiColumnElement( m_pCreationCursor, pTag );
break;
#endif
// unimplemented Tags
case P_GRID:
case P_GRID_CELL:
case P_NOGRIDS:
case P_MAX:
m_pCreationCursor = pElement = CreateElement( pTag, m_pCreationCursor );
if( BitSet( edt_setSoloTags, m_pCreationCursor->GetType() ) ){
m_pCreationCursor = m_pCreationCursor->GetParent();
}
break;
//
// someones added a new tag. Inspect pTag and look in pa_tags.h
//
default:
XP_ASSERT(FALSE);
break;
// its ok to ignore this.
case P_HTML:
break;
}
}
pTag->edit_element = pElement;
return retVal;
}
void CEditBuffer::ParseBodyTag(PA_Tag *pTag){
// Allow multiple body tags -- don't replace parameter a new one isn't found.
edt_FetchParamString2(pTag, PARAM_BACKGROUND, m_pBackgroundImage);
edt_FetchParamColor2( pTag, PARAM_BGCOLOR, m_colorBackground);
edt_FetchParamColor2( pTag, PARAM_TEXT, m_colorText );
edt_FetchParamColor2( pTag, PARAM_LINK, m_colorLink );
edt_FetchParamColor2( pTag, PARAM_ALINK, m_colorActiveLink );
edt_FetchParamColor2( pTag, PARAM_VLINK, m_colorFollowedLink );
//m_pBody = m_pCreationCursor = pElement = CreateElement( pTag, m_pCreationCursor );
edt_FetchParamExtras2( pTag, bodyParams, m_pBodyExtra );
}
// Relayout timer ... doesn't quite work perfectly.
CRelayoutTimer::CRelayoutTimer(){
m_pBuffer = NULL;
}
void CRelayoutTimer::SetEditBuffer(CEditBuffer* pBuffer){
m_pBuffer = pBuffer;
}
void CRelayoutTimer::OnCallback() {
m_pBuffer->Relayout(m_pStartElement, m_iOffset);
}
void CRelayoutTimer::Flush() {
if (IsTimeoutEnabled() ) {
Cancel();
OnCallback();
}
}
void CRelayoutTimer::Relayout( CEditElement *pStartElement, int iOffset)
{
const uint32 kRelayoutDelay = 50;
XP_Bool bSetValues = TRUE;
XP_Bool bInTable = pStartElement->GetTableIgnoreSubdoc() != NULL;
if ( ! bInTable ) {
Flush();
m_pBuffer->Relayout(pStartElement, iOffset);
return;
}
if ( IsTimeoutEnabled() ) {
Cancel();
if ( pStartElement != m_pStartElement ) {
OnCallback(); // Do saved relayout now.
}
else {
bSetValues = FALSE;
}
}
if ( bSetValues) {
m_pStartElement = pStartElement;
m_iOffset = iOffset;
}
// Put the character into the LO_Element string so that the two data structures are consistent.
if (pStartElement->IsText() ){
CEditTextElement* pTextElement = pStartElement->Text();
LO_TextStruct* pLoText = NULL;
int iLoOffset = 0;
if ( pTextElement->GetLOTextAndOffset(iOffset, FALSE, pLoText, iLoOffset) ) {
if ( pLoText ) {
int32 textLen = pLoText->text_len;
char *pText;
if (textLen <= 0 ){
PA_FREE(pLoText->text);
pLoText->text = (PA_Block) PA_ALLOC(2); // char plus zero terminated.
}
else {
pLoText->text = (PA_Block) PA_REALLOC(pLoText->text, textLen + 2);
}
PA_LOCK(pText, char *, pLoText->text);
int32 bytesToMove = textLen - iLoOffset;
if ( bytesToMove > 0){
XP_MEMMOVE(pText + iLoOffset +1, pText + iLoOffset, bytesToMove);
}
pText[iLoOffset] = pTextElement->GetText()[iOffset];
pText[textLen+1] = '\0';
// XP_TRACE(("pText=%08x %s %d\n", pText, pText, textLen));
PA_UNLOCK(pLoText->text);
textLen++;
pLoText->text_len = (int16) textLen;
}
}
}
SetTimeout(kRelayoutDelay);
}
CAutoSaveTimer::CAutoSaveTimer() {
m_pBuffer = NULL;
m_iMinutes = 0;
}
void CAutoSaveTimer::SetEditBuffer(CEditBuffer* pBuffer){
m_pBuffer = pBuffer;
}
void CAutoSaveTimer::OnCallback() {
m_pBuffer->AutoSaveCallback();
Restart();
}
void CAutoSaveTimer::Restart() {
#ifdef DEBUG_AUTO_SAVE
const int32 kMillisecondsPerMinute = 6000L; //60000L; // * 1000 / 10;
#else
const int32 kMillisecondsPerMinute = 60000L;
#endif
if ( m_iMinutes > 0 ) {
SetTimeout(m_iMinutes * kMillisecondsPerMinute);
}
}
void CAutoSaveTimer::SetPeriod(int32 minutes){
Cancel();
m_iMinutes = minutes;
Restart();
}
int32 CAutoSaveTimer::GetPeriod(){
return m_iMinutes;
}
static int iFileCount = 0;
CEditBuffer::CEditBuffer(MWContext *pContext): m_pContext(pContext),
m_colorText(ED_Color::GetUndefined()),
m_colorBackground(ED_Color::GetUndefined()),
m_colorLink(ED_Color::GetUndefined()),
m_colorFollowedLink(ED_Color::GetUndefined()),
m_colorActiveLink(ED_Color::GetUndefined()),
m_pTitle(0),
m_pBackgroundImage(0),
m_iDesiredX(-1),
m_bSelecting(0),
m_bBlocked(TRUE),
m_hackFontSize(0),
m_lastTopY(0),
m_inScroll(FALSE),
m_bNoRelayout(FALSE),
m_bDirty(0),
m_preformatLinePos(0),
m_bInPreformat(FALSE),
m_iCurrentOffset(0),
m_pLoadingImage(0),
m_pSaveObject(0),
m_pCurrent(0),
m_bCurrentStickyAfter(FALSE),
m_pCreationCursor(0),
m_pTypingCommand(0),
m_bLayoutBackpointersDirty(TRUE),
m_pBodyExtra(0),
m_iFileWriteTime(0),
m_bDisplayTables(TRUE),
m_bDummyCharacterAddedDuringLoad(FALSE),
m_status(ED_ERROR_NONE)
{
// create a root element
edt_InitBitArrays();
m_pCreationCursor = m_pRoot = new CEditRootDocElement( this );
ResetParseStateStack();
#ifdef DEBUG
m_pTestManager = new CEditTestManager(this);
m_bSuppressPhantomInsertPointCheck = FALSE;
m_bSkipValidation = FALSE;
#endif
m_relayoutTimer.SetEditBuffer(this);
m_autoSaveTimer.SetEditBuffer(this);
#ifdef DEBUG_AUTO_SAVE
m_autoSaveTimer.SetPeriod(1);
#endif
}
CEditBuffer::~CEditBuffer(){
#ifdef DEBUG
delete m_pTestManager;
#endif
delete m_pRoot;
m_pRoot = NULL;
// Delete meta data
for(intn i = 0; i < m_metaData.Size(); i++ ) {
FreeMetaData(m_metaData[i]);
}
ResetParseStateStack();
}
void CEditBuffer::FixupInsertPoint(){
CEditInsertPoint ip(m_pCurrent, m_iCurrentOffset, m_bCurrentStickyAfter);
FixupInsertPoint(ip);
m_pCurrent = ip.m_pElement;
m_iCurrentOffset = ip.m_iPos;
m_bCurrentStickyAfter = ip.m_bStickyAfter;
XP_ASSERT(m_bCurrentStickyAfter == TRUE || m_bCurrentStickyAfter == FALSE);
}
void CEditBuffer::FixupInsertPoint(CEditInsertPoint& ip){
if( ip.m_iPos == 0 && ! IsPhantomInsertPoint(ip) ){
CEditLeafElement *pPrev = ip.m_pElement->PreviousLeafInContainer();
if( pPrev && pPrev->Leaf()->GetLen() != 0 ){
ip.m_pElement = pPrev;
ip.m_iPos = pPrev->Leaf()->GetLen();
}
}
// Since we fake up spaces at the beginning of paragraph, we might get
// an offset of 1 (after the fake space).
else if( ip.m_pElement->Leaf()->GetLen() == 0 ){
ip.m_iPos = 0;
}
}
void CEditBuffer::SetInsertPoint( CEditLeafElement* pElement, int iOffset, XP_Bool bStickyAfter ){
LO_StartSelectionFromElement( m_pContext, 0, 0);
m_pCurrent = pElement;
m_iCurrentOffset = iOffset;
m_bCurrentStickyAfter = bStickyAfter;
XP_ASSERT(m_bCurrentStickyAfter == TRUE || m_bCurrentStickyAfter == FALSE);
SetCaret();
}
//
// Returns true if the insert point doesn't really exist.
//
XP_Bool CEditBuffer::IsPhantomInsertPoint(){
if( !IsSelected() ) {
CEditInsertPoint ip(m_pCurrent, m_iCurrentOffset, m_bCurrentStickyAfter);
return IsPhantomInsertPoint(ip);
}
else {
return FALSE;
}
}
XP_Bool CEditBuffer::IsPhantomInsertPoint(CEditInsertPoint& ip){
if( ip.m_pElement
&& ip.m_pElement->IsA( P_TEXT )
&& ip.m_pElement->Text()->GetLen() == 0
&& !( ip.m_pElement->IsFirstInContainer()
&& ip.m_pElement->LeafInContainerAfter() == 0 )
){
return TRUE;
}
else {
return FALSE;
}
}
//
// Force the insertpoint to be a real insert point so we can do something.
//
void CEditBuffer::ClearPhantomInsertPoint(){
if( IsPhantomInsertPoint() ){
CEditLeafElement *pPrev = m_pCurrent->PreviousLeafInContainer();
if( pPrev ){
m_pCurrent = pPrev;
m_iCurrentOffset = m_pCurrent->Leaf()->GetLen();
m_bCurrentStickyAfter = FALSE;
}
else {
CEditLeafElement *pNext = m_pCurrent->LeafInContainerAfter();
if( pNext ){
m_pCurrent = pNext;
m_iCurrentOffset = 0;
m_bCurrentStickyAfter = FALSE;
}
else {
XP_ASSERT(FALSE);
}
}
Reduce( m_pCurrent->FindContainer() );
}
}
CEditElement* CEditBuffer::FindRelayoutStart( CEditElement *pStartElement ){
CEditElement* pOldElement = NULL;
while ( pStartElement && pStartElement != pOldElement ) {
pOldElement = pStartElement;
CEditElement* pTable = pStartElement->GetTopmostTableOrMultiColumn();
if ( m_bDisplayTables && pTable ) {
// If this is in a table, skip before it.
pStartElement = pTable->PreviousLeaf();
}
else if( !pStartElement->IsLeaf() ){
pStartElement = pStartElement->PreviousLeaf();
}
else if( pStartElement->Leaf()->GetLayoutElement() == 0 ){
pStartElement = pStartElement->PreviousLeaf();
}
}
return pStartElement;
}
//
// Relayout.
//
void CEditBuffer::Relayout( CEditElement* pStartElement,
int iEditOffset,
CEditElement *pEndElement,
intn relayoutFlags ){
CEditElement *pEdStart, *pNewStartElement;
LO_Element *pLayoutElement;
LO_Element *pLoStartLine;
int iOffset;
int32 iLineNum;
if( m_bNoRelayout ){
return;
}
#if 0
if ( pStartTopmostTable || pEndTopmostTable ) {
// We don't know how to handle incremental table layout.
// So we punt and redraw the whole table.
pStartElement = m_pRoot; // hack
iEditOffset = 0;
pEndElement = m_pRoot->GetLastMostChild();
}
#endif
CEditLeafElement *pBegin, *pEnd;
ElementOffset iBeginPos, iEndPos;
XP_Bool bFromStart;
XP_Bool bWasSelected = IsSelected();
if( bWasSelected ){
GetSelection( pBegin, iBeginPos, pEnd, iEndPos, bFromStart );
LO_StartSelectionFromElement( m_pContext, 0, 0);
}
pNewStartElement = FindRelayoutStart(pStartElement);
if( pNewStartElement && pNewStartElement != pStartElement ){
// we had to back up some. Layout until we pass this point.
if( pEndElement == 0 ){
pEndElement = pStartElement;
}
pStartElement = pNewStartElement;
iEditOffset = pStartElement->Leaf()->GetLen();
}
// If the end is in a table, move it outside of the table.
if ( pEndElement ) {
CEditElement* pTable = pEndElement->GetTopmostTableOrMultiColumn();
if ( m_bDisplayTables && pTable) {
// If this is in a table, skip after it.
pEndElement = pTable->GetLastMostChild()->NextLeaf();
}
}
// laying out from the beginning of the document
if( pNewStartElement == 0 ){
if( pEndElement == 0 ){
pEndElement = pStartElement;
}
iLineNum = 0;
pEdStart = m_pRoot;
iOffset = 0;
}
else {
//
// normal case
//
if( pStartElement->IsA(P_TEXT)){
pLayoutElement = (LO_Element*)pStartElement->Text()->GetLOText( iEditOffset );
}
else {
pLayoutElement = pStartElement->Leaf()->GetLayoutElement();
}
if( pLayoutElement == 0 ){
// we are fucked! try something different.
XP_TRACE(("Yellow Alert! Can't resync, plan B"));
Relayout( pStartElement->FindContainer(), 0, pEndElement ?
pEndElement : pStartElement, relayoutFlags );
return;
}
//
// Find the first element on this line.
pLoStartLine = LO_FirstElementOnLine( m_pContext, pLayoutElement->lo_any.x,
pLayoutElement->lo_any.y, &iLineNum );
//
// Position the tag cursor at this position
//
pEdStart = pLoStartLine->lo_any.edit_element;
iOffset = pLoStartLine->lo_any.edit_offset;
}
// Create a new cursor.
CEditTagCursor cursor(this, pEdStart, iOffset, pEndElement);
//CEditTagCursor *pCursor = new CEditTagCursor(this, m_pRoot, iOffset);
LO_Relayout(m_pContext, &cursor, iLineNum, iOffset, m_bDisplayTables);
// Search for zero-length text elements, and remove the non-breaking spaces we put
// in.
CEditElement* pElement= m_pRoot;
while ( NULL != (pElement = pElement->FindNextElement(&CEditElement::FindLeafAll,0))){
switch ( pElement->GetElementType() ) {
case eTextElement:
{
CEditTextElement* pText = pElement->Text();
intn iLen = pText->GetLen();
if ( iLen == 0 ) {
LO_Element* pTextStruct = pText->GetLayoutElement();
if ( pTextStruct && pTextStruct->type == LO_TEXT
&& pTextStruct->lo_text.text_len == 1
&& pTextStruct->lo_text.text ) {
XP_ASSERT( pElement->Leaf()->PreviousLeafInContainer() == 0 );
XP_ASSERT( pElement->Leaf()->LeafInContainerAfter() == 0 );
// Need to strip the allocated text, because lo_bump_position cares.
pTextStruct->lo_text.text_len = 0;
PA_FREE(pTextStruct->lo_text.text);
pTextStruct->lo_text.text = NULL;
}
}
else {
// Layout stripped out all the spaces that crossed soft line breaks.
// Put them back in so they can be selected.
LO_Element* pTextStruct = pText->GetLayoutElement();
while ( pTextStruct && pTextStruct->type == LO_TEXT
&& pTextStruct->lo_any.edit_element == pText ) {
intn iOffset = pTextStruct->lo_any.edit_offset + pTextStruct->lo_text.text_len;
if ( iOffset >= iLen )
break;
LO_Element* pNext = pTextStruct->lo_any.next;
while ( pNext && pNext->type != LO_TEXT) {
pNext = pNext->lo_any.next;
}
intn bytesToCopy = iLen - iOffset;
if ( pNext && pNext->lo_any.edit_element == pText ){
bytesToCopy = pNext->lo_text.edit_offset - iOffset;
}
if ( bytesToCopy > 0 ) {
// Layout has stripped some characters.
// (Probably just a single space character.)
// Put them back.
int16 length = pTextStruct->lo_text.text_len;
int16 newLength = (int16) (length + bytesToCopy + 1); // +1 for '\0'
PA_Block newData;
if (pTextStruct->lo_text.text) {
newData = (PA_Block) PA_REALLOC(pTextStruct->lo_text.text, newLength);
}
else {
newData = (PA_Block) PA_ALLOC(newLength);
}
if ( ! newData ) {
XP_ASSERT(FALSE); /* Out of memory. Not well tested. */
break;
}
pTextStruct->lo_text.text = newData;
pTextStruct->lo_text.text_len = (int16) (newLength - 1);
char *locked_buff;
PA_LOCK(locked_buff, char *, pTextStruct->lo_text.text);
if ( locked_buff ) {
char* source = pText->GetText();
for ( intn i = 0; i < bytesToCopy; i++ ){
locked_buff[length + i] = source[iOffset+ i];
}
locked_buff[length + bytesToCopy] = '\0';
}
PA_UNLOCK(pNext->lo_text.text);
// The string is now wider. We must adjust the width.
LO_TextInfo text_info;
FE_GetTextInfo(m_pContext, &pTextStruct->lo_text, &text_info);
int32 delta = text_info.max_width - pTextStruct->lo_text.width;
pTextStruct->lo_text.width = text_info.max_width;
// Shrink the linefeed
LO_Element* pNext = pTextStruct->lo_any.next;
if ( pNext && pNext->type == LO_LINEFEED ) {
if ( pNext->lo_linefeed.width > delta ) {
pNext->lo_linefeed.width -= delta;
pNext->lo_linefeed.x += delta;
}
}
}
pTextStruct = pNext;
}
}
}
break;
case eBreakElement:
{
CEditBreakElement* pBreak = pElement->Break();
LO_Element* pBreakStruct = pBreak->GetLayoutElement();
if ( pBreakStruct && pBreakStruct->type == LO_LINEFEED) {
// Need to strip the allocated text, because lo_bump_position cares.
pBreakStruct->lo_linefeed.break_type = LO_LINEFEED_BREAK_HARD;
pBreakStruct->lo_linefeed.edit_element = pElement;
pBreakStruct->lo_linefeed.edit_offset = 0;
}
}
break;
case eHorizRuleElement:
{
// The linefeeds after hrules need to be widened.
// They are zero pixels wide by default.
CEditHorizRuleElement* pHorizRule = (CEditHorizRuleElement*) pElement;
LO_Element* pHRuleElement = pHorizRule->GetLayoutElement();
if ( pHRuleElement ) {
LO_Element* pNext = pHRuleElement->lo_any.next;
if ( pNext && pNext->type == LO_LINEFEED) {
const int32 kMinimumWidth = 7;
if (pNext->lo_linefeed.width < kMinimumWidth ) {
pNext->lo_linefeed.width = kMinimumWidth;
}
}
}
}
break;
default:
break;
}
// Set end-of-paragraph marks
if ( pElement->GetNextSibling() == 0 ){
// We're the last leaf in the container.
CEditLeafElement* pLeaf = pElement->Leaf();
LO_Element* pLastElement;
int iOffset;
if ( pLeaf->GetLOElementAndOffset(pLeaf->GetLen(), TRUE,
pLastElement, iOffset) ){
LO_Element* pNextElement = pLastElement ? pLastElement->lo_any.next : 0;
if ( pNextElement && pNextElement->type == LO_LINEFEED ) {
pNextElement->lo_linefeed.break_type = LO_LINEFEED_BREAK_PARAGRAPH;
}
}
else {
// Last leaf, but we're empty. Yay. Try the previous leaf.
CEditElement* pPrevious = pElement->GetPreviousSibling();
if ( pPrevious ) {
CEditLeafElement* pLeaf = pPrevious->Leaf();
LO_Element* pLastElement;
int iOffset;
if ( pLeaf->GetLOElementAndOffset(pLeaf->GetLen(), TRUE,
pLastElement, iOffset) ){
LO_Element* pNextElement = pLastElement ? pLastElement->lo_any.next : 0;
if ( pNextElement && pNextElement->type == LO_LINEFEED ) {
pNextElement->lo_linefeed.break_type = LO_LINEFEED_BREAK_PARAGRAPH;
}
}
}
}
}
}
if( (relayoutFlags & RELAYOUT_NOCARET) == 0 && !bWasSelected){
SetCaret();
}
if( bWasSelected ){
SelectRegion(pBegin, iBeginPos, pEnd, iEndPos, bFromStart );
}
m_bLayoutBackpointersDirty = FALSE;
}
//
// Insert a character at the current edit point
//
EDT_ClipboardResult CEditBuffer::InsertChar( int newChar, XP_Bool bTyping ){
VALIDATE_TREE(this);
EDT_ClipboardResult result = EDT_COP_OK;
int iChangeOffset = m_iCurrentOffset;
#ifdef DEBUG
if ( bTyping && m_pTestManager->Key((char) newChar) )
return EDT_COP_OK;
#endif
CTypingCommand* cmd = NULL;
if ( bTyping ) {
cmd = GetTypingCommand();
}
if( IsSelected() ){
if ( bTyping ) {
result = TypingDeleteSelection(cmd);
}
else {
result = DeleteSelection();
}
if ( result != EDT_COP_OK ) return result;
}
//ClearSelection();
SetDirtyFlag( TRUE );
ClearMove(FALSE);
if(!IsPhantomInsertPoint() ){
FixupInsertPoint();
}
if( m_pCurrent->IsA( P_TEXT )){
// Check for space after space case.
if ( newChar == ' ' && !m_pCurrent->InFormattedText() ) {
CEditInsertPoint p(m_pCurrent, m_iCurrentOffset);
// move to the right on spacebar if we are under a space.
if( p.IsSpace() ){
NextChar( FALSE );
return result;
}
if ( p.IsSpaceBeforeOrAfter() ) {
return result;
}
}
if ( bTyping ) {
cmd->AddCharStart();
}
//
// The edit element can choose not to insert a character if it is
// a space and at the insertion point there already is a space.
//
if( m_pCurrent->Text()->InsertChar( m_iCurrentOffset, newChar ) ){
// move past the inserted character
m_iCurrentOffset++;
if ( bTyping ) {
cmd->AddCharEnd();
}
// Reduce or die
Reduce(m_pCurrent->FindContainer());
// relay out the stream.
if ( bTyping ) {
m_relayoutTimer.Relayout(m_pCurrent, iChangeOffset);
}
else {
Relayout(m_pCurrent, iChangeOffset);
}
}
}
else {
Reduce( m_pCurrent->FindContainer());
if( m_iCurrentOffset == 0 ){
// insert before leaf case.
CEditElement *pPrev = m_pCurrent->GetPreviousSibling();
if( pPrev == 0 || !pPrev->IsA( P_TEXT ) ){
pPrev = new CEditTextElement((CEditElement*)0,0);
pPrev->InsertBefore( m_pCurrent );
}
m_pCurrent = pPrev->Leaf();
m_iCurrentOffset = m_pCurrent->Text()->GetLen();
}
else {
XP_ASSERT( m_iCurrentOffset == 1 );
// insert after leaf case
CEditLeafElement *pNext = (CEditLeafElement*) m_pCurrent->GetNextSibling();
if( pNext == 0 || !pNext->IsA( P_TEXT ) ){
CEditTextElement* pPrev = m_pCurrent->PreviousTextInContainer();
if( pPrev ){
pNext = pPrev->CopyEmptyText();
}
else {
pNext = new CEditTextElement((CEditElement*)0,0);
}
pNext->InsertAfter( m_pCurrent );
}
m_pCurrent = pNext;
m_iCurrentOffset = 0;
}
// now we have a text. Do the actual insert.
if ( bTyping ) {
cmd->AddCharStart();
}
if( m_pCurrent->Text()->InsertChar( m_iCurrentOffset, newChar ) ){
m_iCurrentOffset++;
if ( bTyping ) {
cmd->AddCharEnd();
}
if ( bTyping ) {
m_relayoutTimer.Relayout(m_pCurrent, iChangeOffset);
}
else {
Relayout(m_pCurrent, iChangeOffset);
}
}
}
return result;
}
EDT_ClipboardResult CEditBuffer::DeletePreviousChar(){
#ifdef DEBUG
if ( m_pTestManager->Backspace() )
return EDT_COP_OK;
#endif
return DeleteChar(FALSE);
}
EDT_ClipboardResult CEditBuffer::DeleteChar(XP_Bool bForward, XP_Bool bTyping){
VALIDATE_TREE(this);
ClearPhantomInsertPoint();
ClearMove();
CTypingCommand* cmd = bTyping ? GetTypingCommand() : NULL;
EDT_ClipboardResult result = EDT_COP_OK;
if( IsSelected() ){
result = TypingDeleteSelection(cmd);
}
else {
CEditSelection selection;
GetSelection(selection);
if ( Move(*selection.GetEdge(bForward), bForward ) ) {
result = CanCut(selection, TRUE);
if ( result == EDT_COP_OK ) {
if ( bTyping ) {
cmd->DeleteStart(selection);
}
DeleteSelection(selection, FALSE);
if ( bTyping ) {
cmd->DeleteEnd();
}
}
}
}
return result;
}
EDT_ClipboardResult CEditBuffer::TypingDeleteSelection(CTypingCommand* cmd){
CEditSelection selection;
GetSelection(selection);
selection.ExcludeLastDocumentContainerEnd();
cmd->DeleteStart(selection);
EDT_ClipboardResult result = DeleteSelection();
cmd->DeleteEnd();
return result;
}
EDT_ClipboardResult CEditBuffer::DeleteNextChar(){
return DeleteChar(TRUE);
}
XP_Bool CEditBuffer::Move(CEditInsertPoint& pt, XP_Bool forward) {
CEditLeafElement* dummyNewElement = pt.m_pElement;
ElementOffset dummyNewOffset = pt.m_iPos;
XP_Bool result = FALSE;
if ( forward )
result = NextPosition(dummyNewElement, dummyNewOffset,
dummyNewElement, dummyNewOffset);
else
result = PrevPosition(dummyNewElement, dummyNewOffset,
dummyNewElement, dummyNewOffset);
if ( result ) {
pt.m_pElement = dummyNewElement;
pt.m_iPos = dummyNewOffset;
}
return result;
}
XP_Bool CEditBuffer::CanMove(CEditInsertPoint& pt, XP_Bool forward) {
CEditInsertPoint test(pt);
return Move(test, forward);
}
XP_Bool CEditBuffer::CanMove(CPersistentEditInsertPoint& pt, XP_Bool forward) {
CPersistentEditInsertPoint test(pt);
return Move(test, forward);
}
XP_Bool CEditBuffer::Move(CPersistentEditInsertPoint& pt, XP_Bool forward) {
CEditInsertPoint insertPoint = PersistentToEphemeral(pt);
XP_Bool result = Move(insertPoint, forward);
if ( result ) {
pt = EphemeralToPersistent(insertPoint);
}
return result;
}
void CEditBuffer::SelectNextChar( ){
CEditLeafElement *pBegin, *pEnd;
ElementOffset iBeginPos, iEndPos;
XP_Bool bFromStart;
XP_Bool bFound;
if( IsSelected() ){
GetSelection( pBegin, iBeginPos, pEnd, iEndPos, bFromStart );
if( bFromStart ){
bFound = NextPosition( pBegin, iBeginPos, pBegin, iBeginPos );
}
else {
bFound = NextPosition( pEnd, iEndPos, pEnd, iEndPos );
}
if( bFound ){
SelectRegion( pBegin, iBeginPos, pEnd, iEndPos, bFromStart, TRUE );
}
}
else {
BeginSelection( TRUE, FALSE );
}
}
void CEditBuffer::SelectPreviousChar( ){
if( IsSelected() ){
CEditLeafElement *pBegin, *pEnd;
ElementOffset iBeginPos, iEndPos;
XP_Bool bFound = FALSE;
XP_Bool bFromStart;
GetSelection( pBegin, iBeginPos, pEnd, iEndPos, bFromStart );
if( bFromStart ){
bFound = PrevPosition( pBegin, iBeginPos, pBegin, iBeginPos );
}
else {
bFound = PrevPosition( pEnd, iEndPos, pEnd, iEndPos );
}
if( bFound ){
SelectRegion( pBegin, iBeginPos, pEnd, iEndPos, bFromStart, FALSE );
}
}
else {
BeginSelection( TRUE, TRUE );
}
}
XP_Bool CEditBuffer::PrevPosition(CEditLeafElement *pEle, ElementOffset iOffset,
CEditLeafElement*& pNew, ElementOffset& iNewOffset ){
LO_Element* pElement;
int iLayoutOffset;
int iNewLayoutOffset;
CEditElement* pNewEditElement;
if( pEle->GetLOElementAndOffset( iOffset, FALSE, pElement, iLayoutOffset ) ){
XP_Bool result = LO_PreviousPosition( m_pContext, pElement, iLayoutOffset, &pNewEditElement, &iNewLayoutOffset );
if ( result ){
pNew = pNewEditElement->Leaf();
iNewOffset = iNewLayoutOffset;
}
return result;
}
else {
return pEle->PrevPosition( iOffset, pNew, iNewOffset );
}
}
XP_Bool CEditBuffer::NextPosition(CEditLeafElement *pEle, ElementOffset iOffset,
CEditLeafElement*& pNew, ElementOffset& iNewOffset ){
LO_Element* pElement;
int iLayoutOffset;
int iNewLayoutOffset;
CEditElement* pNewEditElement;
if( pEle->Leaf()->GetLOElementAndOffset( iOffset, FALSE, pElement, iLayoutOffset )){
XP_Bool result = LO_NextPosition( m_pContext, pElement, iLayoutOffset, &pNewEditElement, &iNewLayoutOffset );
// The insert point can't be the end of the document.
if ( result && (
! pNewEditElement || pNewEditElement->IsEndOfDocument() ) ) {
result = FALSE;
}
if ( result ){
pNew = pNewEditElement->Leaf();
iNewOffset = iNewLayoutOffset;
}
return result;
}
else {
pEle->Leaf()->NextPosition( iOffset, pNew, iNewOffset );
return TRUE;
}
}
void CEditBuffer::NextChar( XP_Bool bSelect ){
VALIDATE_TREE(this);
int iSavePos = m_iCurrentOffset;
if( bSelect ){
SelectNextChar();
return;
}
ClearPhantomInsertPoint();
ClearMove();
ClearSelection();
//FixupInsertPoint();
NextPosition( m_pCurrent, m_iCurrentOffset,
m_pCurrent, m_iCurrentOffset );
SetCaret();
}
XP_Bool CEditBuffer::PreviousChar( XP_Bool bSelect ){
VALIDATE_TREE(this);
if( bSelect ){
SelectPreviousChar();
return FALSE; // no return value on select previous..
}
ClearPhantomInsertPoint();
ClearMove();
ClearSelection();
//FixupInsertPoint();
if( PrevPosition( m_pCurrent, m_iCurrentOffset,
m_pCurrent, m_iCurrentOffset )){
SetCaret();
return TRUE;
}
else {
return FALSE;
}
}
//
// Find out our current layout position and move up or down from here.
//
void CEditBuffer::UpDown( XP_Bool bSelect, XP_Bool bForward ){
VALIDATE_TREE(this);
CEditLeafElement *pEle;
ElementOffset iOffset;
XP_Bool bStickyAfter;
LO_Element* pElement;
int iLayoutOffset;
DoneTyping();
BeginSelection();
if( bSelect ){
GetInsertPoint( &pEle, &iOffset, &bStickyAfter );
}
else {
ClearSelection();
ClearPhantomInsertPoint();
pEle = m_pCurrent->Leaf();
iOffset = m_iCurrentOffset;
bStickyAfter = m_bCurrentStickyAfter;
}
XP_ASSERT(!pEle->IsEndOfDocument());
pEle->GetLOElementAndOffset( iOffset, bStickyAfter, pElement, iLayoutOffset );
LO_UpDown( m_pContext, pElement, iLayoutOffset, GetDesiredX(pEle, iOffset, bStickyAfter), bSelect, bForward );
EndSelection();
}
int32
CEditBuffer::GetDesiredX(CEditElement* pEle, intn iOffset, XP_Bool bStickyAfter)
{
XP_ASSERT(bStickyAfter == TRUE || bStickyAfter == FALSE);
// m_iDesiredX is where we would move if we could. This keeps the
// cursor moving, basically, straight up and down, even if there is
// no text or gaps
if( m_iDesiredX == -1 ){
LO_Element* pElement;
int iLayoutOffset;
pEle->Leaf()->GetLOElementAndOffset( iOffset, bStickyAfter, pElement, iLayoutOffset );
int32 x;
int32 y;
int32 width;
int32 height;
LO_GetEffectiveCoordinates(m_pContext, pElement, iLayoutOffset, &x, &y, &width, &height);
m_iDesiredX = x;
}
return m_iDesiredX;
}
void CEditBuffer::NavigateChunk( XP_Bool bSelect, intn chunkType, XP_Bool bForward ){
if ( chunkType == EDT_NA_UPDOWN ){
UpDown(bSelect, bForward);
}
else {
VALIDATE_TREE(this);
ClearPhantomInsertPoint();
ClearMove(); /* Arrow keys clear the up/down target position */
BeginSelection();
LO_NavigateChunk( m_pContext, chunkType, bSelect, bForward );
EndSelection();
DoneTyping();
}
}
//
// Break current container into two containers.
//
EDT_ClipboardResult CEditBuffer::ReturnKey(XP_Bool bTyping){
EDT_ClipboardResult result = EDT_COP_OK;
if ( bTyping ) {
VALIDATE_TREE(this);
#ifdef DEBUG
if ( m_pTestManager->ReturnKey() )
return result;
#endif
CTypingCommand* cmd = GetTypingCommand();
if( IsSelected() ){
result = TypingDeleteSelection(cmd);
if ( result != EDT_COP_OK ) return result;
}
cmd->AddCharStart();
result = InternalReturnKey(TRUE);
cmd->AddCharEnd();
}
else {
result = InternalReturnKey(TRUE);
}
return result;
}
EDT_ClipboardResult CEditBuffer::InternalReturnKey(XP_Bool bUserTyped){
EDT_ClipboardResult result = EDT_COP_OK;
SetDirtyFlag( TRUE );
if( IsSelected() ){
result = DeleteSelection();
if ( result != EDT_COP_OK ) return result;
}
// ClearSelection();
ClearPhantomInsertPoint();
FixupInsertPoint();
CEditElement *pChangedElement = m_pCurrent;
CEditLeafElement *pNew,*pSplitAfter;
CEditLeafElement *pCopyFormat;
int iChangedOffset = m_iCurrentOffset;
pSplitAfter = m_pCurrent;
pCopyFormat = m_pCurrent->Leaf();
if( m_iCurrentOffset == 0 ){
// split at beginning of element
if( m_pCurrent->PreviousLeafInContainer() == 0){
CEditTextElement *pNew;
pNew = m_pCurrent->Leaf()->CopyEmptyText();
pNew->InsertBefore( m_pCurrent );
pChangedElement = pNew->FindContainer();
pSplitAfter = pNew;
}
}
else {
pNew = m_pCurrent->Divide( m_iCurrentOffset )->Leaf();
/* Get rid of space at start of new line if user typed. */
if( bUserTyped && !pNew->InFormattedText()
&& pNew->IsA(P_TEXT)
&& pNew->Text()->GetLen()
&& pNew->Text()->GetText()[0] == ' ' ){
GetTypingCommand()->AddCharDeleteSpace();
pNew->Text()->DeleteChar(m_pContext, 0);
CEditLeafElement *pNext;
if( pNew->Text()->GetLen() == 0
&& (pNext = pNew->TextInContainerAfter()) != 0 ){
pNew->Unlink();
// Should we delete pNew?
delete pNew;
pNew = pNext;
}
}
m_pCurrent = pNew;
}
m_iCurrentOffset = 0;
m_pCurrent->GetParent()->Split( pSplitAfter, 0,
&CEditElement::SplitContainerTest, 0 );
if ( bUserTyped && m_pCurrent->GetLen() == 0 && m_pCurrent->TextInContainerAfter() == 0 ){
// An empty paragraph. If the style is one of the
// heading styles, set it back to normal
CEditContainerElement* pContainer = m_pCurrent->FindContainer();
TagType tagType = pContainer->GetType();
// To do - use one of those fancy bit arrays.
if ( tagType >= P_HEADER_1 && tagType <= P_HEADER_6){
pContainer->SetType(P_PARAGRAPH);
}
}
if ( bUserTyped ) {
Reduce(m_pRoot); // Or maybe just the two containers?
}
Relayout(pChangedElement, iChangedOffset,
(pChangedElement != m_pCurrent ? m_pCurrent : 0 ));
#if 0
// The bug is that after a return is pressed, the formatting is wrong
// for the new paragrah. Presents a problem. If we insert an empty
// text element here, we get messed up because we may not be able to
// position the caret. We end up reducing and loosing a pointer to
// the layout element. It is a hard problem.
CEditTextElement *pNewText = pCopyFormat->CopyEmptyText();
pNewText->InsertBefore(m_pCurrent);
m_pCurrent = pNewText;
m_iCurrentOffset = 0;
#endif
return result;
}
void CEditBuffer::Indent(){
VALIDATE_TREE(this);
SetDirtyFlag( TRUE );
if( IsSelected() ){
CEditLeafElement *pBegin, *pEnd, *pCurrent;
ElementOffset iBeginPos, iEndPos;
XP_Bool bFromStart;
GetSelection( pBegin, iBeginPos, pEnd, iEndPos, bFromStart );
pCurrent = pBegin;
XP_Bool bDone = FALSE;
CEditContainerElement* pLastContainer = 0;
CEditContainerElement* pContainer = 0;
CEditListElement* pList;
do {
pCurrent->FindList( pContainer, pList );
if( pContainer != pLastContainer ){
IndentContainer( pContainer, pList );
pLastContainer = pContainer;
}
bDone = (pEnd == pCurrent ); // For most cases
pCurrent = pCurrent->NextLeafAll();
bDone = bDone || (iEndPos == 0 && pEnd == pCurrent ); // Pesky edge conditions!
} while( pCurrent && !bDone );
// force layout stop displaying the current selection.
LO_StartSelectionFromElement( m_pContext, 0, 0);
// probably needs to be the common ancestor.
Relayout( pBegin->FindContainer(), 0, pEnd, RELAYOUT_NOCARET );
// Need to force selection.
SelectRegion(pBegin, iBeginPos, pEnd, iEndPos, bFromStart );
return;
}
//
// find where cont-cont has cont
//
CEditContainerElement* pContainer;
CEditListElement* pList;
m_pCurrent->FindList( pContainer, pList );
IndentContainer( pContainer, pList );
Relayout( pContainer, 0, pContainer->GetLastMostChild() );
}
void CEditBuffer::IndentContainer( CEditContainerElement *pContainer,
CEditListElement *pList ){
VALIDATE_TREE(this);
CEditElement *pPrev = pContainer->GetPreviousSibling();
CEditElement *pNext = pContainer->GetNextSibling();
//
// case 1
//
// UL:
// LI:
// LI: <- Indenting this guy
// LI:
// LI:
//
// should result in
//
// UL:
// LI:
// LI: <-end up here
// LI:
// LI:
//
//
// We also need to handle the case
//
// UL:
// LI:
// LI: <- indent this guy
// UL:
// LI:
// LI:
//
// and the second UL becomes redundant so we eliminate it.
//
// UL:
// LI:
// LI: <-Ends up here
// LI: <-frm the redundant container
// LI:
//
if( pPrev && pPrev->IsList() ){
CEditElement *pChild = pPrev->GetLastChild();
if( pChild ){
pContainer->Unlink();
pContainer->InsertAfter( pChild );
}
else {
pContainer->Unlink();
pContainer->InsertAsFirstChild( pPrev );
}
if( pNext && pNext->IsList() ){
pPrev->Merge( pNext );
}
}
//
// case 0
//
// LI: <- Indenting this guy
//
// should result in
//
// UL:
// LI:
//
//
else if( pList == 0 ){
PA_Tag *pTag = XP_NEW( PA_Tag );
XP_BZERO( pTag, sizeof( PA_Tag ) );
pTag->type = P_UNUM_LIST;
CEditElement *pEle = new CEditListElement( 0, pTag );
PA_FreeTag( pTag );
pEle->InsertAfter( pContainer );
pContainer->Unlink();
pContainer->InsertAsFirstChild( pEle );
return;
}
//
// case 2
//
// UL:
// LI:
// LI: <- Indenting this guy
// UL:
// LI:
// LI:
// LI:
//
// should result in
//
// UL:
// LI:
// UL:
// LI: <- Ends up here.
// LI:
// LI:
// LI:
//
else if( pNext && pNext->IsList() ){
CEditElement *pChild = pNext->GetChild();
if( pChild ){
pContainer->Unlink();
pContainer->InsertBefore( pChild );
}
else {
pContainer->Unlink();
pContainer->InsertAsFirstChild( pPrev );
}
}
//
// case 3
//
// UL:
// LI:
// LI: <- Indenting this guy
// LI:
// LI:
//
// should result in
//
// UL:
// LI:
// UL:
// LI: <- Ends up here.
// LI:
// LI:
//
else {
CEditElement *pNewList;
pNewList = pList->Clone(0);
// insert the new cont-cont between the old one and the cont
pNewList->InsertBefore( pContainer );
pContainer->Unlink();
pContainer->InsertAsFirstChild( pNewList);
}
}
void CEditBuffer::Outdent(){
VALIDATE_TREE(this);
SetDirtyFlag( TRUE );
if( IsSelected() ){
CEditLeafElement *pBegin, *pEnd, *pCurrent;
ElementOffset iBeginPos, iEndPos;
XP_Bool bFromStart;
GetSelection( pBegin, iBeginPos, pEnd, iEndPos, bFromStart );
pCurrent = pBegin;
XP_Bool bDone = FALSE;
CEditContainerElement* pLastContainer = 0;
CEditContainerElement* pContainer = 0;
CEditListElement* pList;
do {
pCurrent->FindList( pContainer, pList );
if( pContainer != pLastContainer ){
OutdentContainer( pContainer, pList );
pLastContainer = pContainer;
}
bDone = (pEnd == pCurrent ); // For most cases
pCurrent = pCurrent->NextLeafAll();
bDone = bDone || (iEndPos == 0 && pEnd == pCurrent ); // Pesky edge conditions!
} while( pCurrent && !bDone );
// force layout stop displaying the current selection.
LO_StartSelectionFromElement( m_pContext, 0, 0);
// probably needs to be the common ancestor.
Relayout( pBegin->FindContainer(), 0, pEnd, RELAYOUT_NOCARET );
// Need to force selection.
SelectRegion(pBegin, iBeginPos, pEnd, iEndPos, bFromStart );
return;
}
CEditContainerElement* pContainer;
CEditListElement* pList;
m_pCurrent->FindList( pContainer, pList );
OutdentContainer( pContainer, pList );
Relayout( pContainer, 0, pContainer->GetLastMostChild() );
}
void CEditBuffer::OutdentContainer( CEditContainerElement *pContainer,
CEditListElement *pList ){
//
// case 0
//
// LI: <-- outdent this guy.
//
if( pList == 0 ){
return; // no work to do
}
CEditElement *pPrev = pContainer->GetPreviousSibling();
CEditElement *pNext = pContainer->GetNextSibling();
//
// case 1
//
// UL:
// LI: <-Outdenting this guy
//
// should result in
//
// LI:
//
// No previous or next siblings. Just remove the List and
// put its container in its place.
//
if( pPrev == 0 && pNext == 0 ){
pContainer->Unlink();
pContainer->InsertAfter( pList );
pList->Unlink();
delete pList;
}
//
// case 2
//
// UL:
// LI: <-Outdenting this guy
// LI:
//
//
// Results in:
//
// LI: <-Outdenting this guy
// UL:
// LI:
//
else if( pPrev == 0 ){
pContainer->Unlink();
pContainer->InsertBefore( pList );
}
//
// case 3
//
// UL:
// LI:
// LI: <-Outdenting this guy
//
//
// Results in:
//
// UL:
// LI:
// LI: <-Outdenting this guy
//
else if( pNext == 0 ){
pContainer->Unlink();
pContainer->InsertAfter( pList );
}
//
// case 4
//
// UL:
// LI:
// LI: <-Outdenting this guy
// LI:
//
// Results in:
//
// UL:
// LI:
// LI: <-Outdenting this guy
// UL:
// LI:
else {
CEditElement *pNewList = pList->Clone(0);
while( (pNext = pContainer->GetNextSibling()) != 0 ){
pNext->Unlink();
pNext->InsertAsLastChild( pNewList );
}
pContainer->Unlink();
pContainer->InsertAfter( pList );
pNewList->InsertAfter( pContainer );
}
}
ED_ElementType CEditBuffer::GetCurrentElementType(){
CEditLeafElement *pInsertPoint;
ElementOffset iOffset;
XP_Bool bSingleItem;
bSingleItem = GetPropertyPoint( &pInsertPoint, &iOffset );
if( !bSingleItem ){
if( IsSelected() ){
return ED_ELEMENT_SELECTION;
}
else {
return ED_ELEMENT_TEXT;
}
}
else if( pInsertPoint->IsA(P_TEXT) ){
// should never have single item text objects...
XP_ASSERT(FALSE);
return ED_ELEMENT_TEXT;
}
else if( pInsertPoint->IsImage() ){
return ED_ELEMENT_IMAGE;
}
else if( pInsertPoint->IsA(P_HRULE) ){
return ED_ELEMENT_HRULE;
}
else if( pInsertPoint->IsA(P_LINEBREAK )){
return ED_ELEMENT_TEXT;
}
else if( pInsertPoint->GetElementType() == eTargetElement ){
return ED_ELEMENT_TARGET;
}
else if( pInsertPoint->GetElementType() == eIconElement ){
return ED_ELEMENT_UNKNOWN_TAG;
}
// current element type is unknown.
XP_ASSERT( FALSE );
return ED_ELEMENT_TEXT; // so the compiler doesn't complain.
}
void CEditBuffer::MorphContainer( TagType t ){
VALIDATE_TREE(this);
XP_Bool bDone;
// charley says this is happening.. Hmmmm...
//XP_ASSERT( t != P_TEXT );
if( t == P_TEXT ){
// actually failed this assert.
return;
}
SetDirtyFlag( TRUE );
if( IsSelected() ){
CEditLeafElement *pBegin, *pEnd, *pCurrent;
CEditContainerElement* pContainer = NULL;
ElementOffset iBeginPos, iEndPos;
XP_Bool bFromStart;
GetSelection( pBegin, iBeginPos, pEnd, iEndPos, bFromStart );
pCurrent = pBegin;
bDone = FALSE;
do {
pContainer = pCurrent->FindContainer();
pContainer->SetType( t );
bDone = (pEnd == pCurrent ); // For most cases
pCurrent = pCurrent->NextLeafAll();
bDone = bDone || (iEndPos == 0 && pEnd == pCurrent ); // Pesky edge conditions!
} while( pCurrent && !bDone );
// force layout stop displaying the current selection.
LO_StartSelectionFromElement( m_pContext, 0, 0);
// probably needs to be the common ancestor.
Relayout( pBegin->FindContainer(), 0, pEnd, RELAYOUT_NOCARET );
// Need to force selection.
SelectRegion(pBegin, iBeginPos, pEnd, iEndPos, bFromStart );
}
else {
CEditElement *pContainer = m_pCurrent->FindContainer();
// HACKOLA: just poke in a new tag type.
pContainer->SetType( t );
// Need to relayout from previous line. Line break distance is wrong.
Relayout( pContainer, 0, 0 );
}
}
void CEditBuffer::SetParagraphAlign( ED_Alignment eAlign ){
VALIDATE_TREE(this);
XP_Bool bDone;
SetDirtyFlag( TRUE );
if( IsSelected() ){
CEditLeafElement *pBegin, *pEnd, *pCurrent;
CEditContainerElement *pContainer = NULL;
ElementOffset iBeginPos, iEndPos;
XP_Bool bFromStart;
GetSelection( pBegin, iBeginPos, pEnd, iEndPos, bFromStart );
pCurrent = pBegin;
bDone = FALSE;
do {
pContainer = pCurrent->FindContainer();
pContainer->Container()->SetAlignment( eAlign );
bDone = (pEnd == pCurrent ); // For most cases
pCurrent = pCurrent->NextLeafAll();
bDone = bDone || (iEndPos == 0 && pEnd == pCurrent ); // Pesky edge conditions!
} while( pCurrent && !bDone );
// force layout stop displaying the current selection.
LO_StartSelectionFromElement( m_pContext, 0, 0);
// probably needs to be the common ancestor.
Relayout( pBegin->FindContainer(), 0, pEnd, RELAYOUT_NOCARET );
// Need to force selection.
SelectRegion(pBegin, iBeginPos, pEnd, iEndPos, bFromStart );
}
else {
CEditElement *pContainer = m_pCurrent->FindContainer();
// HACKOLA: just poke in a new tag type.
pContainer->Container()->SetAlignment( eAlign );
// Need to relayout from previous line. Line break distance is wrong.
Relayout( pContainer, 0, pContainer->GetLastMostChild() );
}
}
ED_Alignment CEditBuffer::GetParagraphAlign( ){
CEditLeafElement *pInsertPoint;
CEditContainerElement *pCont;
ElementOffset iOffset;
GetPropertyPoint( &pInsertPoint, &iOffset );
if( pInsertPoint && (pCont = pInsertPoint->FindContainer()) != 0){
return pCont->GetAlignment();
}
else {
return ED_ALIGN_DEFAULT;
}
}
void CEditBuffer::SetFontSize( int iSize ){
VALIDATE_TREE(this);
SetDirtyFlag( TRUE );
CEditSelection selection;
GetSelection(selection);
if ( selection.IsContainerEnd() )
return;
else if( selection.AnyLeavesSelected() ){
CChangeAttributesCommand* pCommand = new CChangeAttributesCommand(this);
SetFontSizeSelection( iSize );
AdoptAndDo(pCommand);
return;
}
FixupInsertPoint();
CEditLeafElement *pRight = m_pCurrent->Divide( m_iCurrentOffset )->Leaf();
if( pRight->IsA(P_TEXT)
&& pRight->Text()->GetLen() == 0 ) {
// && m_pCurrent == pRight
// Bug 27891, this term in the conditional test should not be here, e.g.
// in CEditBuffer::SetCharacterData() it is not there.
pRight->Text()->SetFontSize(iSize);
m_pCurrent = pRight;
m_iCurrentOffset = 0;
}
else {
CEditTextElement *pNew = m_pCurrent->Leaf()->CopyEmptyText();
pNew->InsertBefore(pRight);
pNew->SetFontSize( iSize );
m_pCurrent = pNew;
m_iCurrentOffset = 0;
}
Relayout( m_pCurrent, 0, 0 );
}
void CEditBuffer::SetFontColor( ED_Color iColor ){
// ToDo: Eliminate this function in favor of SetCharacterData
VALIDATE_TREE(this);
SetDirtyFlag( TRUE );
CEditSelection selection;
GetSelection(selection);
if ( selection.IsContainerEnd() )
return;
else if( selection.AnyLeavesSelected() ){
CChangeAttributesCommand* pCommand = new CChangeAttributesCommand(this);
SetFontColorSelection( iColor );
AdoptAndDo(pCommand);
return;
}
FixupInsertPoint();
CEditLeafElement *pRight = m_pCurrent->Divide( m_iCurrentOffset )->Leaf();
if( pRight->IsA(P_TEXT)
&& pRight->Leaf()->GetLen() == 0 ) {
// && m_pCurrent == pRight
// Bug 27891, this term in the conditional test should not be here, e.g.
// in CEditBuffer::SetCharacterData() it is not there.
pRight->Text()->SetColor( iColor );
m_pCurrent = pRight;
m_iCurrentOffset = 0;
}
else {
CEditTextElement *pNew = m_pCurrent->Leaf()->CopyEmptyText();
pNew->InsertBefore(pRight);
pNew->SetColor( iColor );
m_pCurrent = pNew;
m_iCurrentOffset = 0;
}
Relayout( m_pCurrent, 0, 0 );
}
void CEditBuffer::SetHREF( char *pHref, char *pExtra ){
VALIDATE_TREE(this);
ED_LinkId id;
ED_LinkId oldId;
CEditElement *pPrev, *pNext, *pStart, *pEnd;
XP_Bool bSame;
pStart = m_pCurrent;
pEnd = 0;
SetDirtyFlag( TRUE );
//
// if we are setting link data and the current selection or word
// has extra data, we should preserve it.
//
if( pHref ){
EDT_CharacterData* pCD = 0;
if( pExtra == 0 ){
pCD = GetCharacterData();
if( pCD
&& (pCD->mask & TF_HREF)
&& (pCD->values & TF_HREF)
&& pCD->pHREFData
&& pCD->pHREFData->pExtra ){
pExtra = pCD->pHREFData->pExtra;
}
}
id = linkManager.Add( pHref, pExtra );
if( pCD ){
EDT_FreeCharacterData( pCD );
}
}
else {
id = ED_LINK_ID_NONE;
}
CEditSelection selection;
GetSelection(selection);
if ( selection.IsContainerEnd() )
return;
else if( selection.AnyLeavesSelected() ){
CChangeAttributesCommand* pCommand = new CChangeAttributesCommand(this);
SetHREFSelection( id );
AdoptAndDo(pCommand);
}
else {
//FixupInsertPoint();
// check all text around us.
oldId = m_pCurrent->Leaf()->GetHREF();
m_pCurrent->Leaf()->SetHREF( id );
pPrev = m_pCurrent->PreviousLeafInContainer();
bSame = TRUE;
while( pPrev && bSame ){
bSame = pPrev->Leaf()->GetHREF() == oldId;
if( bSame ){
pPrev->Leaf()->SetHREF( id );
pStart = pPrev;
}
pPrev = pPrev->PreviousLeafInContainer();
}
pNext = m_pCurrent->LeafInContainerAfter();
bSame = TRUE;
while( pNext && bSame ){
bSame = pNext->Leaf()->GetHREF() == oldId;
if( bSame ){
pNext->Leaf()->SetHREF( id );
pEnd = pNext;
}
pNext = pNext->LeafInContainerAfter();
}
Relayout( pStart, 0, pEnd );
}
if( id != ED_LINK_ID_NONE ){
linkManager.Free(id);
}
}
// Use this only when setting one attribute at a time
// Always make Superscript and Subscript mutually exclusive - very tricky!
void CEditBuffer::FormatCharacter( ED_TextFormat tf ){
VALIDATE_TREE(this);
SetDirtyFlag( TRUE );
CEditSelection selection;
GetSelection(selection);
if ( selection.IsContainerEnd() )
return;
if( selection.AnyLeavesSelected() ){
CChangeAttributesCommand* pCommand = new CChangeAttributesCommand(this);
FormatCharacterSelection(tf);
AdoptAndDo(pCommand);
return;
}
FixupInsertPoint();
CEditLeafElement *pRight = m_pCurrent->Divide( m_iCurrentOffset )->Leaf();
if( pRight->IsA(P_TEXT) && pRight->Leaf()->GetLen() == 0 ){
if( tf == TF_NONE ){
pRight->Text()->ClearFormatting();
}
else if( tf == TF_SERVER || tf == TF_SCRIPT ){
pRight->Text()->ClearFormatting();
pRight->Text()->m_tf = tf;
}
// If we are in either Java Script type, IGNORE change in format
else if( ED_IS_NOT_SCRIPT(pRight) ){
// Make superscript and subscript mutually-exclusive
// if already either one type, and we are setting the opposite,
// clear this bit first
if( tf == TF_SUPER && (pRight->Text()->m_tf & TF_SUB) ){
pRight->Text()->m_tf &= ~TF_SUB;
} else if( tf == TF_SUB && (pRight->Text()->m_tf & TF_SUPER) ){
pRight->Text()->m_tf &= ~TF_SUPER;
}
pRight->Text()->m_tf ^= tf;
}
m_pCurrent = pRight;
m_iCurrentOffset = 0;
}
else {
// We are making new text element, so we don't need
// to worry about existing Java Script or Super/Subscript stuff
CEditTextElement *pNew = m_pCurrent->Leaf()->CopyEmptyText();
pNew->InsertBefore(pRight);
if( tf == TF_NONE ){
pNew->ClearFormatting();
}
else {
if( tf == TF_SUPER && (pNew->Text()->m_tf & TF_SUB) ){
pNew->Text()->m_tf &= ~TF_SUB;
} else if( tf == TF_SUB && (pNew->Text()->m_tf & TF_SUPER) ){
pNew->Text()->m_tf &= ~TF_SUPER;
}
pNew->m_tf ^= tf;
}
m_pCurrent = pNew;
m_iCurrentOffset = 0;
}
Relayout( m_pCurrent, 0, 0 );
}
void CEditBuffer::SetCharacterDataSelection( EDT_CharacterData *pData ){
VALIDATE_TREE(this);
CEditLeafElement *pCurrent, *pEnd, *pBegin, *pNext;
SetDirtyFlag( TRUE );
//
// Guarantee that the text blocks of the beginning and end of selection
// are atomic.
//
MakeSelectionEndPoints( pBegin, pEnd );
pCurrent = pBegin;
while( pCurrent != pEnd ){
pNext = pCurrent->NextLeafAll();
if( pCurrent->IsA( P_TEXT ) ){
pCurrent->Text()->SetData( pData );
}
// Set just the HREF for an image
else if( pCurrent->IsImage() &&
(pData->mask & TF_HREF) ){
pCurrent->Image()->SetHREF( pData->linkId );
}
pCurrent = pNext;
}
// force layout stop displaying the current selection.
LO_StartSelectionFromElement( m_pContext, 0, 0);
// probably needs to be the common ancestor.
Relayout( pBegin, 0, pEnd, RELAYOUT_NOCARET );
// Need to force selection.
SelectRegion(pBegin, 0, pEnd, 0);
Reduce(pBegin->GetCommonAncestor(pEnd));
}
void CEditBuffer::SetCharacterData( EDT_CharacterData *pData ){
VALIDATE_TREE(this);
SetDirtyFlag( TRUE );
CEditSelection selection;
GetSelection(selection);
if ( selection.IsContainerEnd() )
return;
else if( selection.AnyLeavesSelected() ){
AdoptAndDo(new CChangeAttributesCommand(this));
SetCharacterDataSelection( pData );
return;
}
FixupInsertPoint();
CEditLeafElement *pRight = m_pCurrent->Divide( m_iCurrentOffset )->Leaf();
if( pRight->IsA(P_TEXT) && pRight->Leaf()->GetLen() == 0 ){
pRight->Text()->SetData( pData );
m_pCurrent = pRight;
m_iCurrentOffset = 0;
}
else {
CEditTextElement *pNew = m_pCurrent->Leaf()->CopyEmptyText();
pNew->InsertBefore(pRight);
pNew->SetData( pData );
m_pCurrent = pNew;
m_iCurrentOffset = 0;
}
Relayout( m_pCurrent, 0, 0 );
}
EDT_CharacterData* CEditBuffer::GetCharacterData(){
CEditElement *pElement = m_pCurrent;
// The following assert fails once at program startup.
// XP_ASSERT(pElement || IsSelected());
//
// While selecting, we may not have a valid region
//
if( IsSelecting() ){
return EDT_NewCharacterData();
}
if( IsSelected() ){
CEditSelection selection;
EDT_CharacterData *pData = 0;
GetSelection( selection );
pElement = selection.m_start.m_pElement;
//
while( pElement && pElement != selection.m_end.m_pElement ){
if( pElement->IsA( P_TEXT ) ){
pElement->Text()->MaskData( pData );
}
else if( pElement->IsImage() ){
// We report the HREF status if we find an image
pElement->Image()->MaskData( pData );
}
pElement = pElement->NextLeaf();
}
// if the selection ends in the middle of a text block.
if( pElement && selection.m_end.m_iPos != 0
&& selection.m_end.m_pElement->IsA(P_TEXT) ){
pElement->Text()->MaskData( pData );
}
if( pData == 0 ){
return EDT_NewCharacterData();
}
return pData;
}
if( pElement && pElement->IsA(P_TEXT )){
return pElement->Text()->GetData();;
}
// Try to find the previous text element and use its characterData
if (pElement) {
CEditTextElement *tElement = pElement->PreviousTextInContainer();
if ( tElement){
return tElement->GetData();
}
}
// Create a dummy text element from scratch to guarantee
// that we get the same EDT_CharacterData as when creating a new
// CEditTextElement during typing.
CEditTextElement dummy( (CEditElement *)0,0);
return dummy.GetData();
// return EDT_NewCharacterData();
}
EDT_PageData* CEditBuffer::GetPageData(){
EDT_PageData* pData = EDT_NewPageData();
pData->pColorBackground = edt_MakeLoColor( m_colorBackground );
pData->pColorText = edt_MakeLoColor( m_colorText );
pData->pColorLink = edt_MakeLoColor( m_colorLink );
pData->pColorFollowedLink = edt_MakeLoColor( m_colorFollowedLink );
pData->pColorActiveLink = edt_MakeLoColor( m_colorActiveLink );
pData->pTitle = XP_STRDUP( ( m_pTitle ? m_pTitle : "" ) );
pData->pBackgroundImage = ( m_pBackgroundImage
? XP_STRDUP( m_pBackgroundImage )
: 0 );
return pData;
}
void CEditBuffer::FreePageData( EDT_PageData* pData ){
if ( pData->pColorBackground ) XP_FREE( pData->pColorBackground );
if ( pData->pColorText ) XP_FREE( pData->pColorText );
if ( pData->pColorLink ) XP_FREE( pData->pColorLink );
if ( pData->pColorFollowedLink ) XP_FREE( pData->pColorFollowedLink );
if ( pData->pColorActiveLink ) XP_FREE( pData->pColorActiveLink );
if ( pData->pTitle ) XP_FREE( pData->pTitle );
if ( pData->pBackgroundImage ) XP_FREE( pData->pBackgroundImage );
XP_FREE( pData );
}
void CEditBuffer::SetPageData( EDT_PageData* pData ){
m_colorBackground = EDT_LO_COLOR( pData->pColorBackground );
m_colorLink = EDT_LO_COLOR( pData->pColorLink );
m_colorText = EDT_LO_COLOR( pData->pColorText );
m_colorFollowedLink = EDT_LO_COLOR( pData->pColorFollowedLink );
m_colorActiveLink = EDT_LO_COLOR( pData->pColorActiveLink );
if( m_pTitle ) XP_FREE( m_pTitle );
m_pTitle = (pData->pTitle
? XP_STRDUP( pData->pTitle )
: 0);
FE_SetDocTitle( m_pContext, m_pTitle );
//
// If there was an image, and now there isn't remove it.
//
if (pData->pBackgroundImage == NULL ){
if(m_pBackgroundImage != NULL) XP_FREE(m_pBackgroundImage);
m_pBackgroundImage = NULL;
}
else if( EDT_IS_NEW_DOCUMENT(m_pContext) ){
// We can't copy it now (we don't have a base URL)
// so just use the absolute URL as the background until we save the file
if( m_pBackgroundImage ) XP_FREE( m_pBackgroundImage );
m_pBackgroundImage = XP_STRDUP(pData->pBackgroundImage);
}
else {
// We have a background (possibly new) and a saved file
char *pStr = 0;
intn retVal = NET_MakeRelativeURL( LO_GetBaseURL(m_pContext ),
pData->pBackgroundImage,
&pStr);
XP_ASSERT( retVal != NET_URL_FAIL );
if( pData->bKeepImagesWithDoc
&& retVal != NET_URL_SAME_DIRECTORY
&& !EDT_IS_LIVEWIRE_MACRO( pData->pBackgroundImage ) ){
char *pLocalName = edt_LocalName( pStr );
XP_FREE( pStr );
if( pLocalName != 0 ){
// Don't set m_pBackgroundImage = pLocalName!
// We will set name only after possible file save
// (in CEditBackgroundImageSaveObject::FileFetchComplete)
m_pSaveObject = new CEditBackgroundImageSaveObject( this );
m_pSaveObject->AddFile( pData->pBackgroundImage, pLocalName );
m_pSaveObject->SaveFiles();
XP_FREE(pLocalName);
}
}
else {
if( m_pBackgroundImage ) XP_FREE( m_pBackgroundImage );
m_pBackgroundImage = pStr;
}
}
// Note: all LO_SetDocumentColor calls
// and LO_SetBackgroundImage are done in RefreshLayout
RefreshLayout();
SetDirtyFlag( TRUE );
}
// MetaData
intn CEditBuffer::MetaDataCount( ){
intn i = 0;
intn iRetVal = 0;
while( i < m_metaData.Size() ){
if( m_metaData[i] != 0 ){
iRetVal++;
}
i++;
}
return iRetVal;
}
intn CEditBuffer::FindMetaData( EDT_MetaData *pMetaData ){
intn i = 0;
intn iRetVal = 0;
while( i < m_metaData.Size() ){
if( m_metaData[i] != 0 ){
if( (!!pMetaData->bHttpEquiv == !!m_metaData[i]->bHttpEquiv)
&& XP_STRCMP( m_metaData[i]->pName, pMetaData->pName) == 0 ){
return iRetVal;
}
iRetVal++;
}
i++;
}
return -1;
}
intn CEditBuffer::FindMetaDataIndex( EDT_MetaData *pMetaData ){
intn i = 0;
while( i < m_metaData.Size() ){
if( m_metaData[i] != 0 ){
if( (!!pMetaData->bHttpEquiv == !!m_metaData[i]->bHttpEquiv)
&& XP_STRCMP( m_metaData[i]->pName, pMetaData->pName) == 0 ){
return i;
}
}
i++;
}
return -1;
}
EDT_MetaData* CEditBuffer::MakeMetaData( XP_Bool bHttpEquiv, char *pName, char*pContent ){
EDT_MetaData *pData = XP_NEW( EDT_MetaData );
XP_BZERO( pData, sizeof( EDT_MetaData ) );
pData->bHttpEquiv = bHttpEquiv;
pData->pName = (pName
? XP_STRDUP(pName)
: 0);
pData->pContent = (pContent
? XP_STRDUP(pContent)
: 0);
return pData;
}
EDT_MetaData* CEditBuffer::GetMetaData( int n ){
intn i = 0;
intn iRetVal = 0;
n++;
while( n ){
if ( i >= m_metaData.Size() ) {
return NULL;
}
if( m_metaData[i] != 0 ){
n--;
}
i++;
}
i--;
return MakeMetaData( m_metaData[i]->bHttpEquiv,
m_metaData[i]->pName,
m_metaData[i]->pContent );
}
void CEditBuffer::SetMetaData( EDT_MetaData *pData ){
intn i = FindMetaDataIndex( pData );
EDT_MetaData* pNew = MakeMetaData( pData->bHttpEquiv, pData->pName, pData->pContent );
if( i == -1 ){
m_metaData.Add( pNew );
}
else {
FreeMetaData( m_metaData[i] );
m_metaData[i] = pNew;
}
}
void CEditBuffer::DeleteMetaData( EDT_MetaData *pData ){
intn i = FindMetaDataIndex( pData );
if( i != -1 ){
FreeMetaData( m_metaData[i] );
m_metaData[i] = 0;
}
}
void CEditBuffer::FreeMetaData( EDT_MetaData *pData ){
if ( pData ) {
if( pData->pName ) XP_FREE( pData->pName );
if( pData->pContent ) XP_FREE( pData->pContent );
XP_FREE( pData );
}
}
void CEditBuffer::ParseMetaTag( PA_Tag *pTag ){
XP_Bool bHttpEquiv = TRUE;
char *pName;
char *pContent;
pContent = edt_FetchParamString( pTag, PARAM_CONTENT );
pName = edt_FetchParamString( pTag, PARAM_HTTP_EQUIV );
// if we didn't get http-equiv, try for name=
if( pName == 0 ){
bHttpEquiv = FALSE;
pName = edt_FetchParamString( pTag, PARAM_NAME );
}
// if we got one or the other, add it to the list of meta tags.
if( pName ){
EDT_MetaData *pData = MakeMetaData( bHttpEquiv, pName, pContent );
SetMetaData( pData );
FreeMetaData( pData );
}
if( pName ) XP_FREE( pName );
if( pContent ) XP_FREE( pContent );
}
// Image
EDT_ImageData* CEditBuffer::GetImageData(){
CEditLeafElement *pInsertPoint;
ElementOffset iOffset;
XP_Bool bSingleItem;
bSingleItem = GetPropertyPoint( &pInsertPoint, &iOffset );
XP_ASSERT( bSingleItem );
XP_ASSERT( pInsertPoint->IsA(P_IMAGE) );
return pInsertPoint->Image()->GetImageData( );
}
#define XP_STRCMP_NULL( a, b ) XP_STRCMP( (a)?(a):"", (b)?(b):"" )
void CEditBuffer::SetImageData( EDT_ImageData* pData, XP_Bool bKeepImagesWithDoc ){
VALIDATE_TREE(this);
ClearSelection( TRUE, TRUE );
XP_ASSERT( m_pCurrent->IsA(P_IMAGE) );
// This function is only called when changing attributes of an image, not
// when inserting an image.
//
// There are 4 cases...
//
// 1) They change the src of the image, but not the size of the image
// (assume the dimensions of the new image).
// 2) They change the src and the size of the image
// 3) They change just the size of the image.
// 4) They reset the size of the image.
//
CEditImageElement *pImage = m_pCurrent->Image();
EDT_ImageData *pOldData = m_pCurrent->Image()->GetImageData();
// Case 1, 2, or 4
if( (XP_STRCMP( pOldData->pSrc, pData->pSrc ) != 0 )
|| XP_STRCMP_NULL( pOldData->pLowSrc, pData->pLowSrc )
|| (pData->iHeight == 0 || pData->iWidth== 0) ){
// if they touched height or width, assume they know what they
// are doing.
//
if( pData->iHeight == pOldData->iHeight
&& pData->iWidth == pOldData->iWidth ){
// LoadImage will get the image size from the image.
pData->iHeight = 0;
pData->iWidth = 0;
}
LoadImage( pData, bKeepImagesWithDoc, TRUE );
edt_FreeImageData( pOldData );
}
else {
m_pCurrent->Image()->SetImageData( pData );
Relayout( m_pCurrent->FindContainer(), 0, m_pCurrent, RELAYOUT_NOCARET );
SelectCurrentElement();
}
SetDirtyFlag( TRUE );
// Also set the HREF if it exists
if( pData->pHREFData ){
if( !pData->pHREFData->pURL || XP_STRLEN(pData->pHREFData->pURL) == 0 ){
pImage->SetHREF( ED_LINK_ID_NONE );
} else {
// Add link data to the image
pImage->SetHREF( linkManager.Add( pData->pHREFData->pURL,
pData->pHREFData->pExtra) );
}
}
}
void CEditBuffer::LoadImage( EDT_ImageData* pData, XP_Bool bKeepImagesWithDoc,
XP_Bool bReplaceImage ){
XP_Bool bMakeAbsolute = TRUE;
CEditImageSaveObject* pSaver = 0;
if( m_pLoadingImage != 0 ){
XP_ASSERT(0); // can only be loading one image at a time.
return;
}
if( !m_pContext->is_new_document ){
// You can call this with 3rd param NULL -- it is ignored
// (thats where just filename is placed)
intn retVal = NET_MakeRelativeURL( LO_GetBaseURL(m_pContext ),
pData->pSrc, NULL );
XP_ASSERT( retVal != NET_URL_FAIL );
if( bKeepImagesWithDoc
&& retVal != NET_URL_SAME_DIRECTORY
&& !EDT_IS_LIVEWIRE_MACRO( pData->pSrc )){
char *pLocalName = edt_LocalName( pData->pSrc );
if( pLocalName != 0 ){
bMakeAbsolute = FALSE;
pSaver = new CEditImageSaveObject( this, pData, bReplaceImage );
pSaver->m_srcIndex = pSaver->AddFile(
pData->pSrc, pLocalName );
}
}
// Do the low resolution image if it exists..
if( bKeepImagesWithDoc
&& pData->pLowSrc
&& !EDT_IS_LIVEWIRE_MACRO( pData->pLowSrc )){
char *pLowStr = 0;
intn retVal = NET_MakeRelativeURL( LO_GetBaseURL(m_pContext ),
pData->pLowSrc, NULL );
char *pLocalName = edt_LocalName( pData->pLowSrc );
if( pLocalName && retVal != NET_URL_SAME_DIRECTORY ){
if( pSaver == 0 ) {
pSaver = new CEditImageSaveObject(
this, pData, bReplaceImage );
}
bMakeAbsolute = FALSE;
pSaver->m_lowSrcIndex = pSaver->AddFile(
pData->pLowSrc, pLocalName );
}
}
}
if( bMakeAbsolute ){
m_pLoadingImage = new CEditImageLoader( this, pData, bReplaceImage );
m_pLoadingImage->LoadImage();
}
else if ( pSaver ){
m_pSaveObject = pSaver;
m_pSaveObject->SaveFiles();
}
}
//
// need to do a better job of splitting and inserting in the right place.
//
void CEditBuffer::InsertImage( EDT_ImageData* pData ){
if( IsSelected() ){
ClearSelection();
}
CEditImageElement *pImage = new CEditImageElement(0);
pImage->SetImageData( pData );
// Add link data to the image
if( pData->pHREFData && pData->pHREFData->pURL &&
XP_STRLEN(pData->pHREFData->pURL) > 0){
pImage->SetHREF( linkManager.Add( pData->pHREFData->pURL,
pData->pHREFData->pExtra) );
}
InsertLeaf( pImage );
}
void CEditBuffer::InsertLeaf( CEditLeafElement *pLeaf ){
VALIDATE_TREE(this);
FixupInsertPoint();
if( m_iCurrentOffset == 0 ){
pLeaf->InsertBefore( m_pCurrent );
}
else {
m_pCurrent->Divide(m_iCurrentOffset);
pLeaf->InsertAfter( m_pCurrent );
}
m_pCurrent = pLeaf;
m_iCurrentOffset = 1;
if ( ! m_bNoRelayout ){
Reduce(pLeaf->FindContainer());
}
// we could be doing a better job here. We probably repaint too much...
CEditElement *pContainer = m_pCurrent->FindContainer();
Relayout( pContainer, 0, pContainer->GetLastMostChild() );
SetDirtyFlag( TRUE );
}
void CEditBuffer::InsertNonLeaf( CEditElement *pElement){
VALIDATE_TREE(this);
FixupInsertPoint();
XP_ASSERT( ! pElement->IsLeaf());
CEditElement* pStart = m_pCurrent;
int iStartOffset = m_iCurrentOffset;
CEditInsertPoint start(m_pCurrent, m_iCurrentOffset);
CEditLeafElement* pRight = NULL;
InternalReturnKey(FALSE);
pRight = m_pCurrent;
CEditLeafElement* pLeft = pRight->PreviousLeaf(); // Will be NULL at start of document
pElement->InsertBefore(pRight->FindContainer());
pElement->FinishedLoad(this);
#ifdef DEBUG
m_pRoot->ValidateTree();
#endif
// Put cursor at the end of what has been inserted
if ( pRight->IsEndOfDocument() ) {
m_pCurrent = pRight->PreviousLeaf();
m_iCurrentOffset = m_pCurrent->GetLen();
}
else {
m_pCurrent = pRight;
m_iCurrentOffset = 0;
}
Relayout( pElement, 0, pRight );
SetDirtyFlag( TRUE );
}
// Table stuff
void CEditBuffer::GetTableInsertPoint(CEditInsertPoint& ip){
GetInsertPoint(ip);
CEditSelection s;
GetSelection(s);
if ( ! s.IsInsertPoint() && s.m_end == ip && ip.IsStartOfContainer()){
ip = ip.PreviousPosition();
};
}
XP_Bool CEditBuffer::IsInsertPointInTable(){
CEditInsertPoint ip;
GetTableInsertPoint(ip);
return ip.m_pElement->GetTableIgnoreSubdoc() != NULL;
}
XP_Bool CEditBuffer::IsInsertPointInNestedTable(){
XP_Bool result = FALSE;
CEditInsertPoint ip;
GetTableInsertPoint(ip);
CEditTableElement* pFirstTable = ip.m_pElement->GetTableIgnoreSubdoc();
if ( pFirstTable ) {
CEditTableElement* pSecondTable = pFirstTable->GetParent()->GetTableIgnoreSubdoc();
if ( pSecondTable ) {
result = TRUE;
}
}
return result;
}
EDT_TableData* CEditBuffer::GetTableData(){
CEditInsertPoint ip;
GetTableInsertPoint(ip);
CEditTableElement* pTable = ip.m_pElement->GetTableIgnoreSubdoc();
if ( pTable ){
return pTable->GetData();
}
else {
return NULL;
}
}
void CEditBuffer::SetTableData(EDT_TableData *pData){
VALIDATE_TREE(this);
CEditInsertPoint ip;
GetTableInsertPoint(ip);
CEditTableElement* pTable = ip.m_pElement->GetTableIgnoreSubdoc();
if ( pTable ){
pTable->SetData( pData );
Relayout( pTable, 0 );
SetDirtyFlag( TRUE );
}
}
void CEditBuffer::InsertTable(EDT_TableData *pData){
CEditTableElement *pTable = new CEditTableElement(pData->iColumns, pData->iRows);
if( pTable ){
pTable->SetData( pData );
pTable->FinishedLoad(this); // Sets default paragraphs for all the cells
AdoptAndDo(new CInsertTableCommand(this, pTable));
}
}
void CEditBuffer::DeleteTable(){
VALIDATE_TREE(this);
CEditInsertPoint ip;
GetTableInsertPoint(ip);
CEditTableElement* pTable = ip.m_pElement->GetTableIgnoreSubdoc();
if ( pTable ){
AdoptAndDo(new CDeleteTableCommand(this));
}
}
XP_Bool CEditBuffer::IsInsertPointInTableCaption(){
CEditInsertPoint ip;
GetTableInsertPoint(ip);
// We allow the insert point to be in a table with a table caption.
CEditTableElement* pTable = ip.m_pElement->GetTableIgnoreSubdoc();
CEditCaptionElement* pTableCaption = pTable ? pTable->GetCaption() : NULL;
return pTableCaption != NULL;
}
EDT_TableCaptionData* CEditBuffer::GetTableCaptionData(){
CEditInsertPoint ip;
GetTableInsertPoint(ip);
// We allow the insert point to be in a table with a table caption.
CEditTableElement* pTable = ip.m_pElement->GetTableIgnoreSubdoc();
CEditCaptionElement* pTableCaption = pTable ? pTable->GetCaption() : NULL;
if ( pTableCaption ){
return pTableCaption->GetData();
}
else {
return NULL;
}
}
void CEditBuffer::SetTableCaptionData(EDT_TableCaptionData *pData){
VALIDATE_TREE(this);
CEditInsertPoint ip;
GetTableInsertPoint(ip);
CEditTableElement* pTable = ip.m_pElement->GetTableIgnoreSubdoc();
CEditCaptionElement* pTableCaption = pTable ? pTable->GetCaption() : NULL;
if ( pTableCaption ){
pTableCaption->SetData( pData );
pTable->FinishedLoad(this); // Can move caption up or down.
Relayout( pTable, 0 );
SetDirtyFlag( TRUE );
}
}
void CEditBuffer::InsertTableCaption(EDT_TableCaptionData *pData){
VALIDATE_TREE(this);
CEditInsertPoint ip;
GetTableInsertPoint(ip);
CEditTableElement* pTable = ip.m_pElement->GetTableIgnoreSubdoc();
if ( pTable ){
if ( ! pTable->GetCaption() ) {
AdoptAndDo(new CInsertTableCaptionCommand(this, pData));
}
}
}
void CEditBuffer::DeleteTableCaption(){
VALIDATE_TREE(this);
CEditInsertPoint ip;
GetTableInsertPoint(ip);
CEditTableElement* pTable = ip.m_pElement->GetTableIgnoreSubdoc();
CEditTableCellElement* pTableCell = ip.m_pElement->GetTableCellIgnoreSubdoc();
if ( pTable ){
if ( pTable->GetCaption() ) {
AdoptAndDo(new CDeleteTableCaptionCommand(this));
}
}
}
XP_Bool CEditBuffer::IsInsertPointInTableRow(){
CEditInsertPoint ip;
GetTableInsertPoint(ip);
return ip.m_pElement->GetTableRowIgnoreSubdoc() != NULL;
}
EDT_TableRowData* CEditBuffer::GetTableRowData(){
CEditInsertPoint ip;
GetTableInsertPoint(ip);
CEditTableRowElement* pTableRow = ip.m_pElement->GetTableRowIgnoreSubdoc();
if ( pTableRow ){
return pTableRow->GetData();
}
else {
return NULL;
}
}
void CEditBuffer::SetTableRowData(EDT_TableRowData *pData){
VALIDATE_TREE(this);
CEditInsertPoint ip;
GetTableInsertPoint(ip);
CEditTableRowElement* pTableRow = ip.m_pElement->GetTableRowIgnoreSubdoc();
if ( pTableRow ){
pTableRow->SetData( pData );
Relayout( pTableRow, 0 );
SetDirtyFlag( TRUE );
}
}
void CEditBuffer::InsertTableRows(EDT_TableRowData *pData, XP_Bool bAfterCurrentRow, intn number){
VALIDATE_TREE(this);
CEditInsertPoint ip;
GetTableInsertPoint(ip);
CEditTableCellElement* pTableCell = ip.m_pElement->GetTableCellIgnoreSubdoc();
if ( pTableCell ){
AdoptAndDo(new CInsertTableRowCommand(this, pData, bAfterCurrentRow, number));
}
}
void CEditBuffer::DeleteTableRows(intn number){
VALIDATE_TREE(this);
CEditInsertPoint ip;
GetTableInsertPoint(ip);
CEditTableCellElement* pTableCell = ip.m_pElement->GetTableCellIgnoreSubdoc();
if ( pTableCell ){
AdoptAndDo(new CDeleteTableRowCommand(this, number));
}
}
void CEditBuffer::SyncCursor(CEditTableElement* pTable, intn iColumn, intn iRow)
{
CEditSelection selection = ComputeCursor(pTable, iColumn, iRow);
SetSelection(selection);
}
CEditSelection CEditBuffer::ComputeCursor(CEditTableElement* pTable, intn iColumn, intn iRow)
{
CEditInsertPoint insertPoint;
insertPoint.m_pElement = m_pRoot->GetFirstMostChild()->Leaf();
if ( pTable ) {
intn iRows = pTable->GetRows();
if ( iRows <= iRow ) {
iRow = iRows - 1;
}
CEditTableRowElement* pTableRow = pTable->FindRow(iRow);
if ( pTableRow ) {
intn iColumns = pTableRow->GetCells();
if ( iColumns <= iColumn ) {
iColumn = iColumns - 1;
}
CEditTableCellElement* pTableCell = pTableRow->FindCell(iColumn);
if ( pTableCell ) {
insertPoint.m_pElement = pTableCell->GetLastMostChild()->Leaf();
}
}
}
insertPoint.m_iPos = insertPoint.m_pElement->GetLen();
return CEditSelection(insertPoint, insertPoint);
}
void CEditBuffer::SyncCursor(CEditMultiColumnElement* pMultiColumn)
{
CEditInsertPoint insertPoint;
insertPoint.m_pElement = m_pRoot->GetFirstMostChild()->Leaf();
if ( pMultiColumn && pMultiColumn->GetFirstMostChild()->IsLeaf() ) {
insertPoint.m_pElement = pMultiColumn->GetFirstMostChild()->Leaf();
}
SetInsertPoint(insertPoint);
}
XP_Bool CEditBuffer::IsInsertPointInTableCell(){
CEditInsertPoint ip;
GetTableInsertPoint(ip);
return ip.m_pElement->GetTableCellIgnoreSubdoc() != NULL;
}
EDT_TableCellData* CEditBuffer::GetTableCellData(){
CEditInsertPoint ip;
GetTableInsertPoint(ip);
CEditTableCellElement* pTableCell = ip.m_pElement->GetTableCellIgnoreSubdoc();
if ( pTableCell ){
return pTableCell->GetData();
}
else {
return NULL;
}
}
void CEditBuffer::SetTableCellData(EDT_TableCellData *pData){
VALIDATE_TREE(this);
CEditInsertPoint ip;
GetTableInsertPoint(ip);
CEditTableCellElement* pTableCell = ip.m_pElement->GetTableCellIgnoreSubdoc();
if ( pTableCell ){
pTableCell->SetData( pData );
Relayout( pTableCell, 0 );
SetDirtyFlag( TRUE );
}
}
void CEditBuffer::InsertTableColumns(EDT_TableCellData *pData, XP_Bool bAfterCurrentCell, intn number){
VALIDATE_TREE(this);
CEditInsertPoint ip;
GetTableInsertPoint(ip);
CEditTableCellElement* pTableCell = ip.m_pElement->GetTableCellIgnoreSubdoc();
if ( pTableCell ){
AdoptAndDo(new CInsertTableColumnCommand(this, pData, bAfterCurrentCell, number));
}
}
void CEditBuffer::DeleteTableColumns(intn number){
VALIDATE_TREE(this);
CEditInsertPoint ip;
GetTableInsertPoint(ip);
CEditTableCellElement* pTableCell = ip.m_pElement->GetTableCellIgnoreSubdoc();
if ( pTableCell ){
AdoptAndDo(new CDeleteTableColumnCommand(this, number));
}
}
void CEditBuffer::InsertTableCells(EDT_TableCellData* /* pData */, XP_Bool bAfterCurrentCell, intn number){
VALIDATE_TREE(this);
CEditInsertPoint ip;
GetTableInsertPoint(ip);
CEditTableCellElement* pTableCell = ip.m_pElement->GetTableCellIgnoreSubdoc();
if ( pTableCell ){
AdoptAndDo(new CInsertTableCellCommand(this, bAfterCurrentCell, number));
}
}
void CEditBuffer::DeleteTableCells(intn number){
VALIDATE_TREE(this);
CEditInsertPoint ip;
GetTableInsertPoint(ip);
CEditTableCellElement* pTableCell = ip.m_pElement->GetTableCellIgnoreSubdoc();
if ( pTableCell ){
AdoptAndDo(new CDeleteTableCellCommand(this, number));
}
}
// Multicolumn stuff
XP_Bool CEditBuffer::IsInsertPointInMultiColumn(){
CEditInsertPoint ip;
GetInsertPoint(ip);
return ip.m_pElement->GetMultiColumnIgnoreSubdoc() != NULL;
}
EDT_MultiColumnData* CEditBuffer::GetMultiColumnData(){
CEditInsertPoint ip;
GetInsertPoint(ip);
CEditMultiColumnElement* pMultiColumn = ip.m_pElement->GetMultiColumnIgnoreSubdoc();
if ( pMultiColumn ){
return pMultiColumn->GetData();
}
else {
return NULL;
}
}
void CEditBuffer::SetMultiColumnData(EDT_MultiColumnData *pData){
VALIDATE_TREE(this);
CEditInsertPoint ip;
GetInsertPoint(ip);
CEditMultiColumnElement* pMultiColumn = ip.m_pElement->GetMultiColumnIgnoreSubdoc();
if ( pMultiColumn ){
pMultiColumn->SetData( pData );
Relayout( pMultiColumn, 0 );
SetDirtyFlag( TRUE );
}
}
void CEditBuffer::InsertMultiColumn(EDT_MultiColumnData *pData){
CEditMultiColumnElement *pMultiColumn = new CEditMultiColumnElement(pData->iColumns);
if( pMultiColumn ){
pMultiColumn->SetData( pData );
pMultiColumn->FinishedLoad(this); // Sets default paragraphs for all the cells
AdoptAndDo(new CInsertMultiColumnCommand(this, pMultiColumn));
}
}
void CEditBuffer::DeleteMultiColumn(){
VALIDATE_TREE(this);
CEditInsertPoint ip;
GetInsertPoint(ip);
CEditMultiColumnElement* pMultiColumn = ip.m_pElement->GetMultiColumnIgnoreSubdoc();
if ( pMultiColumn ){
AdoptAndDo(new CDeleteMultiColumnCommand(this));
}
}
EDT_HorizRuleData* CEditBuffer::GetHorizRuleData(){
CEditLeafElement *pInsertPoint;
ElementOffset iOffset;
XP_Bool bSingleItem;
bSingleItem = GetPropertyPoint( &pInsertPoint, &iOffset );
XP_ASSERT( bSingleItem );
XP_ASSERT( pInsertPoint->IsA(P_HRULE) );
return pInsertPoint->HorizRule()->GetData( );
}
void CEditBuffer::SetHorizRuleData( EDT_HorizRuleData* pData ){
ClearSelection( TRUE, TRUE );
XP_ASSERT( m_pCurrent->IsA(P_HRULE) );
m_pCurrent->HorizRule()->SetData( pData );
Relayout( m_pCurrent->FindContainer(), 0, m_pCurrent, RELAYOUT_NOCARET );
SelectCurrentElement();
SetDirtyFlag( TRUE );
}
void CEditBuffer::InsertHorizRule( EDT_HorizRuleData* pData ){
if( IsSelected() ){
ClearSelection();
}
CPasteCommand* command = new CPasteCommand(this, kInsertHorizRuleCommandID, FALSE);
CEditHorizRuleElement *pHorizRule = new CEditHorizRuleElement(0);
pHorizRule->SetData( pData );
InsertLeaf( pHorizRule );
AdoptAndDo(command);
}
char* CEditBuffer::GetTargetData(){
CEditLeafElement *pInsertPoint;
ElementOffset iOffset;
XP_Bool bSingleItem;
bSingleItem = GetPropertyPoint( &pInsertPoint, &iOffset );
XP_ASSERT( bSingleItem );
XP_ASSERT( pInsertPoint->GetElementType() == eTargetElement );
return pInsertPoint->Target()->GetData( );
}
void CEditBuffer::SetTargetData( char* pData ){
ClearSelection( TRUE, TRUE );
XP_ASSERT( m_pCurrent->GetElementType() == eTargetElement );
m_pCurrent->Target()->SetData( pData );
Relayout( m_pCurrent->FindContainer(), 0, m_pCurrent, RELAYOUT_NOCARET );
SelectCurrentElement();
SetDirtyFlag( TRUE );
}
void CEditBuffer::InsertTarget( char* pData ){
if( IsSelected() ){
ClearSelection(TRUE, TRUE);
}
CEditTargetElement *pTarget = new CEditTargetElement(0);
pTarget->SetData( pData );
InsertLeaf( pTarget );
}
char* CEditBuffer::GetAllDocumentTargets(){
intn iSize = 500;
int iCur = 0;
char *pBuf = (char*)XP_ALLOC( iSize );
CEditElement *pNext = m_pRoot;
pBuf[0] = 0;
pBuf[1] = 0;
while(NULL != (pNext = pNext->FindNextElement( &CEditElement::FindTarget, 0 )) ){
char *pName = pNext->Target()->GetData();
if( pName && *pName ){
int iLen = XP_STRLEN( pName );
if( iCur + iLen + 2 > iSize ){
iSize = iSize + iSize;
pBuf = (char*)XP_REALLOC( pBuf, iSize );
}
XP_STRCPY( &pBuf[iCur], pName );
iCur += iLen+1;
}
}
pBuf[iCur] = 0;
return pBuf;
}
#define LINE_BUFFER_SIZE 4096
char* CEditBuffer::GetAllDocumentTargetsInFile(char *pHref){
intn iSize = 500;
int iCur = 0;
char *pBuf = (char*)XP_ALLOC( iSize );
CEditElement *pNext = m_pRoot;
pBuf[0] = 0;
pBuf[1] = 0;
char *pFilename = NULL;
// First check if URL is a local file that exists
XP_StatStruct stat;
XP_Bool bFreeFilename = FALSE;
#if defined(XP_MAC) || defined(XP_UNIX)
if ( -1 != XP_Stat(pHref, &stat, xpURL) &&
stat.st_mode & S_IFREG ) {
#else
if ( -1 != XP_Stat(pHref, &stat, xpURL) &&
stat.st_mode & _S_IFREG ) {
#endif
// We can use unprocessed URL,
// and we don't need to free it
pFilename = pHref;
}
else {
// We probably have a URL,
// get absolute URL path then convert to local format
char *pAbsolute = NET_MakeAbsoluteURL( LO_GetBaseURL(m_pContext ), pHref );
if( !pAbsolute ||
!XP_ConvertUrlToLocalFile(pAbsolute, &pFilename) ){
if( pFilename ) XP_FREE(pFilename);
if( pAbsolute ) XP_FREE(pAbsolute);
return NULL;
}
XP_FREE(pAbsolute);
// We need to free the filename made for us
bFreeFilename = TRUE;
}
// Open local file
XP_File file = XP_FileOpen( pFilename, xpURL, XP_FILE_READ );
if( !file ) {
if( pFilename && bFreeFilename ){
XP_FREE(pFilename);
}
return NULL;
}
char pFileBuf[LINE_BUFFER_SIZE];
char *ptr, *pEnd, *pStart;
size_t count;
PA_Tag *pTag;
char *pName;
// Read unformated chunks
while( 0 < (count = fread(pFileBuf, 1, LINE_BUFFER_SIZE, file)) ){
// Scan from the end to find end of last tag in block
ptr = pFileBuf + count -1;
while( (ptr > pFileBuf) && (*ptr != '>') ) ptr--;
//Bloody unlikely, but we didn't find a tag!
if( ptr == pFileBuf ) continue;
// Move file pointer back so next read starts
// 1 char after the region just found
int iBack = int(ptr - pFileBuf) - int(count) + 1;
fseek( file, iBack, SEEK_CUR );
// Save the end of "tagged" region
// and reset to beginning
pEnd = ptr;
ptr = pFileBuf;
FIND_TAG:
// Scan to beginning of any tag
while( (ptr < pEnd) && (*ptr != '<') ) ptr++;
if( ptr == pEnd ) continue;
// Save start of tag
pStart = ptr;
// Skip over whitespace before tag name
ptr++;
while( (ptr < pEnd) && (XP_IS_SPACE(*ptr)) ) ptr++;
if( ptr == pEnd ) continue;
// Check for Anchor tag
if( ((*ptr == 'a') || (*ptr == 'A')) &&
XP_IS_SPACE(*(ptr+1)) ){
// Find end of the tag
while( (ptr < pEnd) && (*ptr != '>') ) ptr++;
if( ptr == pEnd ) continue;
// Parse into tag struct so we can use
// edt_FetchParamString to do the tricky stuff
// Kludge city. pa_CreateMDLTag needs a pa_DocData solely to
// look at the line count.
{
pa_DocData doc_data;
doc_data.newline_count = 0;
pTag = pa_CreateMDLTag(&doc_data, pStart, (ptr - pStart)+1 );
}
if( pTag ){
if( pTag->type == P_ANCHOR ){
pName = edt_FetchParamString(pTag, PARAM_NAME);
if( pName ){
// We found a Name
int iLen = XP_STRLEN( pName );
if( iCur + iLen + 2 > iSize ){
iSize = iSize + iSize;
pBuf = (char*)XP_REALLOC( pBuf, iSize );
}
XP_STRCPY( &pBuf[iCur], pName );
iCur += iLen+1;
XP_FREE(pName);
}
}
PA_FreeTag(pTag);
// Move past the tag we found and search again
ptr++;
goto FIND_TAG;
}
else {
// We couldn't find a complete tag, get another block
continue;
}
} else {
// Not an anchor tag, try again
ptr++;
goto FIND_TAG;
}
}
XP_FileClose(file);
if( pFilename && bFreeFilename ){
XP_FREE(pFilename);
}
pBuf[iCur] = 0;
return pBuf;
}
#ifdef FIND_REPLACE
XP_Bool CEditBuffer::FindAndReplace( EDT_FindAndReplaceData *pData ){
return FALSE;
}
#endif
class CStretchBuffer {
private:
char *m_pBuffer;
intn m_iSize;
intn m_iCur;
public:
CStretchBuffer();
~CStretchBuffer(){} // intentionall don't destroy the buffer.
void Add( char *pString );
char* GetBuffer(){ return m_pBuffer; }
XP_Bool Contains( char* p );
};
CStretchBuffer::CStretchBuffer(){
m_pBuffer = (char*)XP_ALLOC( 512 );
m_iSize = 512;
m_iCur = 0;
m_pBuffer[0] = 0;
m_pBuffer[1] = 0;
}
void CStretchBuffer::Add( char *p ){
if( p == 0 || *p == 0 ){
return;
}
int iLen = XP_STRLEN( p );
while( m_iCur + iLen + 2 > m_iSize ){
m_iSize = m_iSize + m_iSize;
m_pBuffer = (char*)XP_REALLOC( m_pBuffer, m_iSize );
}
XP_STRCPY( &m_pBuffer[m_iCur], p );
m_iCur += iLen+1;
m_pBuffer[m_iCur] = 0;
}
XP_Bool CStretchBuffer::Contains( char* p ){
char *s = GetBuffer();
while( s && *s ){
if( XP_STRCASECMP( s, p ) == 0 ){
return TRUE;
}
s += XP_STRLEN(s)+1;
}
return FALSE;
}
// Returns true if file URL is in same directory as document.
// If returns TRUE, then filename (no path) is written to *ppFilename
// and must be freed by caller
XP_Bool GetExistingFileInSameDirectory( char *pBaseURL, char *pSrcURL, char **ppFilename )
{
XP_Bool bRetVal = FALSE;
if( pBaseURL && pSrcURL && ppFilename ){
if( NET_URL_SAME_DIRECTORY == NET_MakeRelativeURL( pBaseURL, pSrcURL, ppFilename) &&
*ppFilename ){
char *pAbsoluteURL = NET_MakeAbsoluteURL( pBaseURL, *ppFilename );
if( pAbsoluteURL ){
// Returns TRUE if file exists
bRetVal = XP_ConvertUrlToLocalFile(pAbsoluteURL, NULL);
XP_FREE(pAbsoluteURL);
}
}
}
return bRetVal;
}
char* CEditBuffer::GetAllDocumentFiles(){
CEditElement *pNext = m_pRoot;
CStretchBuffer buf;
char *pRetVal = 0;
char *pDocURL = LO_GetBaseURL(m_pContext);
if( !pDocURL ){
return NULL;
}
char *pFilename = NULL;
// Check all files to make sure they are in same directory as main file
// and make sure they exist (else publish will abort)
if( m_pBackgroundImage &&
GetExistingFileInSameDirectory( pDocURL, m_pBackgroundImage, &pFilename ) ){
buf.Add( pFilename );
XP_FREE(pFilename);
}
while(NULL != (pNext = pNext->FindNextElement( &CEditElement::FindImage, 0 )) ){
EDT_ImageData *pData = pNext->Image()->GetImageData();
if( pData && pData->pSrc && !buf.Contains( pData->pSrc ) &&
GetExistingFileInSameDirectory( pDocURL, pData->pSrc, &pFilename ) ){
buf.Add( pFilename );
XP_FREE(pFilename);
}
if( pData && pData->pLowSrc && !buf.Contains( pData->pLowSrc ) &&
GetExistingFileInSameDirectory( pDocURL, pData->pLowSrc, &pFilename ) ){
buf.Add( pFilename );
XP_FREE(pFilename);
}
EDT_FreeImageData( pData );
}
pRetVal = buf.GetBuffer();
// If no files found, return NULL
if( pRetVal && *pRetVal == '\0' ){
XP_FREE(pRetVal);
pRetVal = NULL;
}
return pRetVal;
}
char* CEditBuffer::GetUnknownTagData(){
CEditLeafElement *pInsertPoint;
ElementOffset iOffset;
XP_Bool bSingleItem;
bSingleItem = GetPropertyPoint( &pInsertPoint, &iOffset );
XP_ASSERT( bSingleItem );
XP_ASSERT( pInsertPoint->IsIcon() );
return pInsertPoint->Icon()->GetData( );
}
void CEditBuffer::SetUnknownTagData( char* pData ){
ClearSelection( TRUE, TRUE );
XP_ASSERT( m_pCurrent->GetElementType() == eIconElement );
m_pCurrent->Icon()->SetData( pData );
Relayout( m_pCurrent->FindContainer(), 0, m_pCurrent, RELAYOUT_NOCARET );
SelectCurrentElement();
SetDirtyFlag( TRUE );
}
void CEditBuffer::InsertUnknownTag( char *pData ){
if( IsSelected() ){
ClearSelection();
}
if ( ! pData ) {
XP_ASSERT(FALSE);
return;
}
NormalizeEOLsInString(pData);
Bool bEndTag = ( XP_STRLEN(pData) > 1 && pData[1] == '/' );
CEditIconElement *pUnknownTag = new CEditIconElement(0,
bEndTag ? EDT_ICON_UNSUPPORTED_END_TAG :EDT_ICON_UNSUPPORTED_TAG);
pUnknownTag->SetData( pData );
InsertLeaf( pUnknownTag );
}
EDT_ListData* CEditBuffer::GetListData(){
CEditContainerElement *pContainer;
CEditListElement *pList;
if( IsSelected() ){
CEditContainerElement *pEndContainer;
CEditListElement *pEndList;
CEditSelection selection;
// LTNOTE: this is a hack. It doesen't handle a bunch of cases.
// It needs to be able to handle multiple lists selected.
// It should work a little better.
GetSelection( selection );
selection.m_start.m_pElement->FindList( pContainer, pList );
selection.GetClosedEndContainer()->GetLastMostChild()->FindList( pEndContainer, pEndList );
if( pList != pEndList ){
return 0;
}
}
else {
m_pCurrent->FindList( pContainer, pList );
}
if( pList ){
return pList->GetData( );
}
else {
return 0;
}
}
void CEditBuffer::SetListData( EDT_ListData* pData ){
VALIDATE_TREE(this);
CEditContainerElement *pContainer;
CEditListElement *pList;
if( IsSelected() ){
CEditLeafElement *pBegin, *pEnd, *pCurrent;
ElementOffset iBeginPos, iEndPos;
XP_Bool bFromStart;
GetSelection( pBegin, iBeginPos, pEnd, iEndPos, bFromStart );
pCurrent = pBegin;
XP_Bool bDone = FALSE;
do {
pCurrent->FindList( pContainer, pList );
if( pList ){
pList->SetData( pData );
}
bDone = (pEnd == pCurrent ); // For most cases
pCurrent = pCurrent->NextLeafAll();
bDone = bDone || (iEndPos == 0 && pEnd == pCurrent ); // Pesky edge conditions!
} while( pCurrent && !bDone );
// force layout stop displaying the current selection.
LO_StartSelectionFromElement( m_pContext, 0, 0);
CEditElement *pLast = pEnd;
if( pEnd ){
pEnd->FindList( pContainer, pList );
if( pList ) pLast = pList->GetLastMostChild();
}
Relayout( pBegin->FindContainer(), 0, pLast, RELAYOUT_NOCARET );
// Need to force selection.
SelectRegion(pBegin, iBeginPos, pEnd, iEndPos, bFromStart );
}
else {
m_pCurrent->FindList( pContainer, pList );
if( pList ){
pList->SetData( pData );
Relayout( pList, 0, pList->GetLastMostChild() );
}
}
SetDirtyFlag( TRUE );
}
void CEditBuffer::InsertBreak( ED_BreakType eBreak, XP_Bool bTyping ){
VALIDATE_TREE(this);
if( IsSelected() ){
// ToDo: Consider cutting the selection here, like InsertChar does.
ClearSelection();
}
CTypingCommand* cmd = NULL;
if ( bTyping ) {
cmd = GetTypingCommand();
}
PA_Tag *pTag = XP_NEW( PA_Tag );
XP_BZERO( pTag, sizeof( PA_Tag ) );
pTag->type = P_LINEBREAK;
SetDirtyFlag( TRUE );
switch( eBreak ){
case ED_BREAK_NORMAL:
break;
case ED_BREAK_LEFT:
edt_SetTagData( pTag, "CLEAR=LEFT>" );
break;
case ED_BREAK_RIGHT:
edt_SetTagData( pTag, "CLEAR=RIGHT>" );
break;
case ED_BREAK_BOTH:
edt_SetTagData( pTag, "CLEAR=BOTH>" );
break;
}
if ( bTyping ){
cmd->AddCharStart();
}
CEditBreakElement *pBreak = new CEditBreakElement( 0, pTag );
InsertLeaf(pBreak);
FixupSpace(bTyping);
Reduce(pBreak->GetParent());
#if 0
CEditElement *pNew = m_pCurrent->Divide(m_iCurrentOffset);
pBreak->InsertBefore( pNew );
m_pCurrent = pBreak;
m_iCurrentOffset = 1;
CEditElement *pContainer = m_pCurrent->FindContainer();
Relayout( pContainer, 0, pContainer->GetLastMostChild() );
#endif
if ( bTyping ){
cmd->AddCharEnd();
}
}
EDT_ClipboardResult CEditBuffer::CanCut(XP_Bool bStrictChecking){
CEditSelection selection;
GetSelection(selection);
return CanCut(selection, bStrictChecking);
}
EDT_ClipboardResult CEditBuffer::CanCut(CEditSelection& selection, XP_Bool bStrictChecking){
EDT_ClipboardResult result = selection.IsInsertPoint() ? EDT_COP_SELECTION_EMPTY : EDT_COP_OK;
if ( bStrictChecking && result == EDT_COP_OK
&& selection.CrossesSubDocBoundary() ) {
result = EDT_COP_SELECTION_CROSSES_TABLE_DATA_CELL;
}
return result;
}
EDT_ClipboardResult CEditBuffer::CanCopy(XP_Bool bStrictChecking){
return CanCut(bStrictChecking); /* In the future we may do something for read-only docs. */
}
EDT_ClipboardResult CEditBuffer::CanCopy(CEditSelection& selection, XP_Bool bStrictChecking){
return CanCut(selection, bStrictChecking); /* In the future we may do something for read-only docs. */
}
EDT_ClipboardResult CEditBuffer::CanPaste(XP_Bool bStrictChecking){
CEditSelection selection;
GetSelection(selection);
return CanPaste(selection, bStrictChecking);
}
EDT_ClipboardResult CEditBuffer::CanPaste(CEditSelection& selection, XP_Bool bStrictChecking){
EDT_ClipboardResult result = EDT_COP_OK;
if ( bStrictChecking
&& selection.CrossesSubDocBoundary() ) {
result = EDT_COP_SELECTION_CROSSES_TABLE_DATA_CELL;
}
return result;
}
XP_Bool CEditBuffer::CanSetHREF(){
if( IsSelected()
|| ( m_pCurrent && m_pCurrent->Leaf()->GetHREF() != ED_LINK_ID_NONE ) ){
return TRUE;
}
else {
return FALSE;
}
}
// This needs to be written to:
// Return data in EDT_HREFData struct containing:
// pTitle: ALL the text of elements with same HREF
// or the image name if only an image is selected
// pHREF what we are getting now
// pTarget what we are ignoring now!
// pMocha "
char *CEditBuffer::GetHREF(){
CEditLeafElement *pElement = m_pCurrent;
if( IsSelecting() ){
return 0;
}
if( IsSelected() ){
ElementOffset i,i1;
CEditLeafElement *pEnd;
XP_Bool bFromStart;
GetSelection( pElement, i, pEnd, i1, bFromStart );
}
if( pElement ){
ED_LinkId id = pElement->Leaf()->GetHREF();
if( id != ED_LINK_ID_NONE ){
return linkManager.GetHREF(id) ;
}
else {
return 0;
}
}
return 0;
}
char* CEditBuffer::GetHREFText(){
CEditLeafElement *pElement = m_pCurrent;
ED_LinkId id;
char* pBuf = 0;
if( pElement && pElement->IsText() &&
(id = pElement->GetHREF()) != ED_LINK_ID_NONE ){
// Move back to find start of contiguous elements
// with same HREF
CEditLeafElement *pStartElement = pElement;
while ( (pElement = (CEditLeafElement*)pElement->GetPreviousSibling()) &&
pElement->IsText() &&
pElement->GetHREF() == id ) {
pStartElement = pElement;
}
// We now have starting EditText element
pElement = pStartElement;
char* pText = pStartElement->Text()->GetText();
pBuf = pText ? XP_STRDUP( pText ) : 0;
if( !pBuf ){
return 0;
}
// Now scan forward to accumulate text
while ( (pElement = (CEditLeafElement*)pElement->GetNextSibling()) &&
pElement && pElement->IsText() &&
pElement->GetHREF() == id ){
// Cast to access text class (we always test element first)
// CEditTextElement *pText = (CEditTextElement*)pElement;
pBuf = (char*)XP_REALLOC(pBuf, XP_STRLEN(pBuf) + pElement->Text()->GetSize() + 1);
if( !pBuf ){
return 0;
}
char* pText = pElement->Text()->GetText();
if ( pText ) {
strcat( pBuf, pText );
}
}
}
return pBuf;
}
void CEditBuffer::SetCaret(){
InternalSetCaret(TRUE);
}
void CEditBuffer::InternalSetCaret(XP_Bool bRevealPosition){
LO_TextStruct* pText;
int iLayoutOffset;
int32 winHeight, winWidth;
int32 topX, topY;
if( m_bNoRelayout ){
return;
}
// jhp Check m_pCurrent because IsSelected won't give accurate results when
// the document is being layed out.
if( !IsSelected() && m_pCurrent && !m_inScroll ){
if( m_pCurrent->IsA( P_TEXT ) ){
if( !m_pCurrent->Text()->GetLOTextAndOffset( m_iCurrentOffset,
m_bCurrentStickyAfter, pText, iLayoutOffset ) ){
return;
}
}
FE_DestroyCaret(m_pContext);
if ( bRevealPosition ) {
RevealPosition(m_pCurrent, m_iCurrentOffset, m_bCurrentStickyAfter);
}
FE_GetDocAndWindowPosition( m_pContext, &topX, &topY, &winWidth, &winHeight );
m_lastTopY = topY;
if( m_pCurrent->IsA( P_TEXT ) ){
if( !m_pCurrent->Text()->GetLOTextAndOffset( m_iCurrentOffset, m_bCurrentStickyAfter,
pText, iLayoutOffset ) ){
return;
}
FE_DisplayTextCaret( m_pContext, FE_VIEW, pText, iLayoutOffset );
}
else
{
LO_Position effectiveCaretPosition = GetEffectiveCaretLOPosition(m_pCurrent, m_iCurrentOffset, m_bCurrentStickyAfter);
switch ( effectiveCaretPosition.element->type )
{
case LO_IMAGE:
FE_DisplayImageCaret( m_pContext,
& effectiveCaretPosition.element->lo_image,
(ED_CaretObjectPosition)effectiveCaretPosition.position );
break;
default:
FE_DisplayGenericCaret( m_pContext,
& effectiveCaretPosition.element->lo_any,
(ED_CaretObjectPosition)effectiveCaretPosition.position );
break;
}
}
}
}
LO_Position CEditBuffer::GetEffectiveCaretLOPosition(CEditElement* pElement, intn iOffset, XP_Bool bCurrentStickyAfter)
{
LO_Position position;
int iPos;
pElement->Leaf()->GetLOElementAndOffset( iOffset, bCurrentStickyAfter, position.element, iPos);
position.position = iPos;
// Linefeeds only have a single position. If you are at the end
// of a linefeed, you are really at the beginning of the next line.
if( position.element->type == LO_LINEFEED && position.position == 1 ){
//LO_Element *pNext = LO_NextEditableElement( pLoElement );
LO_Element *pNext = position.element->lo_any.next;
if( pNext ){
position.element = pNext;
position.position = 0;
}
else {
position.position = 0;
}
}
return position;
}
// A utility function for ensuring that a range is visible. This handles one
// axis; it's called twice, once for each dimension.
//
// All variable names are given in terms of the vertical case. The horizontal
// case is the same.
//
// top - the position of the top edge of the visible portion of the document,
// in the document's coordinate system.
//
// length - the length of the window, in the document's coordinate system.
//
// targetLow - the first position we desire to be visible.
//
// targetHigh - the last position we desire to be visible.
//
// In case both positions can't be made visible, targetLow wins.
//
// upMargin - if we have to move up, where to
// position the targetLow relative to top.
// downMargin - if we have to move down, where to position targetLow relative to top.
//
// The margins should be in the range 0..length - 1
//
// Returns TRUE if top changed.
XP_Bool MakeVisible(int32& top, int32 length, int32 targetLow, int32 targetHigh, int32 upMargin, int32 downMargin)
{
int32 newTop = top;
if ( targetLow < newTop ) {
// scroll up
newTop = targetLow - upMargin;
if ( newTop < 0 ) {
newTop = 0;
}
}
else if ( targetHigh >= (newTop + length) ) {
// scroll down
int32 potentialTop = targetHigh - downMargin;
if ( potentialTop < 0 ) {
potentialTop = 0;
}
if ( potentialTop < targetLow && targetLow < potentialTop + length ) {
newTop = potentialTop;
}
else {
// Too narrow to show all of cursor. Show just the top part.
newTop = targetLow - upMargin;
if ( newTop < 0 ) {
newTop = 0;
}
}
}
XP_Bool changed = newTop != top;
top = newTop;
return changed;
}
void CEditBuffer::RevealPosition(CEditElement* pElement, int iOffset, XP_Bool bStickyAfter)
{
int32 winHeight, winWidth;
int32 topX, topY;
int32 targetX, targetYLow, targetYHigh;
if ( ! pElement ) {
XP_ASSERT(FALSE);
return;
}
FE_GetDocAndWindowPosition( m_pContext, &topX, &topY, &winWidth, &winHeight );
if ( ! GetLOCaretPosition(pElement, iOffset, bStickyAfter, targetX, targetYLow, targetYHigh) )
return;
// XP_TRACE(("top %d %d target %d %d %d", topX, topY, targetX, targetYLow, targetYHigh));
// The visual position policy is that when moving up or down we stick close to
// the existing margin, but when moving left or right we move to 2/3rds of the
// way across the screen. (If we ever support right-to-left text we'll
// have to revisit this.)
const int32 kUpMargin = ( winHeight > 30 ) ? 10 : (winHeight / 3);
const int32 kDownMargin = winHeight - kUpMargin;
const int32 kLeftRightMargin = winWidth * 2 / 3;
XP_Bool updateX = MakeVisible(topX, winWidth, targetX, targetX, kLeftRightMargin, kLeftRightMargin);
XP_Bool updateY = MakeVisible(topY, winHeight, targetYLow, targetYHigh, kUpMargin, kDownMargin);
if ( updateX || updateY ) {
// XP_TRACE(("new top %d %d", topX, topY));
XP_Bool bHaveCaret = ! IsSelected();
if ( bHaveCaret){
FE_DestroyCaret(m_pContext);
}
#ifdef XP_UNIX
FE_ScrollDocTo(m_pContext, FE_VIEW, topX, topY);
#else
FE_SetDocPosition(m_pContext, FE_VIEW, topX, topY);
#endif
if ( bHaveCaret){
InternalSetCaret(FALSE);
}
}
}
XP_Bool CEditBuffer::GetLOCaretPosition(CEditElement* pElement, int iOffset, XP_Bool bStickyAfter,
int32& targetX, int32& targetYLow, int32& targetYHigh)
{
if ( ! pElement ) {
XP_ASSERT(FALSE);
return FALSE;
}
LO_Position position = GetEffectiveCaretLOPosition(pElement, iOffset, bStickyAfter);
FE_GetCaretPosition(m_pContext, &position, &targetX, &targetYLow, &targetYHigh);
return TRUE;
}
void CEditBuffer::WindowScrolled(){
if( m_inScroll ){
return;
}
m_inScroll = TRUE;
if( !IsSelected() ){
InternalSetCaret(FALSE);
}
m_inScroll = FALSE;
#if 0
// This code keeps the cursor on the screen, which is pretty weird behavior for
// a text editor. That's not how any WYSIWYG editor works -- I'm guessing it's
// some crufty key-based editor feature. -- jhp
LO_Element* pElement;
int iLayoutOffset;
int32 winHeight, winWidth;
int32 topX, topY;
int32 iDesiredY;
if( m_inScroll ){
return;
}
m_inScroll = TRUE;
FE_GetDocAndWindowPosition( m_pContext, &topX, &topY, &winWidth, &winHeight );
// if there is a current selection, we have nothing to do.
if( !IsSelected() ){
m_pCurrent->Leaf()->GetLOElementAndOffset( m_iCurrentOffset, m_bCurrentStickyAfter,
pElement, iLayoutOffset );
// m_iDesiredX is where we would move if we could. This keeps the
// cursor moving, basically, straight up and down, even if there is
// no text or gaps
if( m_iDesiredX == -1 ){
m_iDesiredX = pElement->lo_any.x +
(m_pCurrent->IsA(P_TEXT)
? LO_TextElementWidth( m_pContext,
(LO_TextStruct*) pElement, iLayoutOffset)
: 0 );
}
if( pElement->lo_any.y < topY ){
iDesiredY = (pElement->lo_any.y - m_lastTopY) + topY;
// caret is above the current window
LO_PositionCaret( m_pContext, m_iDesiredX, iDesiredY );
}
else if( pElement->lo_any.y+pElement->lo_any.height > topY+winHeight ){
// caret is below the current window
iDesiredY = (pElement->lo_any.y - m_lastTopY) + topY;
LO_PositionCaret( m_pContext, m_iDesiredX, iDesiredY);
}
else {
SetCaret();
}
}
m_lastTopY = topY;
m_inScroll = FALSE;
#endif
}
void CEditBuffer::DebugPrintTree(CEditElement* pElement){
const size_t kIndentBufSize = 256;
char indent[kIndentBufSize];
char *pData;
char *pData1;
PA_Tag *pTag;
int indentSize = printState.m_iLevel*2;
if ( indentSize >= kIndentBufSize ) {
indentSize = kIndentBufSize - 1;
}
CEditElement *pNext = pElement->GetChild();
int i;
for(i = 0; i < indentSize; i++ ){
indent[i] = ' ';
}
indent[i] = '\0';
if( pElement->IsA(P_TEXT) ) {
pData = pElement->Text()->GetText();
pData1 = pElement->Text()->DebugFormat();
pTag = 0;
}
else if( pElement->IsContainer() ){
ED_Alignment alignment = pElement->Container()->GetAlignment();
if ( alignment < ED_ALIGN_DEFAULT || alignment > ED_ALIGN_ABSTOP ) {
alignment = ED_ALIGN_ABSTOP;
}
pData = lo_alignStrings[alignment];
pData1 = "";
pTag = 0;
}
else {
pData1 = "";
pTag = pElement->TagOpen(0);
if( pTag ){
// no multiple tag fetches.
//XP_ASSERT( pTag->next == 0 );
PA_LOCK( pData, char*, pTag->data );
}
else {
pData = 0;
}
}
printState.m_pOut->Printf("\n");
ElementIndex baseIndex = pElement->GetElementIndex();
ElementIndex elementCount = pElement->GetPersistentCount();
printState.m_pOut->Printf("0x%08x %6ld-%6ld", pElement, (long)baseIndex,
(long)baseIndex + elementCount);
printState.m_pOut->Printf("%s %s: %s%c",
indent,
EDT_TagString(pElement->GetType()),
pData1,
(pData ? '"': ' ') );
if( pData ){
printState.m_pOut->Write( pData, XP_STRLEN(pData));
printState.m_pOut->Write( "\"", 1 );
}
if( pTag ){
PA_UNLOCK( pTag->data );
PA_FreeTag( pTag );
}
while( pNext ){
printState.m_iLevel++;
DebugPrintTree( pNext );
printState.m_iLevel--;
pNext = pNext->GetNextSibling();
}
}
void CEditBuffer::PrintTree( CEditElement* pElement ){
CEditElement* pNext = pElement->GetChild();
pElement->PrintOpen( &printState );
while( pNext ){
PrintTree( pNext );
pNext = pNext->GetNextSibling();
}
pElement->PrintEnd( &printState );
}
void CEditBuffer::AppendTitle( char *pTitle ){
if( m_pTitle == 0 ){
m_pTitle = XP_STRDUP( pTitle );
}
else {
int32 titleLen = XP_STRLEN(m_pTitle)+XP_STRLEN(pTitle)+1;
m_pTitle = (char*)XP_REALLOC( m_pTitle,
titleLen);
strcat( m_pTitle, pTitle );
}
}
void CEditBuffer::PrintMetaData( CPrintState *pPrintState ){
int i = 0;
EDT_MetaData *pData;
while( i < m_metaData.Size() ){
if( (pData = m_metaData[i]) != 0 ){
// LTNOTE:
// You can't merge these into a single printf. edt_MakeParamString
// uses a single static buffer.
//
pPrintState->m_pOut->Printf( " <META %s=%s ",
(pData->bHttpEquiv ? "HTTP-EQUIV" : "NAME"),
edt_MakeParamString( pData->pName ) );
pPrintState->m_pOut->Printf( "CONTENT=%s>\n",
edt_MakeParamString( pData->pContent ) );
}
i++;
}
}
void CEditBuffer::PrintDocumentHead( CPrintState *pPrintState ){
pPrintState->m_pOut->Printf(
"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 3.2//EN\">\n"
"<HTML>\n"
"<HEAD>\n"
" <TITLE>");
edt_PrintWithEscapes( pPrintState, m_pTitle, FALSE );
pPrintState->m_pOut->Printf(
"</TITLE>\n");
PrintMetaData( pPrintState );
if( GetParseState()->m_pJavaScript ){
int32 iLen = GetParseState()->m_pJavaScript->GetLen();
XP_HUGE_CHAR_PTR pScript = GetParseState()->m_pJavaScript->GetText();
// The <SCRIPT> tag, and its parameters, are in the saved text.
// pPrintState->m_pOut->Printf("<SCRIPT>");
while( iLen ){
int16 iToWrite = (int16) min( iLen, 0x4000 );
pPrintState->m_pOut->Write( pScript, iToWrite );
iLen -= iToWrite;
pScript += iToWrite;
}
pPrintState->m_pOut->Printf("</SCRIPT>\n");
}
pPrintState->m_pOut->Printf(
"</HEAD>\n"
"<BODY");
// print the contents of the body tag.
if( m_colorText.IsDefined() ) {
pPrintState->m_pOut->Printf(" TEXT=\"#%06lX\"", m_colorText.GetAsLong() );
}
if( m_colorBackground.IsDefined() ) {
pPrintState->m_pOut->Printf(" BGCOLOR=\"#%06lX\"", m_colorBackground.GetAsLong() );
}
if( m_colorLink.IsDefined() ) {
pPrintState->m_pOut->Printf(" LINK=\"#%06lX\"", m_colorLink.GetAsLong() );
}
if( m_colorFollowedLink.IsDefined() ) {
pPrintState->m_pOut->Printf(" VLINK=\"#%06lX\"", m_colorFollowedLink.GetAsLong() );
}
if( m_colorActiveLink.IsDefined() ) {
pPrintState->m_pOut->Printf(" ALINK=\"#%06lX\"", m_colorActiveLink.GetAsLong() );
}
if( m_pBackgroundImage ) {
pPrintState->m_pOut->Printf(" BACKGROUND=%s", edt_MakeParamString(m_pBackgroundImage) );
}
if( m_pBodyExtra ) {
pPrintState->m_pOut->Printf(" %s", m_pBodyExtra );
}
pPrintState->m_pOut->Printf(">\n");
}
void CEditBuffer::PrintDocumentEnd( CPrintState *pPrintState ){
pPrintState->m_pOut->Printf( "\n"
"</BODY>\n"
"</HTML>\n");
}
// Setup to save file
ED_FileError CEditBuffer::SaveFile( char* pSourceURL,
char* pDestURL,
XP_Bool bSaveAs,
XP_Bool bKeepImagesWithDoc,
XP_Bool bAutoAdjustLinks ){
DoneTyping();
CEditSaveObject *pSaveObject;
if( m_pSaveObject != 0 ){
// This could happen if user is in the middle of saving a file
// (such as at an overwrite or error dialog when inserting image)
// and then AutoSave triggers saving the file. So don't ASSERT
// XP_ASSERT(0); // shouldn't be returning a value...
return ED_ERROR_BLOCKED;
}
// Set write time to 0 so we don't trigger reload before file is saved
// (doesn't completely solve this problem)
if( bSaveAs) m_iFileWriteTime = 0;
m_pSaveObject = pSaveObject = new CEditSaveObject( this, pSourceURL,
pDestURL, bSaveAs, bKeepImagesWithDoc, bAutoAdjustLinks );
pSaveObject->FindAllDocumentImages();
// This may save 1 or more files and delete itself before
// returning here. Error status (m_status) is set to pSaveObject->m_status
// via CEditBuffer::FileFetchComplete(m_status)
m_status = ED_ERROR_NONE;
pSaveObject->SaveFiles();
#if 0
ED_FileError result = pSaveObject->SaveFiles();
if ( result == ED_ERROR_NONE ) {
m_bDirty = FALSE;
}
return result;
#endif
return m_status;
}
ED_FileError CEditBuffer::WriteBufferToFile( char *pDestURL ){
XP_File hFile = NULL;
ED_FileError nError = ED_ERROR_NONE;
CEditFileBackup fb;
nError = fb.BeginTransaction( pDestURL );
if( nError != ED_ERROR_NONE ){
return nError;
}
// Note: xpTemporary gives us maximum private write permission
// but xpURL must be used (at least in Window platform)
// to prevent WH_FileName() from changing our directory or extension
#ifdef XP_MAC
hFile = XP_FileOpen(fb.FileName(), xpURL, XP_FILE_TRUNCATE );
#else
hFile = XP_FileOpen(fb.FileName(), xpTemporary, XP_FILE_TRUNCATE );
#endif
if ( hFile == NULL ) {
fb.Rollback();
return ED_ERROR_FILE_OPEN;
}
if ( 0 != WriteToFile( hFile ) ) {
fb.Rollback();
return ED_ERROR_FILE_WRITE;
}
fb.Commit();
return nError;
}
// CM: Return 0 if OK, or -1 if device is full.
int CEditBuffer::WriteToFile( XP_File hFile ){ //char *pFileName ){
int iResult = 0;
CStreamOutFile *pOutFile;
pOutFile = new CStreamOutFile(hFile, FALSE);
edt_InitEscapes(m_pContext);
printState.Reset( pOutFile, this );
Reduce( m_pRoot );
PrintTree( m_pRoot );
SetDirtyFlag( FALSE );
printState.Reset( 0, this );
if( pOutFile->Status() != IStreamOut::EOS_NoError ){
iResult = -1;
}
else {
iResult = 0;
}
delete pOutFile;
// To Jack: this is a problem
// When saving to a new file, this does nothing
// 7/2/96
// edit_saving_url flag is set/cleared in CEditSaveObject
// and GetFileWriteTime() is done in that object's destructor
// GetFileWriteTime();
m_autoSaveTimer.Restart();
return iResult;
}
void CEditBuffer::WriteToBuffer( char **ppBuffer ){
CStreamOutMemory *pOutMemory = new CStreamOutMemory();
edt_InitEscapes(m_pContext);
printState.Reset( pOutMemory, this );
DebugPrintTree( m_pRoot );
printState.Reset( 0, this );
*ppBuffer = pOutMemory->GetText();
delete pOutMemory;
}
#ifdef DEBUG
void CEditBuffer::DebugPrintState(IStreamOut& stream) {
{
CEditSelection selection;
GetSelection(selection);
stream.Printf("\nCurrent selection: ");
selection.Print(stream);
stream.Printf("\n");
}
{
CPersistentEditSelection selection;
GetSelection(selection);
stream.Printf("\nCurrent selection: ");
selection.Print(stream);
stream.Printf("\n");
}
if ( IsPhantomInsertPoint() ) {
stream.Printf(" IsPhantomInsertPoint() = TRUE\n");
}
stream.Printf("Current typing command: ");
if ( m_pTypingCommand ) {
stream.Printf("0x%08lx (should be first on Undo)", m_pTypingCommand);
}
else {
stream.Printf("none");
}
stream.Printf("\n");
m_commandLog.Print(stream);
{
stream.Printf("\n\nFiles asociated with the current Buffer:");
char*p = GetAllDocumentFiles();
char *p1 = p;
while( p && *p ){
stream.Printf("\n %s", p );
p += XP_STRLEN( p );
}
if( p1) XP_FREE( p1 );
}
{
stream.Printf("\n\nNamed Anchors in the current Buffer:");
char*p = GetAllDocumentTargets();
char *p1 = p;
while( p && *p ){
stream.Printf("\n %s", p );
p += XP_STRLEN( p );
}
if( p1 ) XP_FREE( p1 );
}
// lo_PrintLayout(m_pContext);
}
#endif
#ifdef DEBUG
void CEditBuffer::ValidateTree(){
if ( m_bSkipValidation ) return;
// Have the tree validate itself.
m_pRoot->ValidateTree();
if ( ! m_bSuppressPhantomInsertPointCheck ) {
// Check that there are no phantom insert points other than at m_pCurrent
CEditLeafElement* pElement;
for ( pElement = m_pRoot->GetFirstMostChild()->Leaf();
pElement;
pElement = pElement->NextLeafAll() ){
CEditInsertPoint ip(pElement, 0);
if ( IsPhantomInsertPoint(ip) ){
if ( pElement != m_pCurrent ) {
XP_ASSERT(FALSE);
}
}
}
}
}
void CEditBuffer::SuppressPhantomInsertPointCheck(XP_Bool bSuppress){
if ( bSuppress )
m_bSuppressPhantomInsertPointCheck++;
else
m_bSuppressPhantomInsertPointCheck--;
}
#endif
void CEditBuffer::DisplaySource( ){
CStreamOutNet *pOutNet = new CStreamOutNet( m_pContext );
edt_InitEscapes(m_pContext);
printState.Reset( pOutNet, this );
#ifdef DEBUG
DebugPrintTree( m_pRoot );
DebugPrintState(*pOutNet);
#else
PrintTree( m_pRoot );
#endif
printState.Reset( 0, this );
delete pOutNet;
}
char* CEditBuffer::NormalizeText( char* pSrc ){
char *pStr = pSrc;
char *pDest;
char lastChar = 1;
pDest = pStr;
if( GetParseState()->bLastWasSpace ){
while( *pStr != '\0' && XP_IS_SPACE( *pStr ) ){
lastChar = ' ';
pStr++;
}
}
//
// Loop through convert all white space to a single white space.
//
while( *pStr != '\0'){
if( XP_IS_SPACE( *pStr ) ){
lastChar = *pDest++ = ' ';
while( *pStr != '\0' && XP_IS_SPACE( *pStr )){
pStr++;
}
}
else {
lastChar = *pDest++ = *pStr++;
}
}
*pDest = '\0';
// if we actually coppied something, check the last character.
if( pSrc != pDest ){
GetParseState()->bLastWasSpace = XP_IS_SPACE( lastChar );
}
return pSrc;
}
void CEditBuffer::RepairAndSet(CEditSelection& selection)
{
// force layout stop displaying the current selection.
LO_StartSelectionFromElement( m_pContext, 0, 0);
CPersistentEditSelection persel = EphemeralToPersistent(selection);
Reduce(selection.GetCommonAncestor());
selection = PersistentToEphemeral(persel);
Relayout( selection.m_start.m_pElement, 0, selection.m_end.m_pElement, RELAYOUT_NOCARET );
// Need to force selection.
SetSelection(selection);
}
XP_Bool CEditBuffer::Reduce( CEditElement *pRoot ){
// work backwards so when children go away, we don't have
// lossage.
CEditElement *pChild, *pNext;
pChild = pRoot->GetChild();
while( pChild ) {
pNext = pChild->GetNextSibling();
if( Reduce( pChild )){
pChild->Unlink(); // may already be unlinked
delete pChild;
}
pChild = pNext;
}
return pRoot->Reduce( this );
}
//
//
//
void CEditBuffer::NormalizeTree(){
CEditElement* pElement;
// Make sure all text is in a container
pElement = m_pRoot;
while( (pElement = pElement->FindNextElement( &CEditElement::FindTextAll,0 ))
!= 0 ) {
// this block of text does not have a container.
if( pElement->FindContainer() == 0 ){
}
}
}
// Finish load timer.
CFinishLoadTimer::CFinishLoadTimer(){
}
void CFinishLoadTimer::OnCallback() {
m_pBuffer->FinishedLoad2();
}
void CFinishLoadTimer::FinishedLoad(CEditBuffer* pBuffer)
{
m_pBuffer = pBuffer;
const uint32 kRelayoutDelay = 1;
SetTimeout(kRelayoutDelay);
}
void CEditBuffer::FinishedLoad(){
// To avoid race conditions, set up a timer.
// An example of a race condition is when the
// save dialog box goes up when the load is
// finished, but the image
// attributes haven't been set yet.
m_finishLoadTimer.FinishedLoad(this);
}
// Relayout the whole document.
void CEditBuffer::FinishedLoad2(){
m_pCreationCursor = NULL;
// Get rid of empty items.
Reduce( m_pRoot );
// Give everyone a chance to clean themselves up
m_pRoot->FinishedLoad(this);
// May have cleared out some more items.
Reduce( m_pRoot );
#ifdef DEBUG
m_pRoot->ValidateTree();
#endif
m_pCurrent = m_pRoot->NextLeafAll();
m_iCurrentOffset = 0;
// Set page properties (color, background, etc.)
// for a new document:
if( EDT_IS_NEW_DOCUMENT(m_pContext) ){
// This will call EDT_SetPageData(),
// which calls RefreshLayout()
FE_SetNewDocumentProperties(m_pContext);
m_bDummyCharacterAddedDuringLoad = TRUE; /* Sometimes it's a no-break space. */
}
else {
RefreshLayout();
}
if ( m_bDummyCharacterAddedDuringLoad ) {
// Get rid of that damn non-breaking space character
EDT_ClipboardResult deleteResult = DeleteNextChar();
XP_ASSERT(deleteResult == EDT_COP_OK);
// EDT_SetPageData() sets this TRUE,
// but we are just a new doc, so clear it
SetDirtyFlag( FALSE );
Trim(); // Get rid of undo log in case FE_SetNewDocumentProperties called back.
}
#if 0
// Stamp charset info to newly created document
char buf[100];
XP_STRCPY(buf, "text/html;charset=");
INTL_CharSetIDToName(m_pContext->win_csid, buf + strlen(buf));
EDT_MetaData *pData = MakeMetaData( TRUE, "Content-Type", buf);
SetMetaData( pData );
FreeMetaData(pData);
#endif
char generatorValue[300];
XP_SPRINTF(generatorValue, "%.90s/%.100s [%.100s]", XP_AppCodeName, XP_AppVersion, XP_AppName);
EDT_MetaData *pData = MakeMetaData( FALSE, "GENERATOR", generatorValue);
SetMetaData( pData );
FreeMetaData( pData );
m_bBlocked = FALSE;
// Notify front end that user interaction can resume
FE_EditorDocumentLoaded( m_pContext );
}
void CEditBuffer::DummyCharacterAddedDuringLoad(){
m_bDummyCharacterAddedDuringLoad = TRUE;
}
XP_Bool CEditBuffer::IsFileModified(){
int32 last_write_time = m_iFileWriteTime;
//Get current time and save in m_iFileWriteTime,
// but NOT if in the process of saving a file
if( m_pContext->edit_saving_url ){
return FALSE;
}
GetFileWriteTime();
// Skip if current_time is 0 -- first time through
return( last_write_time != 0 && last_write_time != m_iFileWriteTime );
}
void CEditBuffer::GetFileWriteTime(){
char *pDocURL = LO_GetBaseURL(m_pContext);
char *pFilename = NULL;
// Don't set the time if we are in the process of saving the file
if( m_pContext->edit_saving_url ){
return;
}
// Must be a local file type
if( NET_IsLocalFileURL(pDocURL) &&
XP_ConvertUrlToLocalFile(pDocURL, &pFilename) ){
XP_StatStruct statinfo;
if( -1 != XP_Stat(pFilename, &statinfo, xpURL) &&
statinfo.st_mode & S_IFREG ){
m_iFileWriteTime = statinfo.st_mtime;
}
}
// pFilename is allocated even if XP_ConvertUrlToLocalFile returns FALSE
if( pFilename ){
XP_FREE(pFilename);
}
}
static void SetDocColor(MWContext* pContext, int type, ED_Color& color)
{
LO_Color *pColor = edt_MakeLoColor(color);
// If pColor is NULL, this will use the "MASTER",
// (default Browser) color, and that's OK!
LO_SetDocumentColor( pContext, type, pColor );
if( pColor) XP_FREE(pColor);
}
void CEditBuffer::RefreshLayout(){
VALIDATE_TREE(this);
SetDocColor(m_pContext, LO_COLOR_BG, m_colorBackground);
SetDocColor(m_pContext, LO_COLOR_FG, m_colorText);
SetDocColor(m_pContext, LO_COLOR_LINK, m_colorLink);
SetDocColor(m_pContext, LO_COLOR_VLINK, m_colorFollowedLink);
SetDocColor(m_pContext, LO_COLOR_ALINK, m_colorActiveLink);
LO_SetBackgroundImage( m_pContext, m_pBackgroundImage );
Relayout(m_pRoot, 0, m_pRoot->GetLastMostChild() );
}
void CEditBuffer::SetDisplayParagraphMarks(XP_Bool bDisplay) {
m_pContext->display_paragraph_marks = bDisplay;
RefreshLayout();
}
XP_Bool CEditBuffer::GetDisplayParagraphMarks() {
return m_pContext->display_paragraph_marks;
}
void CEditBuffer::SetDisplayTables(XP_Bool bDisplay) {
m_bDisplayTables = bDisplay;
RefreshLayout();
}
XP_Bool CEditBuffer::GetDisplayTables() {
return m_bDisplayTables;
}
ED_TextFormat CEditBuffer::GetCharacterFormatting( ){
CEditLeafElement *pElement = m_pCurrent;
//
// While selecting, we may not have a valid region
//
if( IsSelecting() ){
return TF_NONE;
}
if( IsSelected() ){
ElementOffset i,i1;
CEditLeafElement *pEnd;
XP_Bool bFromStart;
GetSelection( pElement, i, pEnd, i1, bFromStart );
}
if( pElement && pElement->IsA(P_TEXT )){
return pElement->Text()->m_tf;
}
return TF_NONE;
}
int CEditBuffer::GetFontSize( ){
CEditLeafElement *pElement = m_pCurrent;
if( IsSelecting() ){
return 0;
}
if( IsSelected() ){
ElementOffset i,i1;
CEditLeafElement *pEnd;
XP_Bool bFromStart;
GetSelection( pElement, i, pEnd, i1, bFromStart );
}
if( pElement && pElement->IsA(P_TEXT )){
return pElement->Text()->GetFontSize();
}
//
// The following logic is similar to CEditBuffer::GetCharacterData().
//
// Try to find the previous text element and use its font size.
if (pElement) {
CEditTextElement *tElement = pElement->PreviousTextInContainer();
if ( tElement){
return tElement->GetFontSize();
}
// Get font size from parent of pElement.
return pElement->GetDefaultFontSize();
}
return 0;
}
ED_Color CEditBuffer::GetFontColor( ){
CEditLeafElement *pElement = m_pCurrent;
if( IsSelecting() ){
return ED_Color::GetUndefined();
}
if( IsSelected() ){
ElementOffset i,i1;
CEditLeafElement *pEnd;
XP_Bool bFromStart;
GetSelection( pElement, i, pEnd, i1, bFromStart );
}
if( pElement && pElement->IsA(P_TEXT )){
return pElement->Text()->GetColor();
}
return ED_Color::GetUndefined();
}
TagType CEditBuffer::GetParagraphFormatting(){
CEditContainerElement *pCont;
#if 0
CEditLeafElement *pInsertPoint;
// The old way - returns paragraph style at beginning of selection
// even if it contains multiple styles
ElementOffset iOffset;
GetPropertyPoint( &pInsertPoint, &iOffset );
if( pInsertPoint && (pCont = pInsertPoint->FindContainer()) != 0){
return pCont->GetType();
}
else {
return P_UNKNOWN;
}
#endif
TagType type = P_UNKNOWN;
if( IsSelected() ){
// Grab the current selection.
CEditSelection selection;
GetSelection(selection);
CEditContainerElement* pStart = selection.m_start.m_pElement->FindContainer();
CEditContainerElement* pEnd = selection.m_end.m_pElement->FindContainer();
XP_Bool bUseEndContainer = !selection.EndsAtStartOfContainer();
// Scan for all text elements and save first container type found
// return that type only if all selected text elements have the same type
while( pStart ){
if( type == P_UNKNOWN ){
// First time through
type = pStart->GetType();
} else if( type != pStart->GetType() ) {
// Type is different - get out now
return P_UNKNOWN;
}
// Start = end, we're done
if( pStart == pEnd ){
break;
}
CEditElement* pLastChild = pStart->GetLastMostChild();
if ( ! pLastChild ) break;
CEditElement* pNextChild = pLastChild->NextLeaf();
if ( ! pNextChild ) break;
pStart = pNextChild->FindContainer();
if ( pStart == pEnd && ! bUseEndContainer ) break;
}
}
else {
if( m_pCurrent && (pCont = m_pCurrent->FindContainer()) != 0){
type = pCont->GetType();
}
else {
type = P_UNKNOWN;
}
}
return type;
}
void CEditBuffer::PositionCaret(int32 x, int32 y) {
VALIDATE_TREE(this);
ClearPhantomInsertPoint();
DoneTyping();
ClearMove();
LO_PositionCaret( m_pContext, x, y );
}
// this routine is called when the mouse goes down.
//
void CEditBuffer::StartSelection( int32 x, int32 y, XP_Bool doubleClick ){
VALIDATE_TREE(this);
ClearPhantomInsertPoint();
{
// This is a hack to avoid auto-scrolling to the old selection.
XP_Bool scrolling = m_inScroll;
m_inScroll = TRUE;
ClearSelection();
m_inScroll = scrolling;
}
FE_DestroyCaret(m_pContext);
ClearMove();
DoneTyping();
if ( doubleClick ) {
LO_DoubleClick( m_pContext, x, y );
}
else {
LO_Click( m_pContext, x, y, FALSE );
}
}
// this routine is called when the right mouse goes down.
//
void CEditBuffer::SelectObject( int32 x, int32 y ){
PositionCaret(x,y);
ClearSelection();
FE_DestroyCaret(m_pContext);
LO_SelectObject(m_pContext, x, y);
}
//
//
// this routine is called when the mouse is moved while down.
// at this point we actually decide that we are inside a selection.
//
void CEditBuffer::ExtendSelection( int32 x, int32 y ){
BeginSelection();
LO_ExtendSelection( m_pContext, x, y );
// may not result in an actual selection.
// if this is the case, just position the caret.
if( ! IsSelected() ) {
// If there's no selection, it is still possible that a
// selection has been started.
if ( ! LO_IsSelectionStarted( m_pContext ) ) {
StartSelection( x, y, FALSE );
}
else
{
// The selection is the empty selection.
//ClearSelection();
//FE_DestroyCaret(m_pContext);
//ClearMove();
}
}
else {
m_pCurrent = 0;
m_iCurrentOffset = 0;
}
}
void CEditBuffer::ExtendSelectionElement( CEditLeafElement *pEle, int iOffset, XP_Bool bStickyAfter ){
int iLayoutOffset;
LO_Element* pLayoutElement;
BeginSelection();
if( IsSelected() ){
LO_Element *pStart, *pEnd;
intn iStartPos, iEndPos;
XP_Bool bFromStart;
LO_GetSelectionEndPoints( m_pContext,
&pStart, &iStartPos,
&pEnd, &iEndPos,
&bFromStart, 0);
if( !(pStart == pEnd && iStartPos == iEndPos) ){
if( iOffset ) iOffset--;
}
}
else {
if( iOffset ) iOffset--;
}
// we need to convert a selection edit iOffset to a Selection
// offset. This will only work when selecting to the right...
pEle->GetLOElementAndOffset( iOffset, bStickyAfter,
pLayoutElement, iLayoutOffset );
LO_ExtendSelectionFromElement( m_pContext, pLayoutElement,
iLayoutOffset, FALSE );
}
void CEditBuffer::SelectAll(){
VALIDATE_TREE(this);
ClearPhantomInsertPoint();
ClearMove();
DoneTyping();
// Clear selection (don't resync insertion point to avoid scrolling.)
ClearSelection(FALSE);
// Destroy the cursor. Perhaps this is something ClearSelection should do.
FE_DestroyCaret(m_pContext);
// Delegate actual selection to the layout engine
if( LO_SelectAll(m_pContext) ) {
// Clear insertion point
m_pCurrent = 0;
m_iCurrentOffset = 0;
}
RevealSelection();
}
void CEditBuffer::SelectTable(){
VALIDATE_TREE(this);
ClearPhantomInsertPoint();
ClearMove();
DoneTyping();
CEditSelection selection;
GetSelection(selection);
CEditTableElement* pStartTable = selection.m_start.m_pElement->GetTableIgnoreSubdoc();
CEditSelection startAll;
if ( pStartTable ) {
pStartTable->GetAll(startAll);
}
CEditTableElement* pEndTable = selection.m_end.m_pElement->GetTableIgnoreSubdoc();
CEditSelection endAll;
if ( pEndTable ) {
pEndTable->GetAll(endAll);
}
if ( pStartTable ) {
if ( pEndTable ) {
// Both a start table and an end table.
selection.m_start = startAll.m_start;
selection.m_end = endAll.m_end;
}
else {
// Just a start table.
selection = startAll;
}
} else {
if ( pEndTable ) {
// Just an end table.
selection = endAll;
}
else {
// No table. Don't change selection.
return;
}
}
SetSelection(selection);
}
void CEditBuffer::SelectTableCell(){
VALIDATE_TREE(this);
ClearPhantomInsertPoint();
ClearMove();
DoneTyping();
CEditSelection selection;
GetSelection(selection);
CEditTableCellElement* pStartTableCell =
selection.m_start.m_pElement->GetTableCellIgnoreSubdoc();
CEditSelection startAll;
if ( pStartTableCell ) {
pStartTableCell->GetAll(startAll);
// KLUDGE ALERT: Without this, attempts to set cell data
// go to next cell. As it is, there is a bug in which
// caret is in next cell after unselecting this cell
startAll.m_bFromStart = TRUE;
AdoptAndDo(new CSetSelectionCommand(this, startAll));
}
}
void CEditBuffer::BeginSelection( XP_Bool bExtend, XP_Bool bFromStart ){
ClearMove(); // For side effect of flushing table formatting timer.
if( !IsSelected() ){
int iLayoutOffset;
CEditLeafElement *pNext;
LO_Element* pLayoutElement;
m_bSelecting = TRUE;
FE_DestroyCaret( m_pContext );
ClearPhantomInsertPoint();
#if 0
if( m_pCurrent->IsA( P_TEXT )) {
if( m_pCurrent->Text()->GetLen() == m_iCurrentOffset
&& (pNext = m_pCurrent->TextInContainerAfter())
!= 0){
pNext->Text()->GetLOElementAndOffset( 0, FALSE,
pLayoutElement, iLayoutOffset );
}
else {
m_pCurrent->Text()->GetLOElementAndOffset( m_iCurrentOffset, m_bCurrentStickyAfter,
pLayoutElement, iLayoutOffset );
}
}
#endif
// If we're at the end of an element, and the next element isn't a break,
// then move the cursor to the start of the next element.
if ( m_pCurrent->GetLen() == m_iCurrentOffset
&& (pNext = m_pCurrent->LeafInContainerAfter()) != 0
&& ! m_pCurrent->CausesBreakAfter() && ! pNext->CausesBreakBefore()){
pNext->GetLOElementAndOffset( 0, FALSE,
pLayoutElement, iLayoutOffset );
}
else {
XP_Bool good = m_pCurrent->Leaf()->GetLOElementAndOffset( m_iCurrentOffset, m_bCurrentStickyAfter,
pLayoutElement, iLayoutOffset );
if ( ! good ) {
XP_ASSERT(FALSE);
return;
}
}
// if we're selecting forward, and
// if we are before the space that cause the wrap, we should be
// starting selection from the line feed.
if( ! bFromStart && pLayoutElement->type == LO_TEXT
&& iLayoutOffset == pLayoutElement->lo_text.text_len ){
if( pLayoutElement->lo_any.next->type == LO_LINEFEED ){
pLayoutElement = pLayoutElement->lo_any.next;
iLayoutOffset = 0;
}
else{
// The layout element is shorter than we think it should
// be.
XP_ASSERT( FALSE );
}
}
XP_ASSERT( pLayoutElement );
if ( bExtend ) {
if ( ! LO_SelectElement( m_pContext, pLayoutElement,
iLayoutOffset, bFromStart ) ) {
SetCaret(); // If the selection moved off the end of the document, set the caret again.
}
}
else {
LO_StartSelectionFromElement( m_pContext, pLayoutElement,
iLayoutOffset);
}
}
}
void CEditBuffer::EndSelection(int32 /* x */, int32 /* y */){
EndSelection();
}
void CEditBuffer::EndSelection( )
{
// Clear the move just in case EndSelection is called out of order
// (this seems to be happening in the WinFE when you click a lot.)
// ClearMove has a side-effect of flushing the relayout timer.
ClearMove();
// Fix for the mouse move that does not move enough
// to show selection (kills caret)
if( LO_IsSelectionStarted(m_pContext) ){
SetCaret();
}
m_bSelecting = FALSE;
// Make the active end visible
RevealSelection();
}
void CEditBuffer::RevealSelection()
{
if ( IsSelected() ) {
CEditLeafElement* pStartElement;
ElementOffset iStartOffset;
CEditLeafElement* pEndElement;
ElementOffset iEndOffset;
XP_Bool bFromStart;
GetSelection( pStartElement, iStartOffset, pEndElement, iEndOffset, bFromStart );
if ( bFromStart ) {
RevealPosition(pStartElement, iStartOffset, FALSE);
}
else {
RevealPosition(pEndElement, iEndOffset, FALSE);
}
}
else
{
if(m_pCurrent != NULL){
RevealPosition(m_pCurrent, m_iCurrentOffset, m_bCurrentStickyAfter);
}
}
}
void CEditBuffer::SelectCurrentElement(){
FE_DestroyCaret(m_pContext);
SelectRegion( m_pCurrent, 0, m_pCurrent, 1,
TRUE, TRUE );
m_pCurrent = 0;
m_iCurrentOffset = 0;
}
void CEditBuffer::ClearSelection( XP_Bool bResyncInsertPoint, XP_Bool bKeepLeft ){
if( !bResyncInsertPoint ){
LO_StartSelectionFromElement( m_pContext, 0, 0);
return;
}
CEditSelection selection;
GetSelection(selection);
selection.ExcludeLastDocumentContainerEnd();
SetInsertPoint(*selection.GetEdge( ! bKeepLeft ));
#if 0
LO_Element *pStart, *pEnd;
intn iStartPos, iEndPos;
CEditLeafElement *pEle;
intn iOffset;
XP_Bool bFromStart;
if( IsSelected() ){
//
// Grab the current selection.
//
LO_GetSelectionEndPoints( m_pContext,
&pStart, &iStartPos,
&pEnd, &iEndPos,
&bFromStart, 0 );
XP_ASSERT( pStart );
XP_ASSERT( pEnd );
if( pStart == pEnd && iEndPos == iStartPos ){
bFromStart = TRUE;
}
LO_StartSelectionFromElement( m_pContext, 0, 0);
m_bSelecting = FALSE;
if( bFromStart || bKeepLeft ){
while( pStart && pStart->lo_any.edit_element == 0 ){
pStart = pStart->lo_any.next;
if( pStart &&pStart->type == LO_TEXT ){
iStartPos = pStart->lo_text.text_len;
if( iStartPos ) iStartPos--;
}
else {
iStartPos = 0;
}
}
if( pStart ){
pEle = pStart->lo_any.edit_element->Leaf(),
iOffset = pStart->lo_any.edit_offset+iStartPos;
SetInsertPoint( pEle, iOffset, iStartPos == 0);
}
else{
XP_ASSERT(FALSE);
// go to the end of document
NavigateChunk( FALSE, LO_NA_DOCUMENT, TRUE );
//EndOfDocument( FALSE );
}
}
else {
while( pEnd && pEnd->lo_any.edit_element == 0 ){
pEnd = pEnd->lo_any.prev;
if( pEnd && pEnd->type == LO_TEXT ){
iEndPos = pEnd->lo_text.text_len;
if( iEndPos ) iEndPos--;
}
else {
iEndPos = 0;
}
}
if( pEnd ){
pEle = pEnd->lo_any.edit_element->Leaf(),
iOffset = pEnd->lo_any.edit_offset+iEndPos;
SetInsertPoint( pEle, iOffset, iEndPos == 0 );
}
else {
XP_ASSERT(FALSE);
// go to the end of document
NavigateChunk( FALSE, LO_NA_DOCUMENT, TRUE );
}
}
}
#endif
}
void CEditBuffer::GetInsertPoint( CEditLeafElement** ppEle, ElementOffset *pOffset, XP_Bool *pbStickyAfter){
LO_Element *pStart, *pEnd;
intn iStartPos, iEndPos;
XP_Bool bFromStart;
if( IsSelected() ){
//
// Grab the current selection.
//
LO_GetSelectionEndPoints( m_pContext,
&pStart, &iStartPos,
&pEnd, &iEndPos,
&bFromStart, 0 );
XP_ASSERT( pStart );
XP_ASSERT( pEnd );
if( pStart == pEnd && iEndPos == iStartPos ){
bFromStart = TRUE;
}
if( bFromStart ){
*pbStickyAfter = iStartPos != 0;
while( pStart && pStart->lo_any.edit_element == 0 ){
pStart = pStart->lo_any.next;
if( pStart &&pStart->type == LO_TEXT ){
iStartPos = pStart->lo_text.text_len;
if( iStartPos ) iStartPos--;
}
else {
iStartPos = 0;
}
}
if( pStart ){
*ppEle = pStart->lo_any.edit_element->Leaf(),
*pOffset = pStart->lo_any.edit_offset+iStartPos;
}
else{
// LTNOTE: NEED to implement end of document:
//Insert point is at end of document
//EndOfDocument( FALSE );
XP_ASSERT(FALSE);
}
}
else {
*pbStickyAfter = iEndPos != 0;
while( pEnd && pEnd->lo_any.edit_element == 0 ){
pEnd = pEnd->lo_any.prev;
if( pEnd && pEnd->type == LO_TEXT ){
iEndPos = pEnd->lo_text.text_len;
if( iEndPos ) iEndPos--;
}
else {
iEndPos = 0;
}
}
if( pEnd ){
*ppEle = pEnd->lo_any.edit_element->Leaf(),
*pOffset = pEnd->lo_any.edit_offset+iEndPos;
}
else {
// LTNOTE: NEED to implement end of document:
//Insert point is at end of document.
//EndOfDocument( FALSE );
XP_ASSERT(FALSE);
}
}
}
else {
*ppEle = m_pCurrent->Leaf();
*pOffset = m_iCurrentOffset;
*pbStickyAfter = m_bCurrentStickyAfter;
}
if (ppEle && *ppEle && (*ppEle)->IsEndOfDocument()) {
// End of document. Move back one edit position
CEditInsertPoint ip(*ppEle, *pOffset, *pbStickyAfter);
ip = ip.PreviousPosition();
*ppEle = ip.m_pElement;
*pOffset = ip.m_iPos;
*pbStickyAfter = ip.m_bStickyAfter;
}
}
//
// The 'PropertyPoint' is the beginning of a selection or current insert
// point.
//
// Returns
// TRUE if a single element is selected like an image or hrule
// FALSE no selection or an extended selection.
//
XP_Bool CEditBuffer::GetPropertyPoint( CEditLeafElement**ppElement,
ElementOffset* pOffset ){
LO_Element *pStart, *pEnd;
intn iStartPos, iEndPos;
XP_Bool bFromStart;
XP_Bool bSingleElementSelection;
if( IsSelected() ){
//
// Grab the current selection.
//
LO_GetSelectionEndPoints( m_pContext,
&pStart, &iStartPos,
&pEnd, &iEndPos,
&bFromStart, &bSingleElementSelection );
XP_ASSERT( pStart->lo_any.edit_element );
//------------------------- Begin Removable code ------------------------
//
// LTNOTE: We are doing this in GetSelectionEndpoints, but I
// don't think we need to be doing it. If the above assert never
// happens remove this code
//
while( pStart && pStart->lo_any.edit_element == 0 ){
pStart = pStart->lo_any.next;
if( pStart &&pStart->type == LO_TEXT ){
iStartPos = pStart->lo_text.text_len;
if( iStartPos ) iStartPos--;
}
else {
iStartPos = 0;
}
}
//------------------------- End Removable code ------------------------
*ppElement = pStart->lo_any.edit_element->Leaf();
*pOffset = pStart->lo_any.edit_offset+iStartPos;
XP_ASSERT( *ppElement );
}
else {
*ppElement = m_pCurrent;
*pOffset = m_iCurrentOffset;
bSingleElementSelection = FALSE;
}
return bSingleElementSelection;
}
void CEditBuffer::SelectRegion(CEditLeafElement *pBegin, intn iBeginPos,
CEditLeafElement* pEnd, intn iEndPos, XP_Bool bFromStart, XP_Bool bForward ){
int iBeginLayoutOffset;
LO_Element* pBeginLayoutElement;
int iEndLayoutOffset;
LO_Element* pEndLayoutElement;
// If the start is an empty element, grab previous item.
if( iBeginPos == 0 && pBegin->Leaf()->GetLen() == iBeginPos ){
CEditLeafElement *pPrev = pBegin->PreviousLeafInContainer();
if( pPrev ){
pBegin = pPrev;
iBeginPos = pPrev->GetLen();
}
}
// if the end is a phantom insert point, grab the previous
// item.
if( iEndPos == 0 ){
CEditLeafElement* pPrev = pEnd->PreviousLeafInContainer();
if( pPrev ){
pEnd = pPrev;
iEndPos = pPrev->GetLen();
}
}
XP_Bool startOK = pBegin->GetLOElementAndOffset( iBeginPos, FALSE,
pBeginLayoutElement, iBeginLayoutOffset );
XP_Bool endOK = pEnd->GetLOElementAndOffset( iEndPos, FALSE,
pEndLayoutElement, iEndLayoutOffset );
XP_ASSERT(startOK);
XP_ASSERT(endOK);
if (startOK && endOK ) {
if ( ! IsSelected() ) {
FE_DestroyCaret( m_pContext );
}
// Hack for end-of-document
if ( iEndLayoutOffset < 0 ) iEndLayoutOffset = 0;
LO_SelectRegion( m_pContext, pBeginLayoutElement, iBeginLayoutOffset,
pEndLayoutElement, iEndLayoutOffset, bFromStart, bForward );
#ifdef DEBUG_EDITOR_LAYOUT
// Verify that the selection is reversible.
CEditSelection selection;
GetSelection( selection );
CEditInsertPoint a(pBegin, iBeginPos);
CEditInsertPoint b(pEnd, iEndPos);
CEditSelection original(a, b, bFromStart);
XP_ASSERT(original == selection);
#endif
}
// EndSelection();
}
void CEditBuffer::SetSelection(CEditSelection& selection)
{
if ( selection.IsInsertPoint() ) {
SetInsertPoint(selection.m_start.m_pElement, selection.m_start.m_iPos, selection.m_start.m_bStickyAfter);
}
else {
SelectRegion(selection.m_start.m_pElement, selection.m_start.m_iPos,
selection.m_end.m_pElement, selection.m_end.m_iPos,
selection.m_bFromStart);
}
}
void CEditBuffer::SetInsertPoint(CEditInsertPoint& insertPoint) {
CEditSelection selection(insertPoint, insertPoint, FALSE);
SetSelection(selection);
}
void CEditBuffer::SetInsertPoint(CPersistentEditInsertPoint& insertPoint) {
CEditInsertPoint p = PersistentToEphemeral(insertPoint);
SetInsertPoint(p);
}
void CEditBuffer::GetSelection( CEditLeafElement*& pStartElement, ElementOffset& iStartOffset,
CEditLeafElement*& pEndElement, ElementOffset& iEndOffset, XP_Bool& bFromStart ){
if( m_bLayoutBackpointersDirty ) {
XP_ASSERT(FALSE);
return;
}
LO_Element *pStart, *pEnd;
intn iStartPos, iEndPos;
XP_ASSERT( IsSelected());
//
// Grab the current selection.
//
LO_GetSelectionEndPoints( m_pContext,
&pStart, &iStartPos,
&pEnd, &iEndPos,
&bFromStart, 0 );
if ( ! pStart->lo_any.edit_element ) {
XP_ASSERT(FALSE);
// We should always be handed back an editable element.
// LTNOTE: if we blow up here it is because we have searched to the end
// of document.
while( pStart->lo_any.edit_element == 0 ){
pStart = pStart->lo_any.next;
iStartPos = 0;
}
}
pStartElement = pStart->lo_any.edit_element->Leaf();
iStartOffset = pStart->lo_any.edit_offset+iStartPos;
#if 0
// LTNOTE: Don't fixup the insert point. Layout metrics point to the
// right container.
if( iStartOffset == 0 ){
CEditElement* pPrev = pStartElement->PreviousLeafInContainer();
if( pPrev ){
pStartElement = pPrev;
iStartOffset = pPrev->Leaf()->GetLen();
}
}
#endif
if ( ! pEnd->lo_any.edit_element ) {
XP_ASSERT(FALSE); // We should always be handed back an editable element.
while( pEnd->lo_any.edit_element == 0 ){
pEnd = pEnd->lo_any.prev;
if( pEnd->type == LO_TEXT ){
iEndPos = pEnd->lo_text.text_len;
if( iEndPos ) iEndPos--;
}
else {
iEndPos = 0;
}
}
}
pEndElement = pEnd->lo_any.edit_element->Leaf();
#if 0
if( pEndElement && pEndElement->IsA( P_TEXT ) && pEndElement->Text()->GetLen() ){
iEndOffset = pEnd->lo_any.edit_offset+iEndPos+1;
}
else if ( pEndElement && pEndElement->IsA( P_TEXT ) ) {
// we are dealing with an empty paragraph
iEndOffset = 0;
}
else {
// an image or hrule.
iEndOffset = iEndPos + 1;
}
#else
// jhp The simple way seems to work better.
iEndOffset = pEnd->lo_any.edit_offset+iEndPos;
if ( iEndOffset < 0 ) {
iEndOffset = 0; // End-of-document flummery
}
// Normalize multibyte position,
// make it always start from beginning of char, end of the beginning of next char
if (pStart->type == LO_TEXT && pStart->lo_text.text
&& INTL_NthByteOfChar(m_pContext->win_csid, (char *) pStart->lo_text.text, iStartOffset+1) > 1) {
XP_TRACE(("iStartOffset = %d which is wrong for multibyte", (int)iStartOffset));
iStartOffset = INTL_NextCharIdx(m_pContext->win_csid, (unsigned char *) pStart->lo_text.text,iStartOffset);
}
if (pEnd->type == LO_TEXT && pEnd->lo_text.text
&& INTL_NthByteOfChar(m_pContext->win_csid, (char *) pEnd->lo_text.text, iEndOffset+1) > 1) {
XP_TRACE(("iEndOffset = %d which is wrong for multibyte", (int)iEndOffset));
iEndOffset = INTL_NextCharIdx(m_pContext->win_csid, (unsigned char *) pStart->lo_text.text,iEndOffset);
}
#endif
}
void CEditBuffer::MakeSelectionEndPoints( CEditSelection& selection,
CEditLeafElement*& pBegin, CEditLeafElement*& pEnd ){
#ifdef DEBUG
// If the selection is busted, we're in big trouble.
CPersistentEditSelection persel = EphemeralToPersistent(selection);
XP_ASSERT ( persel.m_start.m_index <= persel.m_end.m_index);
#endif
pEnd = selection.m_end.m_pElement->Divide( selection.m_end.m_iPos )->Leaf();
pBegin = selection.m_start.m_pElement->Divide( selection.m_start.m_iPos )->Leaf();
m_bLayoutBackpointersDirty = TRUE;
}
void CEditBuffer::MakeSelectionEndPoints( CEditLeafElement*& pBegin,
CEditLeafElement*& pEnd ){
CEditSelection selection;
GetSelection(selection);
MakeSelectionEndPoints(selection, pBegin, pEnd);
}
int CEditBuffer::Compare( CEditElement *p1, int i1Offset,
CEditElement *p2, int i2Offset ) {
CEditPositionComparable *pPos1, *pPos2;
int iRetVal;
pPos1 = new CEditPositionComparable( p1, i1Offset);
pPos2 = new CEditPositionComparable( p2, i2Offset);
// find which one is really the beginning.
iRetVal = pPos1->Compare( pPos2 );
delete pPos1;
delete pPos2;
return iRetVal;
}
// Prevent recursive calls for autosaving
XP_Bool CEditBuffer::m_bAutoSaving = FALSE;
void CEditBuffer::AutoSaveCallback() {
if ( m_bAutoSaving || IsBlocked() ) {
XP_TRACE(("Auto Save ignored -- busy."));
return;
}
if ( ! m_bDirty ) {
XP_TRACE(("Skipping AutoSave because we're not dirty."));
return;
}
// Skip trying to save if we are already in the process of saving a file
if( m_pContext->edit_saving_url ) {
XP_TRACE(("Skipping AutoSave because we're not already saving a file."));
return;
}
XP_TRACE(("Auto Save...."));
m_bAutoSaving = TRUE;
if( EDT_IS_NEW_DOCUMENT(m_pContext) ){
// New document -- prompt front end to get filename and "save file as"
if( !FE_CheckAndAutoSaveDocument(m_pContext) ){
// User canceled the Autosave prompt, turn off autosave
SetAutoSavePeriod(0);
}
} else {
History_entry * hist_entry = SHIST_GetCurrent(&(m_pContext->hist));
if(hist_entry && hist_entry->address)
{
char *szLocalFile = NULL;
if ( XP_ConvertUrlToLocalFile( hist_entry->address, &szLocalFile ) )
{
char buf[300];
PR_snprintf(buf, sizeof(buf)-1, XP_GetString(XP_EDITOR_AUTO_SAVE), szLocalFile);
FE_Progress(m_pContext, buf);
ED_FileError nResult = EDT_SaveFile(m_pContext,hist_entry->address, // Source
hist_entry->address, // Destination
FALSE, // Not doing "SaveAs"
FALSE,
FALSE );
}
if (szLocalFile){
XP_FREE(szLocalFile);
}
}
}
m_bAutoSaving = FALSE;
}
void CEditBuffer::FileFetchComplete(ED_FileError status) {
m_status = status;
FE_Progress(m_pContext, NULL);
RefreshLayout();
}
void CEditBuffer::SetAutoSavePeriod(int32 minutes) {
m_autoSaveTimer.SetPeriod(minutes);
}
int32 CEditBuffer::GetAutoSavePeriod() {
return m_autoSaveTimer.GetPeriod();
}
void CEditBuffer::ClearMove(XP_Bool bFlushRelayout){
if ( bFlushRelayout ) {
m_relayoutTimer.Flush();
}
m_iDesiredX = -1;
}
void CEditBuffer::FormatCharacterSelection( ED_TextFormat tf ){
VALIDATE_TREE(this);
CEditLeafElement *pCurrent, *pEnd, *pBegin, *pNext;
XP_Bool bSet;
XP_Bool bHaveSet = FALSE;
SetDirtyFlag( TRUE );
//
// Guarantee that the text blocks of the beginning and end of selection
// are atomic.
//
MakeSelectionEndPoints( pBegin, pEnd );
pCurrent = pBegin;
while( pCurrent != pEnd && pCurrent ){
pNext = pCurrent->NextLeafAll();
XP_ASSERT(pNext);
if( pCurrent->IsA( P_TEXT ) ){
// figure out weather we are setting or clearing...
if( !bHaveSet ) {
bSet = !(pCurrent->Text()->m_tf & tf);
bHaveSet = TRUE;
}
if( tf == TF_NONE ){
pCurrent->Text()->ClearFormatting();
}
else if (tf == TF_SERVER || tf == TF_SCRIPT ){
pCurrent->Text()->ClearFormatting();
pCurrent->Text()->m_tf = tf;
}
else {
if( bSet && pCurrent->IsA( P_TEXT ) ){
// If we are in either Java Script type, IGNORE change in format
if( ED_IS_NOT_SCRIPT(pCurrent) ){
// Make superscript and subscript mutually-exclusive
if( tf == TF_SUPER && (pCurrent->Text()->m_tf & TF_SUB) ){
pCurrent->Text()->m_tf &= ~TF_SUB;
} else if( tf == TF_SUB && (pCurrent->Text()->m_tf & TF_SUPER) ){
pCurrent->Text()->m_tf &= ~TF_SUPER;
}
pCurrent->Text()->m_tf |= tf;
}
}
else {
pCurrent->Text()->m_tf &= ~tf;
}
}
}
else if( pCurrent->IsImage() && tf == TF_NONE ){
// Clear the link for the image within the selection
pCurrent->Image()->SetHREF( ED_LINK_ID_NONE );
}
pCurrent = pNext;
}
CEditSelection tmp(pBegin, 0, pEnd, 0);
RepairAndSet(tmp);
}
void CEditBuffer::SetFontSizeSelection( int iSize){
VALIDATE_TREE(this);
CEditLeafElement *pCurrent, *pEnd, *pBegin, *pNext;
SetDirtyFlag( TRUE );
//
// Guarantee that the text blocks of the beginning and end of selection
// are atomic.
//
MakeSelectionEndPoints( pBegin, pEnd );
pCurrent = pBegin;
while( pCurrent != pEnd ){
pNext = pCurrent->NextLeafAll();
if( pCurrent->IsA( P_TEXT ) &&
ED_IS_NOT_SCRIPT(pCurrent) ){
pCurrent->Text()->SetFontSize(iSize);
}
pCurrent = pNext;
}
// force layout stop displaying the current selection.
LO_StartSelectionFromElement( m_pContext, 0, 0);
// probably needs to be the common ancestor.
Relayout( pBegin, 0, pEnd, RELAYOUT_NOCARET );
// Need to force selection.
SelectRegion(pBegin, 0, pEnd, 0);
Reduce(pBegin->GetCommonAncestor(pEnd));
}
void CEditBuffer::SetFontColorSelection( ED_Color iColor ){
VALIDATE_TREE(this);
CEditLeafElement *pCurrent, *pEnd, *pBegin, *pNext;
SetDirtyFlag( TRUE );
//
// Guarantee that the text blocks of the beginning and end of selection
// are atomic.
//
MakeSelectionEndPoints( pBegin, pEnd );
pCurrent = pBegin;
while( pCurrent != pEnd ){
pNext = pCurrent->NextLeafAll();
if( pCurrent->IsA( P_TEXT ) ){
pCurrent->Text()->SetColor( iColor );
}
pCurrent = pNext;
}
// force layout stop displaying the current selection.
LO_StartSelectionFromElement( m_pContext, 0, 0);
// probably needs to be the common ancestor.
Relayout( pBegin, 0, pEnd, RELAYOUT_NOCARET );
// Need to force selection.
SelectRegion(pBegin, 0, pEnd, 0);
Reduce(pBegin->GetCommonAncestor(pEnd));
}
void CEditBuffer::SetHREFSelection( ED_LinkId id ){
VALIDATE_TREE(this);
CEditLeafElement *pCurrent, *pEnd, *pBegin, *pNext;
SetDirtyFlag( TRUE );
//
// Guarantee that the text blocks of the beginning and end of selection
// are atomic.
//
MakeSelectionEndPoints( pBegin, pEnd );
pCurrent = pBegin;
while( pCurrent != pEnd ){
pNext = pCurrent->NextLeafAll();
pCurrent->Leaf()->SetHREF( id );
pCurrent = pNext;
}
CEditSelection tmp(pBegin, 0, pEnd, 0);
RepairAndSet(tmp);
}
EDT_ClipboardResult CEditBuffer::DeleteSelection(CEditSelection& selection, XP_Bool bCopyAppendAttributes){
SetSelection(selection);
return DeleteSelection(bCopyAppendAttributes);
}
EDT_ClipboardResult CEditBuffer::DeleteSelection(XP_Bool bCopyAppendAttributes){
VALIDATE_TREE(this);
EDT_ClipboardResult result = CanCut(TRUE);
if ( result == EDT_COP_OK ){
if ( IsSelected() ) {
CEditLeafElement *pBegin;
CEditLeafElement *pEnd;
CEditSelection selection;
GetSelection(selection);
if ( selection.ContainsLastDocumentContainerEnd() ) {
selection.ExcludeLastDocumentContainerEnd();
SetSelection(selection);
}
if ( selection.IsInsertPoint() ) {
return result;
}
MakeSelectionEndPoints( selection, pBegin, pEnd );
SetDirtyFlag( TRUE );
DeleteBetweenPoints( pBegin, pEnd, bCopyAppendAttributes );
}
}
return result;
}
CPersistentEditSelection CEditBuffer::GetEffectiveDeleteSelection(){
// If the selection includes the end of the document, then the effective selection is
// less than that.
CEditSelection selection;
GetSelection(selection);
selection.ExcludeLastDocumentContainerEnd();
selection.ExpandToIncludeFragileSpaces();
return EphemeralToPersistent(selection);
}
//
// Get comparable positions to the
//
void CEditBuffer::DeleteBetweenPoints( CEditLeafElement* pBegin, CEditLeafElement* pEnd,
XP_Bool bCopyAppendAttributes){
CEditLeafElement *pCurrent;
CEditLeafElement *pNext;
SetDirtyFlag( TRUE );
pCurrent = pBegin;
XP_ASSERT( pEnd != 0 );
CEditLeafElement* pPrev = pCurrent->PreviousLeafInContainer();
CEditElement* pCommonAncestor = pBegin->GetCommonAncestor(pEnd);
CEditElement* pContainer = pCurrent->FindContainer();
CEditElement* pCurrentContainer;
while( pCurrent != pEnd ){
pNext = pCurrent->NextLeafAll();
// if we've entered a new container, merge it.
pCurrentContainer = pCurrent->FindContainer();
// DeleteElement removes empty paragraphs so we don't have to do the merges.
//
//if( pContainer != pCurrentContainer ){
// pContainer->Merge( pCurrentContainer );
//}
#ifdef DEBUG
m_pRoot->ValidateTree();
#endif
if( pCurrent->DeleteElement( pContainer )) {
pContainer = pNext->FindContainer();
}
#ifdef DEBUG
m_pRoot->ValidateTree();
#endif
pCurrent = pNext;
}
//
// if the selection spans partial paragraphs, merge the remaining paragraphs
//
if( pPrev && pPrev->FindContainer() != pEnd->FindContainer() ){
pPrev->FindContainer()->Merge( pEnd->FindContainer(), bCopyAppendAttributes );
}
m_pCurrent = pEnd->PreviousLeafInContainer();
if( m_pCurrent != 0 ){
m_iCurrentOffset = m_pCurrent->Leaf()->GetLen();
}
else {
m_pCurrent = pEnd;
m_iCurrentOffset = 0;
}
FixupSpace();
// probably needs to be the common ancestor.
ClearSelection(FALSE);
CEditInsertPoint tmp(pEnd,0);
CPersistentEditInsertPoint end2 = EphemeralToPersistent(tmp);
Reduce( pCommonAncestor );
SetCaret();
if ( pPrev && pPrev->FindContainer() != pContainer ) {
Reduce( pPrev->FindContainer() );
}
CEditInsertPoint end3 = PersistentToEphemeral(end2);
Relayout( pContainer, 0, end3.m_pElement);
}
//
// LTNOTE: this routine needs to be broken into PasteText and PasteFormattedText
// It didn't happen because we were too close to ship.
//
EDT_ClipboardResult CEditBuffer::PasteText( char *pText ){
VALIDATE_TREE(this);
SUPPRESS_PHANTOMINSERTPOINTCHECK(this);
EDT_ClipboardResult result = CanPaste(TRUE);
if ( result != EDT_COP_OK ) return result;
SetDirtyFlag( TRUE );
m_bNoRelayout = TRUE;
int iCharsOnLine = 0;
if( IsSelected() ){
DeleteSelection();
}
FixupInsertPoint();
// If the pasted text starts with returns, the start element can end up
// being moved down the document as paragraphs are inserted above it.
// So we hold onto the start as a persistent insert point.
CPersistentEditInsertPoint persistentStart;
GetInsertPoint(persistentStart);
CEditElement *pEnd = m_pCurrent;
// If we're in preformatted text, every return is a break.
// If we're in normal text, single returns are spaces,
// double returns are paragraph marks.
//XP_Bool bInFormattedText = m_pCurrent->FindContainer()->GetType() == P_PREFORMAT;
XP_Bool bInFormattedText = m_pCurrent->InFormattedText();
XP_Bool bLastCharWasReturn = FALSE;
while( *pText ){
XP_Bool bReturn = FALSE;
if( *pText == 0x0d ){
if( *(pText+1) == 0xa ){
pText++;
}
bReturn = TRUE;
}
else if ( *pText == 0xa ){
bReturn = TRUE;
}
if( bReturn ){
if ( bInFormattedText ) {
InsertBreak(ED_BREAK_NORMAL, FALSE);
iCharsOnLine = 0;
}
else {
if ( bLastCharWasReturn ){
ReturnKey(FALSE);
iCharsOnLine = 0;
bLastCharWasReturn = FALSE;
}
else {
// Remember this return for later. It will
// become either a space or a return.
bLastCharWasReturn = TRUE;
}
}
}
else {
if ( bLastCharWasReturn ) {
InsertChar(' ', FALSE);
bLastCharWasReturn = FALSE;
}
if ( *pText == '\t' ){
if ( bInFormattedText ) {
do {
InsertChar(' ', FALSE);
iCharsOnLine++;
} while( iCharsOnLine % DEF_TAB_WIDTH != 0 );
}
else {
InsertChar(' ', FALSE);
iCharsOnLine++;
}
}
else {
InsertChar( *pText, FALSE );
iCharsOnLine++;
}
bLastCharWasReturn = FALSE;
}
pText++;
}
if ( bLastCharWasReturn ) {
InsertChar(' ', FALSE);
}
m_bNoRelayout = FALSE;
Reduce(m_pRoot);
CEditInsertPoint start = PersistentToEphemeral(persistentStart);
Relayout( start.m_pElement, start.m_iPos, pEnd );
return result;
}
EDT_ClipboardResult CEditBuffer::PasteExternalHTML( char* /* pText */ ){
VALIDATE_TREE(this);
EDT_ClipboardResult result = CanPaste(TRUE);
if ( result != EDT_COP_OK ) return result;
// Create a new EDT buffer
return result;
}
EDT_ClipboardResult CEditBuffer::PasteHTML( char *pBuffer, XP_Bool bUndoRedo ){
CStreamInMemory stream(pBuffer);
return PasteHTML(stream, bUndoRedo);
}
EDT_ClipboardResult CEditBuffer::PasteHTML( IStreamIn& stream, XP_Bool /* bUndoRedo */ ){
VALIDATE_TREE(this);
EDT_ClipboardResult result = CanPaste(TRUE);
if ( result != EDT_COP_OK ) return result;
m_bNoRelayout = TRUE;
SetDirtyFlag( TRUE );
if( IsSelected() ){
DeleteSelection();
}
FixupInsertPoint();
#ifdef DEBUG
m_pRoot->ValidateTree();
#endif
CEditElement* pStart = m_pCurrent;
int iStartOffset = m_iCurrentOffset;
CEditLeafElement* pRight = m_pCurrent->Divide(m_iCurrentOffset)->Leaf();
XP_ASSERT(pRight->FindContainer());
XP_Bool bAtStartOfParagraph = pRight->PreviousLeafInContainer() == NULL;
CEditLeafElement* pLeft = pRight->PreviousLeaf(); // Will be NULL at start of document
// The first thing in the buffer is a flag that tells us if we need to merge the end.
int32 bMergeEnd = stream.ReadInt();
// The buffer has zero or more elements. (The case where there's more than one is
// the one where there are multiple paragraphs.)
CEditElement* pFirstInserted = NULL;
CEditElement* pLastInserted = NULL;
{
CEditElement* pElement;
while ( NULL != (pElement = CEditElement::StreamCtor(&stream, this)) ){
if ( pElement->IsLeaf() ) {
XP_ASSERT(FALSE);
delete pElement;
continue;
}
#ifdef DEBUG
pElement->ValidateTree();
#endif
if ( ! pFirstInserted ) {
pFirstInserted = pElement;
if ( ! bAtStartOfParagraph ){
XP_ASSERT(pRight->FindContainer());
InternalReturnKey(FALSE);
XP_ASSERT(pRight->FindContainer());
}
}
pLastInserted = pElement;
pElement->InsertBefore(pRight->FindContainer());
#ifdef DEBUG
m_pRoot->ValidateTree();
#endif
}
}
#ifdef DEBUG
m_pRoot->ValidateTree();
#endif
if ( pFirstInserted ) {
CEditElement* pLast = pLastInserted->GetLastMostChild()->NextLeafAll();
// Get the children early, because if we paste a single container,
// it will be deleted when we merge the left edge
CEditLeafElement* pFirstMostNewChild = pFirstInserted->GetFirstMostChild()->Leaf();
CEditLeafElement* pLastMostNewChild = pLastInserted->GetLastMostChild()->Leaf();
m_pCurrent = pLastMostNewChild;
m_iCurrentOffset = pLastMostNewChild->GetLen();
ClearPhantomInsertPoint();
if ( ! bAtStartOfParagraph ){
if ( pLeft ) {
CEditContainerElement* pLeftContainer = pLeft->FindContainer();
CEditContainerElement* pFirstNewContainer =
pFirstMostNewChild->FindContainer();
if ( pLeftContainer != pFirstNewContainer ){
XP_Bool bCopyNewAttributes = FALSE;
pLeftContainer->Merge(pFirstNewContainer);
// Now deleted in Merge delete pFirstNewContainer;
FixupInsertPoint();
}
}
}
else {
// The insert went into the container before us. Adjust the start.
pStart = pFirstInserted->GetFirstMostChild();
iStartOffset = 0;
}
if ( bMergeEnd ) {
CEditContainerElement* pRightContainer = pRight->FindContainer();
CEditContainerElement* pLastNewContainer = pLastMostNewChild->FindContainer();
if ( pRightContainer != pLastNewContainer ){
pLastNewContainer->Merge(pRightContainer);
// Now deleted in Merge delete pRightContainer;
FixupInsertPoint();
}
}
else {
// Move to beginning of next container
m_pCurrent = pLastMostNewChild->NextContainer()->GetFirstMostChild()->Leaf();
m_iCurrentOffset = 0;
}
// Have to FinishedLoad after merging because FinishedLoad is eager to
// remove spaces from text element at the end of containers. If we let
// FinishedLoad run earlier, we couldn't paste text that ended in white space.
m_pRoot->FinishedLoad(this);
#ifdef DEBUG
m_pRoot->ValidateTree();
#endif
m_bNoRelayout = FALSE;
FixupSpace( );
// We need to reduce before we relayout because otherwise
// null insert points mess up the setting of end-of-paragraph
// marks.
CEditInsertPoint tmp(pLast,0);
CPersistentEditInsertPoint end2 = EphemeralToPersistent(tmp);
Reduce( m_pRoot );
CEditInsertPoint end3 = PersistentToEphemeral(end2);
Relayout( pStart, iStartOffset, end3.m_pElement );
}
else {
m_bNoRelayout = FALSE;
}
return result;
}
EDT_ClipboardResult CEditBuffer::PasteHREF( char **ppHref, char **ppTitle, int iCount){
VALIDATE_TREE(this);
SUPPRESS_PHANTOMINSERTPOINTCHECK(this);
EDT_ClipboardResult result = CanPaste(TRUE);
if ( result != EDT_COP_OK ) return result;
m_bNoRelayout = TRUE;
XP_Bool bFirst = TRUE;
SetDirtyFlag( TRUE );
if( IsSelected() ){
DeleteSelection();
}
FixupInsertPoint();
FormatCharacter( TF_NONE );
InsertChar(' ', FALSE);
CEditElement *pStart = m_pCurrent;
int iStartOffset = m_iCurrentOffset;
m_pCurrent->Divide( m_iCurrentOffset );
int i = 0;
while( i < iCount ){
char *pTitle = ppTitle[i];
if( pTitle == 0 ){
pTitle = ppHref[i];
}
else {
// LTNOTE:
// probably shouldn't be doing this to a buffer we were passed
// but what the hell.
NormalizeText( pTitle );
// kill any trailing spaces..
int iLen = XP_STRLEN( pTitle )-1;
while( iLen >= 0 && pTitle[iLen] == ' ' ){
pTitle[iLen] = 0;
iLen--;
}
if( *pTitle == 0 ){
pTitle = ppHref[i];
}
}
CEditTextElement *pElement = new CEditTextElement( 0, pTitle );
pElement->SetHREF( linkManager.Add( ppHref[i], 0 ));
if( bFirst && m_iCurrentOffset == 0 ){
pElement->InsertBefore( m_pCurrent );
pStart = pElement;
}
else {
pElement->InsertAfter( m_pCurrent );
}
if( bFirst ){
FixupSpace();
bFirst = FALSE;
}
m_pCurrent = pElement;
m_iCurrentOffset = pElement->Text()->GetLen();
i++;
}
m_bNoRelayout = FALSE;
FixupSpace( );
// Fixes bug 21920 - Crash when dropping link into blank document.
// We might have an empty text container just after m_pCurrent.
Reduce(m_pRoot);
FormatCharacter( TF_NONE );
InsertChar(' ', FALSE);
Relayout( pStart, iStartOffset, m_pCurrent );
Reduce( m_pRoot );
return result;
}
//
// Make sure that there are not spaces next to each other after a delete,
// or paste
//
void CEditBuffer::FixupSpace( XP_Bool bTyping){
if( m_pCurrent->InFormattedText() ){
return;
}
if( m_pCurrent->IsBreak() && m_iCurrentOffset == 1 ){
// Can't have a space after a break.
CEditLeafElement *pNext = m_pCurrent->TextInContainerAfter();
if( pNext
&& pNext->IsA(P_TEXT)
&& pNext->Text()->GetLen() != 0
&& pNext->Text()->GetText()[0] == ' '){
if ( bTyping ) {
GetTypingCommand()->AddCharDeleteSpace();
}
pNext->Text()->DeleteChar(m_pContext, 0);
return;
}
}
if( !m_pCurrent->IsA(P_TEXT) ){
return;
}
CEditTextElement *pText = m_pCurrent->Text();
if( pText->GetLen() == 0 ){
return;
}
if( m_iCurrentOffset > 0 && pText->GetText()[ m_iCurrentOffset-1 ] == ' ' ){
if( m_iCurrentOffset == pText->GetLen() ){
CEditLeafElement *pNext = pText->TextInContainerAfter();
if( pNext
&& pNext->IsA(P_TEXT)
&& pNext->Text()->GetLen() != 0
&& pNext->Text()->GetText()[0] == ' '){
if ( bTyping ) {
GetTypingCommand()->AddCharDeleteSpace();
}
pNext->Text()->DeleteChar(m_pContext, 0);
//m_iCurrentOffset--; // WHy???
return;
}
}
else if( m_iCurrentOffset < pText->GetLen() && pText->GetText()[m_iCurrentOffset] == ' ' ){
pText->DeleteChar(m_pContext, m_iCurrentOffset);
return;
}
}
// check for beginning of paragraph with a space.
else if( m_iCurrentOffset == 0 && pText->GetText()[0] == ' ' ){
pText->DeleteChar(m_pContext, 0);
CEditLeafElement *pNext = m_pCurrent->TextInContainerAfter();
if( m_pCurrent->Text()->GetLen() == 0 && pNext ){
m_pCurrent = pNext;
}
}
// Can't reduce here because it might smash objects pointers we know about
}
EDT_ClipboardResult CEditBuffer::CutSelection( char **ppText, int32* pTextLen,
char **ppHtml, int32* pHtmlLen){
VALIDATE_TREE(this);
EDT_ClipboardResult result = CanCut(TRUE);
if ( result != EDT_COP_OK ) return result;
result = CopySelection( ppText, pTextLen, ppHtml, pHtmlLen );
if ( result != EDT_COP_OK ) return result;
CPersistentEditSelection selection = GetEffectiveDeleteSelection();
CChangeAttributesCommand* pCommand =
new CChangeAttributesCommand(this, selection, kCutCommandID);
result = DeleteSelection();
#if 0
// Old voo doo
ClearPhantomInsertPoint();
FixupInsertPoint();
#endif
AdoptAndDo(pCommand);
// Check to see if we trashed the document
XP_ASSERT ( m_pCurrent && m_pCurrent->GetElementType() != eEndElement );
return result;
}
XP_Bool CEditBuffer::CutSelectionContents( CEditSelection& selection,
char **ppHtml, int32* pHtmlLen ){
XP_Bool result = CopySelectionContents( selection, ppHtml, pHtmlLen );
if ( result ) {
CEditLeafElement *pBegin;
CEditLeafElement *pEnd;
MakeSelectionEndPoints( selection, pBegin, pEnd );
DeleteBetweenPoints( pBegin, pEnd );
}
return TRUE;
}
EDT_ClipboardResult CEditBuffer::CopySelection( char **ppText, int32* pTextLen,
char **ppHtml, int32* pHtmlLen){
EDT_ClipboardResult result = CanCopy(TRUE);
if ( result != EDT_COP_OK ) return result;
if ( ppText ) *ppText = (char*) LO_GetSelectionText( m_pContext );
if ( pTextLen ) *pTextLen = XP_STRLEN( *ppText );
CEditSelection selection;
GetSelection(selection);
CopySelectionContents( selection, ppHtml, pHtmlLen );
return result;
}
XP_Bool CEditBuffer::CopyBetweenPoints( CEditElement *pBegin,
CEditElement *pEnd, char **ppText, int32* pTextLen,
char **ppHtml, int32* pHtmlLen ){
if ( ppText ) *ppText = (char*) LO_GetSelectionText( m_pContext );
if ( pTextLen ) *pTextLen = XP_STRLEN( *ppText );
CEditInsertPoint a(pBegin, 0);
CEditInsertPoint b(pEnd, 0);
CEditSelection s(a,b);
return CopySelectionContents(s, ppHtml, pHtmlLen);
}
XP_Bool CEditBuffer::CopySelectionContents( CEditSelection& selection,
char **ppHtml, int32* pHtmlLen ){
CStreamOutMemory stream;
XP_Bool result = CopySelectionContents(selection, stream);
*ppHtml = stream.GetText();
*pHtmlLen = stream.GetLen();
return result;
}
XP_Bool CEditBuffer::CopySelectionContents( CEditSelection& selection,
IStreamOut& stream ){
int32 bMergeEnd = ! selection.EndsAtStartOfContainer();
stream.WriteInt(bMergeEnd);
m_pRoot->PartialStreamOut(&stream, selection);
stream.WriteInt( (int32)eElementNone );
return TRUE;
#if 0
CEditElement *pCurrent;
CEditElement *pNext;
pCurrent = selection.m_start.m_pElement;
if( selection.m_end.m_pElement == 0 ) {
XP_ASSERT(FALSE);
return FALSE;
}
CEditElement* pContainer = pCurrent->FindContainer();
CEditElement* pCurrentContainer;
// The containers are copied out in postfix style.
while( pCurrent != selection.m_end.m_pElement ){
pNext = pCurrent->NextLeafAll();
if ( pCurrent == selection.m_start.m_pElement && selection.m_start.m_iPos > 0 ) {
pCurrent->PartialStreamOut( &stream, selection );
}
else {
pCurrent->StreamOut( &stream );
}
pCurrent = pNext;
// if we've entered a new container, copy the old container.
pCurrentContainer = pCurrent->FindContainer();
if( pContainer != pCurrentContainer ){
pContainer->StreamOut( &stream );
pContainer = pCurrentContainer;
}
}
// Write last element
if ( pCurrent == selection.m_start.m_pElement ) {
// Only one element
pCurrent->PartialStreamOut( &stream, selection );
}
else if ( selection.m_end.m_iPos > 0 ) {
// if we've entered a new container, merge it.
pCurrentContainer = pCurrent->FindContainer();
if( pContainer != pCurrentContainer ){
pCurrentContainer->StreamOut( &stream );
pContainer = pCurrentContainer;
}
if ( selection.m_end.m_iPos < pCurrent->Leaf()->GetLen() ) {
pCurrent->PartialStreamOut( &stream, selection );
}
else {
pCurrent->StreamOut( &stream );
}
}
stream.WriteInt( eElementNone );
return TRUE;
#endif
}
//
// Used during parse phase.
//
ED_Alignment CEditBuffer::GetCurrentAlignment(){
if( GetParseState()->m_formatAlignStack.IsEmpty()){
return m_pCreationCursor->GetDefaultAlignment();
}
else {
return GetParseState()->m_formatAlignStack.Top();
}
}
void CEditBuffer::GetSelection( CEditSelection& selection ){
if ( IsSelected() ) {
GetSelection( selection.m_start.m_pElement, selection.m_start.m_iPos,
selection.m_end.m_pElement, selection.m_end.m_iPos,
selection.m_bFromStart);
}
else {
GetInsertPoint( &selection.m_start.m_pElement, &selection.m_start.m_iPos, &selection.m_start.m_bStickyAfter);
selection.m_end = selection.m_start;
selection.m_bFromStart = FALSE;
}
}
void CEditBuffer::GetSelection( CPersistentEditSelection& persistentSelection ){
CEditSelection selection;
GetSelection(selection);
persistentSelection = EphemeralToPersistent(selection);
}
void CEditBuffer::GetInsertPoint(CEditInsertPoint& insertPoint){
if ( ! IsSelected() )
{
GetInsertPoint( & insertPoint.m_pElement, & insertPoint.m_iPos, & insertPoint.m_bStickyAfter );
}
else
{
CEditSelection selection;
GetSelection(selection);
selection.ExcludeLastDocumentContainerEnd();
insertPoint = *selection.GetActiveEdge();
}
}
void CEditBuffer::GetInsertPoint(CPersistentEditInsertPoint& insertPoint){
CEditInsertPoint pt;
GetInsertPoint(pt);
insertPoint = EphemeralToPersistent(pt);
}
void CEditBuffer::SetSelection(CPersistentEditSelection& persistentSelection){
CEditSelection selection = PersistentToEphemeral(persistentSelection);
SetSelection(selection);
}
void CEditBuffer::CopyEditText(CEditText& text){
text.Clear();
if ( IsSelected() ) {
XP_Bool copyOK = CopySelection(NULL, NULL, text.GetPChars(), text.GetPLength());
XP_ASSERT(copyOK);
}
}
void CEditBuffer::CopyEditText(CPersistentEditSelection& persistentSelection, CEditText& text){
text.Clear();
if ( ! persistentSelection.IsInsertPoint() ) {
CEditSelection selection = PersistentToEphemeral(persistentSelection);
CopySelectionContents(selection, text.GetPChars(), text.GetPLength());
}
}
void CEditBuffer::CutEditText(CEditText& text){
text.Clear();
CEditSelection selection;
GetSelection(selection);
if ( ! selection.IsInsertPoint() ) {
CutSelectionContents(selection, text.GetPChars(), text.GetPLength());
}
}
void CEditBuffer::PasteEditText(CEditText& text){
if ( text.Length() > 0 )
PasteHTML( text.GetChars(), TRUE);
}
// Persistent to regular selection conversion routines
CEditInsertPoint CEditBuffer::PersistentToEphemeral(CPersistentEditInsertPoint& persistentInsertPoint){
CEditInsertPoint result = m_pRoot->IndexToInsertPoint(
persistentInsertPoint.m_index, persistentInsertPoint.m_bStickyAfter);
#ifdef DEBUG_EDITOR_LAYOUT
// Check for reversability
CPersistentEditInsertPoint p2 = result.m_pElement->GetPersistentInsertPoint(result.m_iPos);
XP_ASSERT(persistentInsertPoint == p2);
// Check for legality.
if ( result.m_pElement->IsEndOfDocument() &&
result.m_iPos != 0){
XP_ASSERT(FALSE);
result.m_iPos = 0;
}
#endif
return result;
}
CPersistentEditInsertPoint CEditBuffer::EphemeralToPersistent(CEditInsertPoint& insertPoint){
CPersistentEditInsertPoint result = insertPoint.m_pElement->GetPersistentInsertPoint(insertPoint.m_iPos);
#ifdef DEBUG_EDITOR_LAYOUT
// Check for reversability
CEditInsertPoint p2 = m_pRoot->IndexToInsertPoint(result.m_index);
if ( ! ( p2 == insertPoint ||
insertPoint.IsDenormalizedVersionOf(p2) ) ) {
// One or the other is empty.
if ( insertPoint.m_pElement->Leaf()->GetLen() == 0 ||
p2.m_pElement->Leaf()->GetLen() == 0 ) {
// OK, do they at least have the same persistent point?
CPersistentEditInsertPoint pp2 = p2.m_pElement->GetPersistentInsertPoint(p2.m_iPos);
XP_ASSERT(result == pp2);
}
else
{
// Just wrong.
XP_ASSERT(FALSE);
}
}
#endif
return result;
}
CEditSelection CEditBuffer::PersistentToEphemeral(CPersistentEditSelection& persistentSelection){
CEditSelection selection(PersistentToEphemeral(persistentSelection.m_start),
PersistentToEphemeral(persistentSelection.m_end),
persistentSelection.m_bFromStart);
XP_ASSERT(!selection.m_start.IsEndOfDocument());
return selection;
}
CPersistentEditSelection CEditBuffer::EphemeralToPersistent(CEditSelection& selection){
CPersistentEditSelection persistentSelection;
XP_ASSERT(!selection.m_start.IsEndOfDocument());
persistentSelection.m_start = EphemeralToPersistent(selection.m_start);
persistentSelection.m_end = EphemeralToPersistent(selection.m_end);
persistentSelection.m_bFromStart = selection.m_bFromStart;
return persistentSelection;
}
void CEditBuffer::AdoptAndDo(CEditCommand* command) {
DoneTyping();
m_commandLog.AdoptAndDo(command);
SetDirtyFlag( TRUE );
}
void CEditBuffer::Undo() {
DoneTyping();
m_commandLog.Undo();
SetDirtyFlag( TRUE );
}
void CEditBuffer::Redo() {
DoneTyping();
m_commandLog.Redo();
SetDirtyFlag( TRUE );
}
void CEditBuffer::Trim() {
DoneTyping();
m_commandLog.Trim();
}
intn CEditBuffer::GetCommandHistoryLimit() {
return m_commandLog.GetCommandHistoryLimit();
}
void CEditBuffer::SetCommandHistoryLimit(intn newLimit) {
m_commandLog.SetCommandHistoryLimit(newLimit);
}
// Returns NULL if out of range
CEditCommand* CEditBuffer::GetUndoCommand(intn n) {
return m_commandLog.GetUndoCommand(n);
}
CEditCommand* CEditBuffer::GetRedoCommand(intn n) {
return m_commandLog.GetRedoCommand(n);
}
void CEditBuffer::BeginBatchChanges(intn id) {
m_commandLog.BeginBatchChanges(new CEditCommandGroup(this, id));
}
void CEditBuffer::EndBatchChanges() {
m_commandLog.EndBatchChanges();
}
void CEditBuffer::DoneTyping() {
#ifdef DEBUG
if ( m_pTypingCommand )
XP_TRACE(("Done typing."));
#endif
m_pTypingCommand = 0;
}
void CEditBuffer::TypingDeleted(CTypingCommand* deletedCommand) {
if ( deletedCommand == m_pTypingCommand ) {
m_pTypingCommand = 0;
XP_TRACE(("Typing deleted."));
}
}
CTypingCommand* CEditBuffer::GetTypingCommand() {
// Creates it if needed
if ( ! m_pTypingCommand ) {
m_pTypingCommand = new CTypingCommand(this, kTypingCommandID);
m_commandLog.AdoptAndDo(m_pTypingCommand);
}
return m_pTypingCommand;
}
//-----------------------------------------------------------------------------
// CEditTagCursor
//-----------------------------------------------------------------------------
CEditTagCursor::CEditTagCursor( CEditBuffer* pEditBuffer,
CEditElement *pElement, int iEditOffset, CEditElement* pEndElement ):
m_pEditBuffer(pEditBuffer),
m_pCurrentElement(pElement),
m_endPos(pEndElement,0),
m_stateDepth(0),
m_currentStateDepth(0),
m_iEditOffset( iEditOffset ),
m_pContext( pEditBuffer->GetContext() ),
m_pStateTags(0),
m_bClearRelayoutState(0),
m_tagPosition(tagOpen){
CEditElement *pContainer = m_pCurrentElement->FindContainer();
if( (m_pCurrentElement->PreviousLeafInContainer() == 0 )
&& iEditOffset == 0
&& pContainer
&& pContainer->IsA( P_LIST_ITEM ) ){
m_pCurrentElement = pContainer;
}
//m_bClearRelayoutState = (m_pCurrentElement->PreviousLeafInContainer() != 0 );
//
// If the element has a parent, setup the depth
//
pElement = m_pCurrentElement->GetParent();
while( pElement ){
PA_Tag *pTag = pElement->TagOpen( 0 );
PA_Tag *pTagEnd = pTag;
while( pTagEnd->next != 0 ) pTagEnd = pTag->next;
pTagEnd->next = m_pStateTags;
m_pStateTags = pTag;
pElement = pElement->GetParent();
}
}
CEditTagCursor::~CEditTagCursor(){
PA_Tag *pTag = m_pStateTags;
while ( pTag ) {
PA_Tag* pNext = pTag->next;
PA_FreeTag( pTag );
pTag = pNext;
}
}
CEditTagCursor* CEditTagCursor::Clone(){
CEditTagCursor *pCursor = new CEditTagCursor( m_pEditBuffer,
m_pCurrentElement,
m_iEditOffset,
m_endPos.Element());
return pCursor;
}
//
// We iterate through the edit tree generating tags. The the goto code here
// insures that after we deliver a tag, the m_pCurrentElement is always pointing
// to the next element to be output
//
PA_Tag* CEditTagCursor::GetNextTag(){
XP_Bool bDone = FALSE;
PA_Tag* pTag;
// Check for end
if( m_pCurrentElement == 0 ){
return 0;
}
while( !bDone && m_pCurrentElement != 0 ){
// we are either generating the beginning tag, content or end tags
switch( m_tagPosition ){
case tagOpen:
pTag = m_pCurrentElement->TagOpen( m_iEditOffset );
bDone = (pTag != 0);
m_iEditOffset = 0; // only counts on the first tag.
if( m_pCurrentElement->GetChild() ){
m_pCurrentElement = m_pCurrentElement->GetChild();
m_tagPosition = tagOpen;
}
else {
m_tagPosition = tagEnd;
if( !WriteTagClose(m_pCurrentElement->GetType()) ){
goto seek_next;
}
}
break;
case tagEnd:
pTag = m_pCurrentElement->TagEnd();
bDone = (pTag != 0);
seek_next:
if( m_pCurrentElement->GetNextSibling()){
m_pCurrentElement = m_pCurrentElement->GetNextSibling();
m_tagPosition = tagOpen;
}
else {
//
// We've exausted all the elements at this level. Set the
// current element to the end of our parent. m_tagPosition is
// already set to tagEnd, but lets be explicit
//
m_pCurrentElement = m_pCurrentElement->GetParent();
m_tagPosition = tagEnd;
if( m_pCurrentElement && !WriteTagClose(m_pCurrentElement->GetType()) ){
goto seek_next;
}
}
break;
}
}
if( bDone ){
return pTag;
}
else {
XP_DELETE(pTag);
return 0;
}
}
PA_Tag* CEditTagCursor::GetNextTagState(){
PA_Tag *pRet = m_pStateTags;
if( m_pStateTags ){
m_pStateTags = 0;
}
return pRet;
}
XP_Bool CEditTagCursor::AtBreak( XP_Bool* pEndTag ){
XP_Bool bAtPara;
XP_Bool bEndTag;
if( m_pCurrentElement == 0 ){
return FALSE;
}
if( m_tagPosition == tagEnd ){
bEndTag = TRUE;
bAtPara = BitSet( edt_setTextContainer, m_pCurrentElement->GetType() );
}
else {
bEndTag = FALSE;
bAtPara = BitSet( edt_setParagraphBreak, m_pCurrentElement->GetType() );
}
if( bAtPara ){
// force the layout engine to process this tag before computing line
// position.
//if( m_pCurrentElement->IsA( P_LINEBREAK ){
// bEndTag = TRUE;
//}
// if there is an end position and the current position is before it
// ignore this break.
CEditPosition p(m_pCurrentElement);
if( m_endPos.IsPositioned() &&
m_endPos.Compare(&p) <= 0 ){
return FALSE;
}
if( CurrentLine() != -1 ){
*pEndTag = bEndTag;
return TRUE;
}
else {
//XP_ASSERT(FALSE)
}
}
return FALSE;
}
int32 CEditTagCursor::CurrentLine(){
CEditElement *pElement;
LO_Element * pLayoutElement, *pLoStartLine;
int32 iLineNum;
//
// Pop to the proper state
//
CEditElement *pSave = m_pCurrentElement;
while( m_pCurrentElement && m_tagPosition == tagEnd ){
// some convelouded shit, so do it this way.
PA_Tag* pTag = GetNextTag();
PA_FreeTag( pTag );
m_tagPosition = tagEnd; // restore this.
}
// if we fell of the end of the document, then we are done.
if( m_pCurrentElement == 0 ){
m_pCurrentElement = pSave;
return -1;
}
// LTNOTE: need to check to see if we are currently at a TextElement..
pElement = m_pCurrentElement->NextLeaf();
m_pCurrentElement = pSave;
if( pElement == 0 ){
return -1;
}
XP_ASSERT( pElement->IsLeaf() );
pLayoutElement = pElement->Leaf()->GetLayoutElement();
if( pLayoutElement == 0 ){
return -1;
}
// Find the first element on this line.
pLoStartLine = LO_FirstElementOnLine( m_pContext, pLayoutElement->lo_any.x,
pLayoutElement->lo_any.y, &iLineNum );
// LTNOTE: this isn't true in delete cases.
//XP_ASSERT( pLoStartLine == pLayoutElement );
return iLineNum;
}
#ifdef XP_WIN16
// code segment is full, switch to a new segment
#pragma code_seg("EDTSAVE3_TEXT","CODE")
#endif
#endif