mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-30 13:45:27 +00:00
6484 lines
194 KiB
C++
6484 lines
194 KiB
C++
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
|
*
|
|
* The contents of this file are subject to the Netscape Public License
|
|
* Version 1.0 (the "NPL"); you may not use this file except in
|
|
* compliance with the NPL. You may obtain a copy of the NPL at
|
|
* http://www.mozilla.org/NPL/
|
|
*
|
|
* Software distributed under the NPL is distributed on an "AS IS" basis,
|
|
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
|
|
* for the specific language governing rights and limitations under the
|
|
* NPL.
|
|
*
|
|
* The Initial Developer of this code under the NPL is Netscape
|
|
* Communications Corporation. Portions created by Netscape are
|
|
* Copyright (C) 1998 Netscape Communications Corporation. All Rights
|
|
* Reserved.
|
|
*/
|
|
|
|
|
|
//
|
|
// Public interface and shared subsystem data.
|
|
//
|
|
|
|
#ifdef EDITOR
|
|
|
|
#include "editor.h"
|
|
#include "rosetta.h"
|
|
|
|
typedef struct PA_AmpEsc_struct {
|
|
char *str;
|
|
char value;
|
|
intn len;
|
|
} PA_AmpEsc;
|
|
|
|
#ifndef XP_MAC
|
|
static PA_AmpEsc PA_AmpEscapes[] = {
|
|
{"lt", '<', 2},
|
|
{"LT", '<', 2},
|
|
{"gt", '>', 2},
|
|
{"GT", '>', 2},
|
|
{"amp", '&', 3},
|
|
{"AMP", '&', 3},
|
|
{"quot", '\"', 4},
|
|
{"QUOT", '\"', 4},
|
|
{"nbsp", '\240', 4},
|
|
{"reg", '\256', 3},
|
|
{"REG", '\256', 3},
|
|
{"copy", '\251', 4},
|
|
{"COPY", '\251', 4},
|
|
|
|
{"iexcl", '\241', 5},
|
|
{"cent", '\242', 4},
|
|
{"pound", '\243', 5},
|
|
{"curren", '\244', 6},
|
|
#ifdef XP_WIN
|
|
{"euro", '\200', 4},
|
|
#endif
|
|
{"yen", '\245', 3},
|
|
{"brvbar", '\246', 6},
|
|
{"sect", '\247', 4},
|
|
|
|
{"uml", '\250', 3},
|
|
{"ordf", '\252', 4},
|
|
{"laquo", '\253', 5},
|
|
{"not", '\254', 3},
|
|
{"shy", '\255', 3},
|
|
{"macr", '\257', 4},
|
|
|
|
{"deg", '\260', 3},
|
|
{"plusmn", '\261', 6},
|
|
{"sup2", '\262', 4},
|
|
{"sup3", '\263', 4},
|
|
{"acute", '\264', 5},
|
|
{"micro", '\265', 5},
|
|
{"para", '\266', 4},
|
|
{"middot", '\267', 6},
|
|
|
|
{"cedil", '\270', 5},
|
|
{"sup1", '\271', 4},
|
|
{"ordm", '\272', 4},
|
|
{"raquo", '\273', 5},
|
|
{"frac14", '\274', 6},
|
|
{"frac12", '\275', 6},
|
|
{"frac34", '\276', 6},
|
|
{"iquest", '\277', 6},
|
|
|
|
{"Agrave", '\300', 6},
|
|
{"Aacute", '\301', 6},
|
|
{"Acirc", '\302', 5},
|
|
{"Atilde", '\303', 6},
|
|
{"Auml", '\304', 4},
|
|
{"Aring", '\305', 5},
|
|
{"AElig", '\306', 5},
|
|
{"Ccedil", '\307', 6},
|
|
|
|
{"Egrave", '\310', 6},
|
|
{"Eacute", '\311', 6},
|
|
{"Ecirc", '\312', 5},
|
|
{"Euml", '\313', 4},
|
|
{"Igrave", '\314', 6},
|
|
{"Iacute", '\315', 6},
|
|
{"Icirc", '\316', 5},
|
|
{"Iuml", '\317', 4},
|
|
|
|
{"ETH", '\320', 3},
|
|
{"Ntilde", '\321', 6},
|
|
{"Ograve", '\322', 6},
|
|
{"Oacute", '\323', 6},
|
|
{"Ocirc", '\324', 5},
|
|
{"Otilde", '\325', 6},
|
|
{"Ouml", '\326', 4},
|
|
{"times", '\327', 5},
|
|
|
|
{"Oslash", '\330', 6},
|
|
{"Ugrave", '\331', 6},
|
|
{"Uacute", '\332', 6},
|
|
{"Ucirc", '\333', 5},
|
|
{"Uuml", '\334', 4},
|
|
{"Yacute", '\335', 6},
|
|
{"THORN", '\336', 5},
|
|
{"szlig", '\337', 5},
|
|
|
|
{"agrave", '\340', 6},
|
|
{"aacute", '\341', 6},
|
|
{"acirc", '\342', 5},
|
|
{"atilde", '\343', 6},
|
|
{"auml", '\344', 4},
|
|
{"aring", '\345', 5},
|
|
{"aelig", '\346', 5},
|
|
{"ccedil", '\347', 6},
|
|
|
|
{"egrave", '\350', 6},
|
|
{"eacute", '\351', 6},
|
|
{"ecirc", '\352', 5},
|
|
{"euml", '\353', 4},
|
|
{"igrave", '\354', 6},
|
|
{"iacute", '\355', 6},
|
|
{"icirc", '\356', 5},
|
|
{"iuml", '\357', 4},
|
|
|
|
{"eth", '\360', 3},
|
|
{"ntilde", '\361', 6},
|
|
{"ograve", '\362', 6},
|
|
{"oacute", '\363', 6},
|
|
{"ocirc", '\364', 5},
|
|
{"otilde", '\365', 6},
|
|
{"ouml", '\366', 4},
|
|
{"divide", '\367', 6},
|
|
|
|
{"oslash", '\370', 6},
|
|
{"ugrave", '\371', 6},
|
|
{"uacute", '\372', 6},
|
|
{"ucirc", '\373', 5},
|
|
{"uuml", '\374', 4},
|
|
{"yacute", '\375', 6},
|
|
{"thorn", '\376', 5},
|
|
{"yuml", '\377', 4},
|
|
|
|
{NULL, '\0', 0},
|
|
};
|
|
#else /* ! XP_MAC */
|
|
/* Entities encoded in MacRoman. */
|
|
static PA_AmpEsc PA_AmpEscapes[] = {
|
|
{"lt", '<', 2},
|
|
{"LT", '<', 2},
|
|
{"gt", '>', 2},
|
|
{"GT", '>', 2},
|
|
{"amp", '&', 3},
|
|
{"AMP", '&', 3},
|
|
{"quot", '\"', 4},
|
|
{"QUOT", '\"', 4},
|
|
{"nbsp", '\007', 4},
|
|
{"reg", '\250', 3},
|
|
{"REG", '\250', 3},
|
|
{"copy", '\251', 4},
|
|
{"COPY", '\251', 4},
|
|
|
|
{"iexcl", '\301', 5},
|
|
{"cent", '\242', 4},
|
|
{"pound", '\243', 5},
|
|
#if 0 /* before MacOS 8.5 0xdb is curren, but it changed to euro in MacOS 8.5 */
|
|
{"curren", '\333', 6},
|
|
#else
|
|
{"euro", '\333', 4},
|
|
#endif
|
|
{"yen", '\264', 3},
|
|
|
|
/*
|
|
* Navigator Gold currently inverts this table in such a way that
|
|
* ASCII characters (less than 128) get converted to the names
|
|
* listed here. For things like ampersand (&) this is the
|
|
* right thing to do, but for this one (brvbar), it isn't since
|
|
* both broken vertical bar and vertical bar are mapped to the same
|
|
* character by the Latin-1 to Mac Roman table.
|
|
*
|
|
* Punt for now. This needs to be fixed later. -- erik
|
|
*/
|
|
/* {"brvbar", '\174', 6}, */
|
|
|
|
{"sect", '\244', 4},
|
|
|
|
{"uml", '\254', 3},
|
|
{"ordf", '\273', 4},
|
|
{"laquo", '\307', 5},
|
|
{"not", '\302', 3},
|
|
{"shy", '\320', 3},
|
|
{"macr", '\370', 4},
|
|
|
|
{"deg", '\241', 3},
|
|
{"plusmn", '\261', 6},
|
|
/* {"sup2", '\62', 4}, see comment above */
|
|
/* {"sup3", '\63', 4}, see comment above */
|
|
{"acute", '\253', 5},
|
|
{"micro", '\265', 5},
|
|
{"para", '\246', 4},
|
|
{"middot", '\341', 6},
|
|
|
|
{"cedil", '\374', 5},
|
|
/* {"sup1", '\61', 4}, see comment above */
|
|
{"ordm", '\274', 4},
|
|
{"raquo", '\310', 5},
|
|
{"frac14", '\271', 6},
|
|
{"frac12", '\270', 6},
|
|
{"frac34", '\262', 6},
|
|
{"iquest", '\300', 6},
|
|
|
|
{"Agrave", '\313', 6},
|
|
{"Aacute", '\347', 6},
|
|
{"Acirc", '\345', 5},
|
|
{"Atilde", '\314', 6},
|
|
{"Auml", '\200', 4},
|
|
{"Aring", '\201', 5},
|
|
{"AElig", '\256', 5},
|
|
{"Ccedil", '\202', 6},
|
|
|
|
{"Egrave", '\351', 6},
|
|
{"Eacute", '\203', 6},
|
|
{"Ecirc", '\346', 5},
|
|
{"Euml", '\350', 4},
|
|
{"Igrave", '\355', 6},
|
|
{"Iacute", '\352', 6},
|
|
{"Icirc", '\353', 5},
|
|
{"Iuml", '\354', 4},
|
|
|
|
{"ETH", '\334', 3}, /* Icelandic MacRoman: ETH ('D' w/horiz bar) */
|
|
{"Ntilde", '\204', 6},
|
|
{"Ograve", '\361', 6},
|
|
{"Oacute", '\356', 6},
|
|
{"Ocirc", '\357', 5},
|
|
{"Otilde", '\315', 6},
|
|
{"Ouml", '\205', 4},
|
|
/* {"times", '\170', 5}, see comment above */
|
|
|
|
{"Oslash", '\257', 6},
|
|
{"Ugrave", '\364', 6},
|
|
{"Uacute", '\362', 6},
|
|
{"Ucirc", '\363', 5},
|
|
{"Uuml", '\206', 4},
|
|
{"Yacute", '\240', 6}, /* Icelandic MacRoman: Yacute */
|
|
{"THORN", '\336', 5}, /* Icelandic MacRoman: THORN (kinda like 'P') */
|
|
{"szlig", '\247', 5},
|
|
|
|
{"agrave", '\210', 6},
|
|
{"aacute", '\207', 6},
|
|
{"acirc", '\211', 5},
|
|
{"atilde", '\213', 6},
|
|
{"auml", '\212', 4},
|
|
{"aring", '\214', 5},
|
|
{"aelig", '\276', 5},
|
|
{"ccedil", '\215', 6},
|
|
|
|
{"egrave", '\217', 6},
|
|
{"eacute", '\216', 6},
|
|
{"ecirc", '\220', 5},
|
|
{"euml", '\221', 4},
|
|
{"igrave", '\223', 6},
|
|
{"iacute", '\222', 6},
|
|
{"icirc", '\224', 5},
|
|
{"iuml", '\225', 4},
|
|
|
|
{"eth", '\335', 3}, /* Icelandic MacRoman: eth ('d' w/horiz bar) */
|
|
{"ntilde", '\226', 6},
|
|
{"ograve", '\230', 6},
|
|
{"oacute", '\227', 6},
|
|
{"ocirc", '\231', 5},
|
|
{"otilde", '\233', 6},
|
|
{"ouml", '\232', 4},
|
|
{"divide", '\326', 6},
|
|
|
|
{"oslash", '\277', 6},
|
|
{"ugrave", '\235', 6},
|
|
{"uacute", '\234', 6},
|
|
{"ucirc", '\236', 5},
|
|
{"uuml", '\237', 4},
|
|
{"yacute", '\340', 6}, /* Icelandic MacRoman: yacute */
|
|
{"thorn", '\337', 5}, /* Icelandic MacRoman: thorn (kinda like 'p') */
|
|
{"yuml", '\330', 4},
|
|
|
|
{NULL, '\0', 0},
|
|
};
|
|
|
|
#endif
|
|
|
|
|
|
// For XP Strings
|
|
extern "C" {
|
|
#include "xpgetstr.h"
|
|
#define WANT_ENUM_STRING_IDS
|
|
#include "allxpstr.h"
|
|
#undef WANT_ENUM_STRING_IDS
|
|
}
|
|
#include "prefapi.h"
|
|
#include "secnav.h"
|
|
#include "xp_str.h"
|
|
|
|
char* edt_CopyFromHuge(int16 /*csid*/, XP_HUGE_CHAR_PTR text, int32 length, int32* pOutLength){
|
|
int len2 = length;
|
|
|
|
#ifdef XP_WIN16
|
|
const int32 kMax = 65530;
|
|
XP_Bool bTruncated = FALSE;
|
|
if ( len2 > kMax ) {
|
|
XP_TRACE(("Truncating huge data. original size: %d bytes", len2));
|
|
bTruncated = TRUE;
|
|
len2 = kMax;
|
|
}
|
|
#endif
|
|
|
|
char* pData = (char*) XP_ALLOC(len2 + 1);
|
|
if ( pData == NULL ) return NULL;
|
|
for(int32 i = 0; i < len2; i++ ){
|
|
// No XP way of transfering from HUGE to normal
|
|
pData[i] = text[i];
|
|
}
|
|
|
|
#ifdef XP_WIN16
|
|
if ( bTruncated ) {
|
|
int nboc = INTL_NthByteOfChar(csid, pData, (int)(len2));
|
|
// nboc is one-based. Stupid!
|
|
if ( nboc > 1 ){
|
|
len2 -= (nboc - 1);
|
|
}
|
|
if ( len2 < 0 ) {
|
|
len2 = 0;
|
|
}
|
|
XP_TRACE(("Final size: %d bytes", len2));
|
|
}
|
|
#endif
|
|
|
|
pData[len2] = '\0';
|
|
if ( pOutLength ) {
|
|
*pOutLength = len2;
|
|
}
|
|
return pData;
|
|
}
|
|
|
|
void edt_StripAtHashOrQuestionMark(char *pUrl)
|
|
{
|
|
if( !pUrl )
|
|
return;
|
|
|
|
char *pStart = XP_STRRCHR(pUrl, '/');
|
|
|
|
if(!pStart)
|
|
pStart = pUrl;
|
|
|
|
char * pSuffix = XP_STRCHR(pStart, '?');
|
|
|
|
if(pSuffix)
|
|
*pSuffix = '\0';
|
|
|
|
pSuffix = XP_STRRCHR(pStart, '#');
|
|
if(pSuffix)
|
|
*pSuffix = '\0';
|
|
}
|
|
|
|
char *edt_StripUsernamePassword(char *pUrl) {
|
|
int iType = NET_URL_Type(pUrl);
|
|
if (iType != FTP_TYPE_URL &&
|
|
iType != HTTP_TYPE_URL &&
|
|
iType != SECURE_HTTP_TYPE_URL) {
|
|
return XP_STRDUP(pUrl);
|
|
}
|
|
|
|
char *pRet = NULL;
|
|
if (NET_ParseUploadURL(pUrl,&pRet,NULL,NULL) && pRet) {
|
|
return pRet;
|
|
}
|
|
else {
|
|
return XP_STRDUP(pUrl);
|
|
}
|
|
}
|
|
|
|
|
|
// Always returns newly allocated memory or NULL.
|
|
PRIVATE char *edt_remove_trailing_slash(const char *pStr) {
|
|
if (pStr) {
|
|
char *pNew = XP_STRDUP(pStr);
|
|
if (pNew && *pNew) {
|
|
// Only do strlen() if pNew is non-NULL.
|
|
int iLen = XP_STRLEN(pNew);
|
|
|
|
// Kill trailing slash.
|
|
if (pNew[iLen-1] == '/') {
|
|
pNew[iLen-1] = '\0';
|
|
}
|
|
}
|
|
return pNew;
|
|
}
|
|
else {
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Just wrappers around the XP_* functions. The only difference is that
|
|
// these functions will work if the directory ends in a trailing slash,
|
|
// avoiding obscure bugs on Win16.
|
|
XP_Dir edt_OpenDir(const char * name, XP_FileType type) {
|
|
XP_ASSERT(type == xpURL);
|
|
char *pNew = edt_remove_trailing_slash(name);
|
|
XP_Dir ret = XP_OpenDir(pNew,type);
|
|
XP_FREEIF(pNew);
|
|
return ret;
|
|
}
|
|
|
|
int edt_MakeDirectory(const char* name, XP_FileType type) {
|
|
XP_ASSERT(type == xpURL);
|
|
char *pNew = edt_remove_trailing_slash(name);
|
|
int ret = XP_MakeDirectory(pNew,type);
|
|
XP_FREEIF(pNew);
|
|
return ret;
|
|
}
|
|
|
|
int edt_RemoveDirectory(const char *name, XP_FileType type) {
|
|
XP_ASSERT(type == xpURL);
|
|
char *pNew = edt_remove_trailing_slash(name);
|
|
int ret = XP_RemoveDirectory(pNew,type);
|
|
XP_FREEIF(pNew);
|
|
return ret;
|
|
}
|
|
|
|
|
|
|
|
// Recursively kill everything under pNameArg, which is in xpURL format.
|
|
void edt_RemoveDirectoryR(char *pNameArg) {
|
|
// non-NULL and non-zero-length.
|
|
if (!(pNameArg && *pNameArg))
|
|
return;
|
|
|
|
// Make sure name ends in slash.
|
|
char *pName = XP_STRDUP(pNameArg);
|
|
if (!pName) {
|
|
return;
|
|
}
|
|
if (pName[XP_STRLEN(pName)-1] != '/') {
|
|
StrAllocCat(pName,"/");
|
|
}
|
|
|
|
|
|
XP_Dir dir = edt_OpenDir(pName, xpURL);
|
|
if (dir) {
|
|
XP_DirEntryStruct *entry = NULL;
|
|
for (entry = XP_ReadDir(dir); entry; entry = XP_ReadDir(dir)) {
|
|
// Don't touch "." or ".."
|
|
if (entry->d_name && XP_STRCMP(entry->d_name,".") && XP_STRCMP(entry->d_name,"..")) {
|
|
|
|
// Create filename inside directory.
|
|
char *subName = XP_STRDUP(pName);
|
|
if (entry->d_name[0] == '/') {
|
|
// skip the slash.
|
|
StrAllocCat(subName,entry->d_name + 1);
|
|
}
|
|
else {
|
|
StrAllocCat(subName,entry->d_name);
|
|
}
|
|
|
|
// Recurse, will do nothing if not a directory.
|
|
edt_RemoveDirectoryR(subName);
|
|
// If subName is a directory, it will already be gone, so this does nothing.
|
|
XP_FileRemove(subName,xpURL);
|
|
|
|
|
|
XP_FREEIF(subName);
|
|
}
|
|
}
|
|
|
|
XP_CloseDir(dir);
|
|
edt_RemoveDirectory(pName,xpURL);
|
|
}
|
|
|
|
|
|
XP_FREE(pName);
|
|
}
|
|
|
|
|
|
// Timer
|
|
|
|
|
|
void CEditTimerCallback (void * closure){
|
|
if (closure) {
|
|
CEditTimer* pTimer = (CEditTimer*) closure;
|
|
pTimer->Callback();
|
|
}
|
|
}
|
|
|
|
CEditTimer::CEditTimer(){
|
|
m_timeoutEnabled = FALSE;
|
|
m_timerID = NULL;
|
|
}
|
|
|
|
CEditTimer::~CEditTimer(){
|
|
Cancel();
|
|
}
|
|
|
|
void CEditTimer::Callback() {
|
|
m_timeoutEnabled = FALSE;
|
|
OnCallback();
|
|
}
|
|
|
|
void CEditTimer::Cancel(){
|
|
if ( m_timeoutEnabled ) {
|
|
FE_ClearTimeout(m_timerID);
|
|
m_timeoutEnabled = FALSE;
|
|
m_timerID = NULL;
|
|
}
|
|
}
|
|
|
|
void CEditTimer::OnCallback() {}
|
|
|
|
void CEditTimer::SetTimeout(uint32 msecs){
|
|
Cancel();
|
|
m_timerID = FE_SetTimeout(&CEditTimerCallback, this, msecs);
|
|
m_timeoutEnabled = TRUE;
|
|
}
|
|
|
|
// Color with a defined/undefined boolean
|
|
|
|
ED_Color::ED_Color() {
|
|
m_bDefined = TRUE;
|
|
m_color.red = 0;
|
|
m_color.green = 0;
|
|
m_color.blue = 0;
|
|
}
|
|
ED_Color::ED_Color(LO_Color& pLoColor) {
|
|
m_bDefined = TRUE;
|
|
m_color = pLoColor;
|
|
}
|
|
|
|
static int ED_Color_Clip(int c) {
|
|
if ( c < 0 ) {
|
|
XP_ASSERT(FALSE);
|
|
c = 0;
|
|
}
|
|
if ( c > 255 ) {
|
|
XP_ASSERT(FALSE);
|
|
c = 255;
|
|
}
|
|
return c;
|
|
}
|
|
|
|
ED_Color::ED_Color(int r, int g, int b) {
|
|
m_bDefined = TRUE;
|
|
m_color.red = ED_Color_Clip(r);
|
|
m_color.green = ED_Color_Clip(g);
|
|
m_color.blue = ED_Color_Clip(b);
|
|
}
|
|
|
|
ED_Color::ED_Color(int32 rgb) {
|
|
if ( rgb == -1 ) {
|
|
m_bDefined = FALSE;
|
|
m_color.red = 0;
|
|
m_color.green = 0;
|
|
m_color.blue = 0;
|
|
}
|
|
else {
|
|
m_bDefined = TRUE;
|
|
m_color.red = (rgb >> 16) & 255;
|
|
m_color.green = (rgb >> 8) & 255;
|
|
m_color.blue = rgb & 255;
|
|
}
|
|
}
|
|
|
|
ED_Color::ED_Color(LO_Color* pLoColor) {
|
|
if ( pLoColor ) {
|
|
m_color.red = ED_Color_Clip(pLoColor->red);
|
|
m_color.green = ED_Color_Clip(pLoColor->green);
|
|
m_color.blue = ED_Color_Clip(pLoColor->blue);
|
|
m_bDefined = TRUE;
|
|
}
|
|
else {
|
|
m_bDefined = FALSE;
|
|
}
|
|
}
|
|
|
|
XP_Bool ED_Color::operator==(const ED_Color& other) const {
|
|
return m_bDefined == other.m_bDefined &&
|
|
(!m_bDefined || m_color.red == other.m_color.red &&
|
|
m_color.green == other.m_color.green &&
|
|
m_color.blue == other.m_color.blue );
|
|
}
|
|
|
|
XP_Bool ED_Color::operator!=(const ED_Color& other) const {
|
|
return ! operator==(other);
|
|
}
|
|
|
|
XP_Bool ED_Color::IsDefined() { return m_bDefined; }
|
|
int ED_Color::Red() { XP_ASSERT(m_bDefined); return m_color.red; }
|
|
int ED_Color::Green() { XP_ASSERT(m_bDefined); return m_color.green; }
|
|
int ED_Color::Blue() { XP_ASSERT(m_bDefined); return m_color.blue; }
|
|
LO_Color ED_Color::GetLOColor() {XP_ASSERT(m_bDefined); return m_color; }
|
|
|
|
long ED_Color::GetAsLong() {
|
|
if ( ! m_bDefined ) return -1;
|
|
return (((long)m_color.red << 16) | ((long)m_color.green << 8) | (long)m_color.blue);
|
|
}
|
|
|
|
void
|
|
ED_Color::SetUndefined() {
|
|
m_bDefined = FALSE;
|
|
m_color.red = 0;
|
|
m_color.green = 0;
|
|
m_color.blue = 0;
|
|
}
|
|
|
|
ED_Color
|
|
ED_Color::GetUndefined()
|
|
{
|
|
ED_Color undef;
|
|
undef.SetUndefined();
|
|
return undef;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Helper functions.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
//
|
|
// Convert a tagtype to a string
|
|
//
|
|
|
|
static char* TagString(int32 tagType)
|
|
{
|
|
switch(tagType)
|
|
{
|
|
case P_UNKNOWN:
|
|
return "";
|
|
case P_TEXT:
|
|
return "text";
|
|
case P_TITLE:
|
|
return PT_TITLE;
|
|
case P_INDEX:
|
|
return PT_INDEX;
|
|
case P_BASE:
|
|
return PT_BASE;
|
|
case P_LINK:
|
|
return PT_LINK;
|
|
case P_HEADER_1:
|
|
return PT_HEADER_1;
|
|
case P_HEADER_2:
|
|
return PT_HEADER_2;
|
|
case P_HEADER_3:
|
|
return PT_HEADER_3;
|
|
case P_HEADER_4:
|
|
return PT_HEADER_4;
|
|
case P_HEADER_5:
|
|
return PT_HEADER_5;
|
|
case P_HEADER_6:
|
|
return PT_HEADER_6;
|
|
case P_ANCHOR:
|
|
return PT_ANCHOR;
|
|
case P_PARAGRAPH:
|
|
return PT_PARAGRAPH;
|
|
case P_ADDRESS:
|
|
return PT_ADDRESS;
|
|
case P_IMAGE:
|
|
return PT_IMAGE;
|
|
case P_NEW_IMAGE:
|
|
return PT_NEW_IMAGE;
|
|
case P_PLAIN_TEXT:
|
|
return PT_PLAIN_TEXT;
|
|
case P_PLAIN_PIECE:
|
|
return PT_PLAIN_PIECE;
|
|
case P_PREFORMAT:
|
|
return PT_PREFORMAT;
|
|
case P_LISTING_TEXT:
|
|
return PT_LISTING_TEXT;
|
|
case P_UNUM_LIST:
|
|
return PT_UNUM_LIST;
|
|
case P_NUM_LIST:
|
|
return PT_NUM_LIST;
|
|
case P_MENU:
|
|
return PT_MENU;
|
|
case P_DIRECTORY:
|
|
return PT_DIRECTORY;
|
|
case P_LIST_ITEM:
|
|
return PT_LIST_ITEM;
|
|
case P_DESC_LIST:
|
|
return PT_DESC_LIST;
|
|
case P_DESC_TITLE:
|
|
return PT_DESC_TITLE;
|
|
case P_DESC_TEXT:
|
|
return PT_DESC_TEXT;
|
|
case P_STRIKEOUT:
|
|
return PT_STRIKEOUT;
|
|
case P_STRIKE:
|
|
return PT_STRIKE;
|
|
case P_UNDERLINE:
|
|
return PT_UNDERLINE;
|
|
case P_FIXED:
|
|
return PT_FIXED;
|
|
case P_CENTER:
|
|
return PT_CENTER;
|
|
case P_EMBED:
|
|
return PT_EMBED;
|
|
case P_SUBDOC:
|
|
return PT_SUBDOC;
|
|
case P_CELL:
|
|
return PT_CELL;
|
|
case P_TABLE:
|
|
return PT_TABLE;
|
|
case P_CAPTION:
|
|
return PT_CAPTION;
|
|
case P_TABLE_ROW:
|
|
return PT_TABLE_ROW;
|
|
case P_TABLE_HEADER:
|
|
return PT_TABLE_HEADER;
|
|
case P_TABLE_DATA:
|
|
return PT_TABLE_DATA;
|
|
case P_BOLD:
|
|
return PT_BOLD;
|
|
case P_ITALIC:
|
|
return PT_ITALIC;
|
|
case P_EMPHASIZED:
|
|
return PT_EMPHASIZED;
|
|
case P_STRONG:
|
|
return PT_STRONG;
|
|
case P_CODE:
|
|
return PT_CODE;
|
|
case P_SAMPLE:
|
|
return PT_SAMPLE;
|
|
case P_KEYBOARD:
|
|
return PT_KEYBOARD;
|
|
case P_VARIABLE:
|
|
return PT_VARIABLE;
|
|
case P_CITATION:
|
|
return PT_CITATION;
|
|
case P_BLOCKQUOTE:
|
|
return PT_BLOCKQUOTE;
|
|
case P_FORM:
|
|
return PT_FORM;
|
|
case P_INPUT:
|
|
return PT_INPUT;
|
|
case P_SELECT:
|
|
return PT_SELECT;
|
|
case P_OPTION:
|
|
return PT_OPTION;
|
|
case P_TEXTAREA:
|
|
return PT_TEXTAREA;
|
|
case P_HRULE:
|
|
return PT_HRULE;
|
|
case P_BODY:
|
|
return PT_BODY;
|
|
case P_COLORMAP:
|
|
return PT_COLORMAP;
|
|
case P_HYPE:
|
|
return PT_HYPE;
|
|
case P_META:
|
|
return PT_META;
|
|
case P_LINEBREAK:
|
|
return PT_LINEBREAK;
|
|
case P_WORDBREAK:
|
|
return PT_WORDBREAK;
|
|
case P_NOBREAK:
|
|
return PT_NOBREAK;
|
|
case P_BASEFONT:
|
|
return PT_BASEFONT;
|
|
case P_FONT:
|
|
return PT_FONT;
|
|
case P_BLINK:
|
|
return PT_BLINK;
|
|
case P_BIG:
|
|
return PT_BIG;
|
|
case P_SMALL:
|
|
return PT_SMALL;
|
|
case P_SUPER:
|
|
return PT_SUPER;
|
|
case P_SUB:
|
|
return PT_SUB;
|
|
case P_GRID:
|
|
return PT_GRID;
|
|
case P_GRID_CELL:
|
|
return PT_GRID_CELL;
|
|
case P_NOGRIDS:
|
|
return PT_NOGRIDS;
|
|
case P_JAVA_APPLET:
|
|
return PT_JAVA_APPLET;
|
|
case P_MAP:
|
|
return PT_MAP;
|
|
case P_AREA:
|
|
return PT_AREA;
|
|
case P_DIVISION:
|
|
return PT_DIVISION;
|
|
case P_KEYGEN:
|
|
return PT_KEYGEN;
|
|
case P_MAX:
|
|
return "HTML";
|
|
case P_SCRIPT:
|
|
return PT_SCRIPT;
|
|
case P_NOEMBED:
|
|
return PT_NOEMBED;
|
|
case P_SERVER:
|
|
return PT_SERVER;
|
|
case P_PARAM:
|
|
return PT_PARAM;
|
|
case P_MULTICOLUMN:
|
|
return PT_MULTICOLUMN;
|
|
case P_SPACER:
|
|
return PT_SPACER;
|
|
case P_NOSCRIPT:
|
|
return PT_NOSCRIPT;
|
|
case P_HEAD:
|
|
return PT_HEAD;
|
|
case P_HTML:
|
|
return PT_HTML;
|
|
case P_CERTIFICATE:
|
|
return PT_CERTIFICATE;
|
|
case P_MQUOTE:
|
|
return PT_MQUOTE;
|
|
case P_STYLE:
|
|
return PT_STYLE;
|
|
case P_LAYER:
|
|
return PT_LAYER;
|
|
case P_ILAYER:
|
|
return PT_ILAYER;
|
|
case P_OBJECT:
|
|
return PT_OBJECT;
|
|
case P_SPAN:
|
|
return PT_SPAN;
|
|
case P_SPELL:
|
|
return PT_SPELL;
|
|
case P_INLINEINPUT:
|
|
return PT_INLINEINPUT;
|
|
case P_INLINEINPUTTHICK:
|
|
return PT_INLINEINPUTTHICK;
|
|
case P_INLINEINPUTDOTTED:
|
|
return PT_INLINEINPUTDOTTED;
|
|
case P_NSDT:
|
|
return PT_NSDT;
|
|
case P_NSCP_CLOSE:
|
|
return PT_NSCP_OPEN;
|
|
case P_NSCP_OPEN:
|
|
return PT_NSDT;
|
|
case P_NSCP_REBLOCK:
|
|
return PT_NSCP_REBLOCK;
|
|
case P_BLOCK:
|
|
return "block"; // PT_BLOCK
|
|
case P_NOLAYER:
|
|
return PT_NOLAYER; // (?) jrm 97/03/08 according to instructions below.
|
|
case P_BUILTIN:
|
|
return PT_BUILTIN;
|
|
default:
|
|
// If we get here, then it's a new tag that's been added to lib\libparse\pa_tags.h
|
|
// The fix is to add this new tag to the case statement above.
|
|
XP_ASSERT(FALSE);
|
|
return "UNKNOWN";
|
|
}
|
|
}
|
|
|
|
const int kTagBufferSize = 40;
|
|
static char tagBuffer[kTagBufferSize];
|
|
|
|
char* EDT_UpperCase(char* tagString)
|
|
{
|
|
int i = 0;
|
|
if ( tagString ) {
|
|
for ( i = 0; i < kTagBufferSize-1 && tagString[i] != '\0'; i++ ) {
|
|
tagBuffer[i] = XP_TO_UPPER(tagString[i]);
|
|
}
|
|
}
|
|
tagBuffer[i] = '\0';
|
|
return tagBuffer;
|
|
}
|
|
|
|
char *EDT_TagString(int32 tagType){
|
|
return EDT_UpperCase(TagString(tagType));
|
|
}
|
|
|
|
char* EDT_AlignString(ED_Alignment align){
|
|
if ( align < ED_ALIGN_CENTER ) {
|
|
XP_ASSERT(FALSE);
|
|
align = ED_ALIGN_CENTER;
|
|
}
|
|
if ( align > ED_ALIGN_ABSTOP ) {
|
|
XP_ASSERT(FALSE);
|
|
align = ED_ALIGN_ABSTOP;
|
|
}
|
|
return EDT_UpperCase(lo_alignStrings[align]);
|
|
}
|
|
|
|
ED_TextFormat edt_TagType2TextFormat( TagType t ){
|
|
switch(t){
|
|
case P_BOLD:
|
|
case P_STRONG:
|
|
return TF_BOLD;
|
|
|
|
case P_ITALIC:
|
|
case P_EMPHASIZED:
|
|
case P_VARIABLE:
|
|
case P_CITATION:
|
|
return TF_ITALIC;
|
|
|
|
case P_FIXED:
|
|
case P_CODE:
|
|
case P_KEYBOARD:
|
|
case P_SAMPLE:
|
|
return TF_FIXED;
|
|
|
|
case P_SUPER:
|
|
return TF_SUPER;
|
|
|
|
case P_SUB:
|
|
return TF_SUB;
|
|
|
|
case P_NOBREAK:
|
|
return TF_NOBREAK;
|
|
|
|
case P_STRIKEOUT:
|
|
return TF_STRIKEOUT;
|
|
|
|
case P_UNDERLINE:
|
|
return TF_UNDERLINE;
|
|
|
|
case P_BLINK:
|
|
return TF_BLINK;
|
|
|
|
case P_SERVER:
|
|
return TF_SERVER;
|
|
|
|
case P_SCRIPT:
|
|
return TF_SCRIPT;
|
|
|
|
case P_STYLE:
|
|
return TF_STYLE;
|
|
|
|
case P_SPELL:
|
|
return TF_SPELL;
|
|
|
|
case P_INLINEINPUT:
|
|
return TF_INLINEINPUT;
|
|
|
|
case P_INLINEINPUTTHICK:
|
|
return TF_INLINEINPUTTHICK;
|
|
|
|
case P_INLINEINPUTDOTTED:
|
|
return TF_INLINEINPUTDOTTED;
|
|
}
|
|
return TF_NONE;
|
|
}
|
|
|
|
static int workBufSize = 0;
|
|
static char* workBuf = 0;
|
|
|
|
char *edt_WorkBuf( int iSize ){
|
|
if( iSize > workBufSize ){
|
|
if( workBuf != 0 ){
|
|
delete[] workBuf;
|
|
}
|
|
workBuf = new char[ iSize ];
|
|
}
|
|
return workBuf;
|
|
}
|
|
|
|
char *edt_QuoteString( char* pString ){
|
|
int iLen = 0;
|
|
char *p = pString;
|
|
while( p && *p ){
|
|
if( *p == '"' ){
|
|
iLen += 6; // "
|
|
}
|
|
else {
|
|
iLen++;
|
|
}
|
|
p++;
|
|
}
|
|
iLen++;
|
|
|
|
char *pRet = edt_WorkBuf( iLen+1 );
|
|
p = pRet;
|
|
while( pString && *pString ){
|
|
if( *pString == '"' ){
|
|
strcpy( p, """ );
|
|
p += 6;
|
|
}
|
|
else {
|
|
*p++ = *pString;
|
|
}
|
|
pString++;
|
|
}
|
|
*p = 0;
|
|
return pRet;
|
|
}
|
|
|
|
|
|
char *edt_MakeParamString( char* pString ){
|
|
int iLen = 0;
|
|
char *p = pString;
|
|
while( p && *p ){
|
|
if( *p == '"' ){
|
|
iLen += 6; // "
|
|
}
|
|
else {
|
|
iLen++;
|
|
}
|
|
p++;
|
|
}
|
|
iLen += 3; // the beginning and ending quote and the trailing 0.
|
|
|
|
char *pRet = edt_WorkBuf( iLen+1 );
|
|
p = pRet;
|
|
if( pString && *pString == '`' ){
|
|
do {
|
|
*p++ = *pString++;
|
|
} while( *pString && *pString != '`' );
|
|
*p++ = '`';
|
|
*p = 0;
|
|
}
|
|
else {
|
|
*p = '"';
|
|
p++;
|
|
while( pString && *pString ){
|
|
if( *pString == '"' ){
|
|
strcpy( p, """ );
|
|
p += 6;
|
|
}
|
|
else {
|
|
*p++ = *pString;
|
|
}
|
|
pString++;
|
|
}
|
|
*p++ = '"';
|
|
*p = 0;
|
|
}
|
|
return pRet;
|
|
}
|
|
|
|
char *edt_FetchParamString( PA_Tag *pTag, char* param, int16 win_csid ){
|
|
PA_Block buff;
|
|
char *str;
|
|
char *retVal = 0;
|
|
|
|
buff = PA_FetchParamValue(pTag, param, win_csid);
|
|
if (buff != NULL)
|
|
{
|
|
PA_LOCK(str, char *, buff);
|
|
lo_StripTextWhitespace(str, XP_STRLEN(str));
|
|
if( str ) {
|
|
retVal = XP_STRDUP( str );
|
|
}
|
|
PA_UNLOCK(buff);
|
|
PA_FREE(buff);
|
|
}
|
|
return retVal;
|
|
}
|
|
|
|
XP_Bool edt_FetchParamBoolExist( PA_Tag *pTag, char* param, int16 csid ){
|
|
PA_Block buff;
|
|
|
|
buff = PA_FetchParamValue(pTag, param, csid);
|
|
if (buff != NULL){
|
|
PA_FREE(buff);
|
|
return TRUE;
|
|
}
|
|
else {
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
XP_Bool edt_FetchDimension( PA_Tag *pTag, char* param,
|
|
int32 *pValue, XP_Bool *pPercent,
|
|
int32 nDefaultValue, XP_Bool bDefaultPercent, int16 csid ){
|
|
PA_Block buff;
|
|
char *str;
|
|
int32 value;
|
|
|
|
// Fill in defaults
|
|
*pValue = nDefaultValue;
|
|
*pPercent = bDefaultPercent;
|
|
|
|
buff = PA_FetchParamValue(pTag, param, csid);
|
|
XP_Bool result = buff != NULL;
|
|
if( buff != NULL )
|
|
{
|
|
PA_LOCK(str, char *, buff);
|
|
if( str != 0 ){
|
|
value = XP_ATOI(str);
|
|
*pValue = value;
|
|
*pPercent = (NULL != XP_STRCHR(str, '%'));
|
|
}
|
|
PA_UNLOCK(buff);
|
|
PA_FREE(buff);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
ED_Alignment edt_FetchParamAlignment( PA_Tag* pTag, ED_Alignment eDefault, XP_Bool bVAlign, int16 csid ){
|
|
PA_Block buff;
|
|
char *str;
|
|
ED_Alignment retVal = eDefault;
|
|
|
|
buff = PA_FetchParamValue(pTag, bVAlign ? PARAM_VALIGN : PARAM_ALIGN, csid);
|
|
if (buff != NULL)
|
|
{
|
|
XP_Bool floating;
|
|
|
|
floating = FALSE;
|
|
PA_LOCK(str, char *, buff);
|
|
lo_StripTextWhitespace(str, XP_STRLEN(str));
|
|
// LTNOTE: this is a hack. We should do a better job maping these
|
|
// could cause problems in the future.
|
|
retVal = (ED_Alignment) lo_EvalAlignParam(str, &floating);
|
|
PA_UNLOCK(buff);
|
|
PA_FREE(buff);
|
|
}
|
|
return retVal;
|
|
}
|
|
|
|
int32 edt_FetchParamInt( PA_Tag *pTag, char* param, int32 defValue, int16 csid ){
|
|
return edt_FetchParamInt(pTag, param, defValue, defValue, csid);
|
|
}
|
|
|
|
int32 edt_FetchParamInt( PA_Tag *pTag, char* param, int32 defValue, int32 defValueIfParamButNoValue, int16 csid ){
|
|
PA_Block buff;
|
|
char *str;
|
|
int32 retVal = defValue;
|
|
|
|
buff = PA_FetchParamValue(pTag, param, csid);
|
|
if (buff != NULL)
|
|
{
|
|
PA_LOCK(str, char *, buff);
|
|
if( str != 0 ){
|
|
if ( *str == '\0' ) { /* They mentioned the param, but gave to value. */
|
|
retVal = defValueIfParamButNoValue;
|
|
}
|
|
else {
|
|
retVal = XP_ATOI(str);
|
|
}
|
|
}
|
|
PA_UNLOCK(buff);
|
|
PA_FREE(buff);
|
|
}
|
|
return retVal;
|
|
}
|
|
|
|
ED_Color edt_FetchParamColor( PA_Tag *pTag, char* param, int16 csid ){
|
|
PA_Block buff = PA_FetchParamValue(pTag, param, csid);
|
|
ED_Color retVal;
|
|
retVal.SetUndefined();
|
|
if (buff != NULL)
|
|
{
|
|
char *color_str;
|
|
PA_LOCK(color_str, char *, buff);
|
|
lo_StripTextWhitespace(color_str, XP_STRLEN(color_str));
|
|
uint8 red, green, blue;
|
|
LO_ParseRGB(color_str, &red, &green, &blue);
|
|
retVal = EDT_RGB(red,green,blue);
|
|
PA_UNLOCK(buff);
|
|
PA_FREE(buff);
|
|
}
|
|
return retVal;
|
|
}
|
|
|
|
PRIVATE
|
|
XP_Bool edt_NameInList( char* pName, char** ppNameList ){
|
|
int i = 0;
|
|
while( ppNameList && ppNameList[i] ){
|
|
if( XP_STRCASECMP( pName, ppNameList[i]) == 0 ){
|
|
return TRUE;
|
|
}
|
|
i++;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
char* edt_FetchParamExtras( PA_Tag *pTag, char**ppKnownParams, int16 win_csid ){
|
|
char** ppNames;
|
|
char** ppValues;
|
|
char* pRet = 0;
|
|
int i = 0;
|
|
char *pSpace = "";
|
|
int iMax;
|
|
|
|
iMax = PA_FetchAllNameValues( pTag, &ppNames, &ppValues, win_csid );
|
|
|
|
while( i < iMax ){
|
|
if( !edt_NameInList( ppNames[i], ppKnownParams ) ){
|
|
if( ppValues[i] ){
|
|
pRet = PR_sprintf_append( pRet, "%s%s=%s",
|
|
pSpace, ppNames[i], edt_MakeParamString( ppValues[i] ) );
|
|
}
|
|
else {
|
|
pRet = PR_sprintf_append( pRet, "%s%s", pSpace, ppNames[i] );
|
|
}
|
|
pSpace = " ";
|
|
}
|
|
i++;
|
|
}
|
|
|
|
i = 0;
|
|
while( i < iMax ){
|
|
if( ppNames[i] ) XP_FREE( ppNames[i] );
|
|
if( ppValues[i] ) XP_FREE( ppValues[i] );
|
|
i++;
|
|
}
|
|
|
|
if( ppNames ) XP_FREE( ppNames );
|
|
if( ppValues ) XP_FREE( ppValues );
|
|
|
|
return pRet;
|
|
}
|
|
|
|
// Override existing.
|
|
void edt_FetchParamString2(PA_Tag* pTag, char* param, char*& data, int16 win_csid){
|
|
char* result = edt_FetchParamString( pTag, param, win_csid );
|
|
if ( result ) {
|
|
if ( data ){
|
|
XP_FREE(data);
|
|
}
|
|
data = result;
|
|
}
|
|
}
|
|
|
|
// Override existing.
|
|
void edt_FetchParamColor2( PA_Tag *pTag, char* param, ED_Color& data, int16 csid ){
|
|
ED_Color result = edt_FetchParamColor(pTag, param, csid);
|
|
if ( result.IsDefined() ) {
|
|
data = result;
|
|
}
|
|
}
|
|
|
|
// Append to existing extras. To Do: Have new versions of a parameter overwrite the old ones.
|
|
void edt_FetchParamExtras2( PA_Tag *pTag, char**ppKnownParams, char*& data, int16 win_csid ){
|
|
char* result = edt_FetchParamExtras( pTag, ppKnownParams, win_csid );
|
|
if ( result ) {
|
|
if ( data ) {
|
|
data = PR_sprintf_append(data, " %s", result);
|
|
XP_FREE(result);
|
|
}
|
|
else {
|
|
data = result;
|
|
}
|
|
}
|
|
}
|
|
|
|
XP_Bool edt_ReplaceParamValue( PA_Tag *pTag, char * pName, char * pValue, int16 csid ){
|
|
XP_ASSERT(pName);
|
|
XP_ASSERT(pTag);
|
|
|
|
char** ppNames;
|
|
char** ppValues;
|
|
char* pNew = 0;
|
|
int i = 0;
|
|
char *pSpace = "";
|
|
int iMax;
|
|
XP_Bool bFound = FALSE;
|
|
|
|
iMax = PA_FetchAllNameValues( pTag, &ppNames, &ppValues, csid );
|
|
|
|
while( i < iMax ){
|
|
if( XP_STRCASECMP( pName, ppNames[i]) == 0 ){
|
|
bFound = TRUE;
|
|
if( pValue){
|
|
pNew = PR_sprintf_append( pNew, "%s%s=%s",
|
|
pSpace, pName, edt_MakeParamString( pValue ) );
|
|
}
|
|
} else {
|
|
if( ppValues[i] ){
|
|
pNew = PR_sprintf_append( pNew, "%s%s=%s",
|
|
pSpace, ppNames[i], edt_MakeParamString( ppValues[i] ) );
|
|
}
|
|
else {
|
|
pNew = PR_sprintf_append( pNew, "%s%s", pSpace, ppNames[i] );
|
|
}
|
|
pSpace = " ";
|
|
}
|
|
i++;
|
|
}
|
|
// Add value at end if not found
|
|
if( !bFound && pValue ){
|
|
pNew = PR_sprintf_append( pNew, "%s%s=%s", pSpace, pName, pValue );
|
|
}
|
|
// Terminate
|
|
pNew = PR_sprintf_append( pNew, "%s", ">" );
|
|
|
|
// Cleanup memory
|
|
i = 0;
|
|
while( i < iMax ){
|
|
if( ppNames[i] ) XP_FREE( ppNames[i] );
|
|
if( ppValues[i] ) XP_FREE( ppValues[i] );
|
|
i++;
|
|
}
|
|
|
|
if( ppNames ) XP_FREE( ppNames );
|
|
if( ppValues ) XP_FREE( ppValues );
|
|
|
|
// Now replace with new data
|
|
|
|
PA_Block buff;
|
|
char *locked_buff;
|
|
int iLen;
|
|
|
|
iLen = XP_STRLEN(pNew);
|
|
buff = (PA_Block)PA_ALLOC((iLen+1) * sizeof(char));
|
|
if (buff != NULL) {
|
|
PA_LOCK(locked_buff, char *, buff);
|
|
XP_BCOPY(pNew, locked_buff, iLen);
|
|
locked_buff[iLen] = '\0';
|
|
PA_UNLOCK(buff);
|
|
|
|
if( pTag->data ) XP_FREE(pTag->data);
|
|
pTag->data = buff;
|
|
pTag->data_len = iLen;
|
|
} else {
|
|
// LTNOTE: out of memory, should throw an exception
|
|
}
|
|
return bFound;
|
|
}
|
|
|
|
LO_Color* edt_MakeLoColor(ED_Color c) {
|
|
if( !c.IsDefined()){
|
|
return 0;
|
|
}
|
|
LO_Color *pColor = XP_NEW( LO_Color );
|
|
pColor->red = EDT_RED(c);
|
|
pColor->green = EDT_GREEN(c);
|
|
pColor->blue = EDT_BLUE(c);
|
|
return pColor;
|
|
}
|
|
|
|
void edt_SetLoColor( ED_Color c, LO_Color *pColor ){
|
|
if( !c.IsDefined()){
|
|
XP_ASSERT(FALSE);
|
|
return;
|
|
}
|
|
pColor->red = EDT_RED(c);
|
|
pColor->green = EDT_GREEN(c);
|
|
pColor->blue = EDT_BLUE(c);
|
|
}
|
|
|
|
|
|
#define CHARSET_SIZE 256
|
|
static PA_AmpEsc *ed_escapes[CHARSET_SIZE];
|
|
|
|
void edt_InitEscapes(int16 /*csid*/, XP_Bool bQuoteHiBits){
|
|
PA_AmpEsc *pEsc = PA_AmpEscapes;
|
|
|
|
XP_BZERO( ed_escapes, CHARSET_SIZE * sizeof( PA_AmpEsc* ) );
|
|
|
|
while( pEsc->value ){
|
|
int ch = 0xff & (int) pEsc->value;
|
|
if( ed_escapes[ ch ] == 0 ){
|
|
if ( ch == '&' || ch == '<' || ch == '>'
|
|
|| (NON_BREAKING_SPACE == ((char) ch))
|
|
|| (ch >= 128 && bQuoteHiBits ) ) {
|
|
ed_escapes[ ch ] = pEsc;
|
|
}
|
|
}
|
|
pEsc++;
|
|
}
|
|
}
|
|
|
|
void edt_PrintWithEscapes( CPrintState *ps, char *p, XP_Bool bBreakLines ){
|
|
int csid = ps->m_pBuffer->GetRAMCharSetID();
|
|
char *pBegin;
|
|
PA_AmpEsc *pEsc;
|
|
|
|
// break the lines after 70 characters if we can, but don't really do it
|
|
// in the formatted case.
|
|
while( p && *p ){
|
|
pBegin = p;
|
|
while( *p && ps->m_iCharPos < 70 && ed_escapes[(unsigned char)*p] == 0
|
|
&& ((unsigned char) *p) != ((unsigned char) NON_BREAKING_SPACE) ){
|
|
char* p2 = INTL_NextChar(csid, p);
|
|
int charWidth = p2 - p;
|
|
ps->m_iCharPos += charWidth;
|
|
p = p2;
|
|
}
|
|
while( *p && *p != ' ' && ed_escapes[(unsigned char)*p] == 0
|
|
&& ((unsigned char) *p) != ((unsigned char) NON_BREAKING_SPACE)){
|
|
char* p2 = INTL_NextChar(csid, p);
|
|
int charWidth = p2 - p;
|
|
ps->m_iCharPos += charWidth;
|
|
p = p2;
|
|
}
|
|
ps->m_pOut->Write( pBegin, p-pBegin);
|
|
|
|
if( *p && *p == ' ' && ps->m_iCharPos >= 70 ){
|
|
if( bBreakLines ){
|
|
ps->m_pOut->Write( "\n", 1 );
|
|
}
|
|
else {
|
|
ps->m_pOut->Write( " ", 1 );
|
|
}
|
|
ps->m_iCharPos = 0;
|
|
}
|
|
else if( *p && (pEsc = ed_escapes[(unsigned char)*p]) != 0 ){
|
|
ps->m_pOut->Write( "&",1 );
|
|
ps->m_pOut->Write( pEsc->str, pEsc->len );
|
|
ps->m_pOut->Write( ";",1 );
|
|
ps->m_iCharPos += pEsc->len+2;
|
|
}
|
|
else if (((unsigned char) *p) == ((unsigned char) NON_BREAKING_SPACE) ){
|
|
const char* nbsp = INTL_NonBreakingSpace(csid);
|
|
int nbsp_len = XP_STRLEN(nbsp);
|
|
ps->m_pOut->Write( (char*) nbsp, XP_STRLEN(nbsp));
|
|
}
|
|
|
|
// move past the space or special char
|
|
if( *p ){
|
|
p = INTL_NextChar(csid, p);
|
|
}
|
|
}
|
|
}
|
|
|
|
char *edt_LocalName( char *pURL ){
|
|
char *pDest = FE_URLToLocalName( pURL );
|
|
#if 0
|
|
if( pDest && FE_EditorPrefConvertFileCaseOnWrite( ) ){
|
|
char *url_ptr = pDest;
|
|
while ( *url_ptr != '\0' ){
|
|
*url_ptr = XP_TO_LOWER(*url_ptr);
|
|
url_ptr++;
|
|
}
|
|
}
|
|
#endif
|
|
return pDest;
|
|
}
|
|
|
|
PA_Block PA_strdup( char* s ){
|
|
char *new_str;
|
|
PA_Block new_buff;
|
|
|
|
if( s == 0 ){
|
|
return 0;
|
|
}
|
|
|
|
new_buff = (PA_Block)PA_ALLOC(XP_STRLEN(s) + 1);
|
|
if (new_buff != NULL)
|
|
{
|
|
PA_LOCK(new_str, char *, new_buff);
|
|
XP_STRCPY(new_str, s);
|
|
PA_UNLOCK(new_buff);
|
|
}
|
|
return new_buff;
|
|
}
|
|
|
|
void edt_SetTagData( PA_Tag* pTag, char* pTagData){
|
|
PA_Block buff;
|
|
char *locked_buff;
|
|
int iLen;
|
|
|
|
iLen = XP_STRLEN(pTagData);
|
|
buff = (PA_Block)PA_ALLOC((iLen+1) * sizeof(char));
|
|
if (buff != NULL)
|
|
{
|
|
PA_LOCK(locked_buff, char *, buff);
|
|
XP_BCOPY(pTagData, locked_buff, iLen);
|
|
locked_buff[iLen] = '\0';
|
|
PA_UNLOCK(buff);
|
|
}
|
|
else {
|
|
// LTNOTE: out of memory, should throw an exception
|
|
return;
|
|
}
|
|
pTag->data = buff;
|
|
pTag->data_len = iLen;
|
|
pTag->next = NULL;
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Create a tag and add it to the list
|
|
//
|
|
void edt_AddTag( PA_Tag*& pStart, PA_Tag*& pEnd, TagType t, XP_Bool bIsEnd,
|
|
char *pTagData ){
|
|
PA_Tag *pTag = XP_NEW( PA_Tag );
|
|
XP_BZERO( pTag, sizeof( PA_Tag ) );
|
|
pTag->type = t;
|
|
pTag->is_end = bIsEnd;
|
|
edt_SetTagData(pTag, pTagData ? pTagData : ">" );
|
|
if( pStart == 0 ){
|
|
pStart = pTag;
|
|
}
|
|
if( pEnd ){
|
|
pEnd->next = pTag;
|
|
}
|
|
pEnd = pTag;
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// CEditPosition
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void CEditPositionComparable::CalcPosition( TXP_GrowableArray_int32* pA,
|
|
CEditPosition *pPos ){
|
|
|
|
CEditElement *pElement, *pParent, *pSib;
|
|
int i;
|
|
|
|
pA->Empty();
|
|
pA->Add( pPos->Offset() );
|
|
pElement = pPos->Element();
|
|
if( pElement ){
|
|
while( (pParent = pElement->GetParent()) != NULL ){
|
|
i = 0;
|
|
pSib = pParent->GetChild();
|
|
while( pSib != pElement ){
|
|
pSib = pSib->GetNextSibling();
|
|
i++;
|
|
}
|
|
pA->Add(i);
|
|
pElement = pParent;
|
|
}
|
|
}
|
|
}
|
|
|
|
int CEditPositionComparable::Compare( CEditPosition *pPos ){
|
|
TXP_GrowableArray_int32 toArray;
|
|
|
|
CalcPosition( &toArray, pPos );
|
|
|
|
int32 i = m_Array.Size();
|
|
int32 iTo = toArray.Size();
|
|
int32 iDiff;
|
|
|
|
while( i && iTo ){
|
|
iDiff = toArray[--iTo] - m_Array[--i];
|
|
if( iDiff != 0 ) {
|
|
return (iDiff > 0 ? 1 : -1 );
|
|
}
|
|
}
|
|
if( i == 0 && iTo == 0){
|
|
return 0;
|
|
}
|
|
iDiff = iTo - i;
|
|
return (iDiff > 0 ? 1 : -1 );
|
|
}
|
|
|
|
|
|
// CEditInsertPoint
|
|
|
|
CEditInsertPoint::CEditInsertPoint() {
|
|
m_pElement = 0;
|
|
m_iPos = 0;
|
|
m_bStickyAfter = FALSE;
|
|
}
|
|
|
|
CEditInsertPoint::CEditInsertPoint(CEditLeafElement* pElement, ElementOffset iPos) {
|
|
m_pElement = pElement;
|
|
m_iPos = iPos;
|
|
m_bStickyAfter = FALSE;
|
|
}
|
|
|
|
CEditInsertPoint::CEditInsertPoint(CEditElement* pElement, ElementOffset iPos){
|
|
m_pElement = pElement->Leaf();
|
|
m_iPos = iPos;
|
|
m_bStickyAfter = FALSE;
|
|
}
|
|
|
|
CEditInsertPoint::CEditInsertPoint(CEditElement* pElement, ElementOffset iPos, XP_Bool bStickyAfter){
|
|
m_pElement = pElement->Leaf();
|
|
m_iPos = iPos;
|
|
m_bStickyAfter = bStickyAfter;
|
|
}
|
|
|
|
XP_Bool CEditInsertPoint::operator==(const CEditInsertPoint& other ) {
|
|
return m_pElement == other.m_pElement && m_iPos == other.m_iPos;
|
|
}
|
|
|
|
XP_Bool CEditInsertPoint::operator!=(const CEditInsertPoint& other ) {
|
|
return ! (*this == other);
|
|
}
|
|
|
|
XP_Bool CEditInsertPoint::operator<(const CEditInsertPoint& other ) {
|
|
return Compare(other) < 0;
|
|
}
|
|
|
|
XP_Bool CEditInsertPoint::operator<=(const CEditInsertPoint& other ) {
|
|
return Compare(other) <= 0;
|
|
}
|
|
|
|
XP_Bool CEditInsertPoint::operator>=(const CEditInsertPoint& other ) {
|
|
return Compare(other) >= 0;
|
|
}
|
|
|
|
XP_Bool CEditInsertPoint::operator>(const CEditInsertPoint& other ) {
|
|
return Compare(other) > 0;
|
|
}
|
|
|
|
CEditElement* CEditInsertPoint::FindNonEmptyElement( CEditElement *pStartElement ){
|
|
CEditElement* pOldElement = NULL;
|
|
while ( pStartElement && pStartElement != pOldElement ) {
|
|
pOldElement = pStartElement;
|
|
if( !pStartElement->IsLeaf() ){
|
|
pStartElement = pStartElement->PreviousLeaf();
|
|
}
|
|
else if( pStartElement->Leaf()->GetLayoutElement() == 0 ){
|
|
pStartElement = pStartElement->PreviousLeaf();
|
|
}
|
|
}
|
|
return pStartElement;
|
|
}
|
|
|
|
int CEditInsertPoint::Compare(const CEditInsertPoint& other ) {
|
|
XP_Bool bAIsBreak = FALSE;
|
|
LO_Element* pA = m_pElement->GetLayoutElement();
|
|
if ( ! pA ) {
|
|
bAIsBreak = TRUE;
|
|
pA = FindNonEmptyElement(m_pElement)->Leaf()->GetLayoutElement();
|
|
}
|
|
XP_Bool bBIsBreak = FALSE;
|
|
LO_Element* pB = other.m_pElement->GetLayoutElement();
|
|
if ( ! pB ) {
|
|
bBIsBreak = TRUE;
|
|
pB = FindNonEmptyElement(other.m_pElement)->Leaf()->GetLayoutElement();
|
|
}
|
|
if ( !pA || !pB ) {
|
|
XP_ASSERT(FALSE); // Phantom insert points.
|
|
return 0;
|
|
}
|
|
int32 aIndex = pA->lo_any.ele_id;
|
|
int32 bIndex = pB->lo_any.ele_id;
|
|
if ( aIndex < bIndex ) {
|
|
return -1;
|
|
}
|
|
else if ( aIndex == bIndex ) {
|
|
// Same element. Compare positions.
|
|
if ( m_iPos < other.m_iPos ) {
|
|
return -1;
|
|
}
|
|
else if ( m_iPos == other.m_iPos ) {
|
|
if ( bAIsBreak < bBIsBreak ) {
|
|
return -1;
|
|
}
|
|
else if ( bAIsBreak == bBIsBreak ) {
|
|
return 0;
|
|
}
|
|
else {
|
|
return 1;
|
|
}
|
|
}
|
|
else {
|
|
return 1;
|
|
}
|
|
}
|
|
else {
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
XP_Bool CEditInsertPoint::IsDenormalizedVersionOf(const CEditInsertPoint& other){
|
|
return other.m_iPos == 0 &&
|
|
m_iPos == m_pElement->GetLen() &&
|
|
m_pElement->NextLeaf() == other.m_pElement;
|
|
}
|
|
|
|
XP_Bool CEditInsertPoint::IsStartOfElement() {
|
|
return m_iPos <= 0;
|
|
}
|
|
|
|
XP_Bool CEditInsertPoint::IsEndOfElement() {
|
|
return m_iPos >= m_pElement->GetLen();
|
|
}
|
|
|
|
XP_Bool CEditInsertPoint::IsStartOfContainer() {
|
|
return IsStartOfElement()
|
|
&& m_pElement->PreviousLeafInContainer() == NULL;
|
|
}
|
|
|
|
XP_Bool CEditInsertPoint::IsEndOfContainer() {
|
|
return IsEndOfElement()
|
|
&& m_pElement->LeafInContainerAfter() == NULL;
|
|
}
|
|
|
|
XP_Bool CEditInsertPoint::IsStartOfDocument(){
|
|
return IsStartOfElement() &&
|
|
m_pElement->PreviousLeaf() == NULL;
|
|
}
|
|
|
|
XP_Bool CEditInsertPoint::IsEndOfDocument(){
|
|
return m_pElement->IsEndOfDocument();
|
|
}
|
|
|
|
XP_Bool CEditInsertPoint::GapWithBothSidesAllowed(){
|
|
XP_Bool bResult = FALSE;
|
|
XP_Bool bAllowBothSidesOfGap = m_pElement->AllowBothSidesOfGap();
|
|
if ( bAllowBothSidesOfGap
|
|
&& IsEndOfElement()
|
|
&& !IsEndOfContainer() ) {
|
|
bResult = TRUE;
|
|
}
|
|
else if ( IsStartOfElement() && ! IsStartOfContainer() ) {
|
|
if ( bAllowBothSidesOfGap ) {
|
|
bResult = TRUE;
|
|
}
|
|
else {
|
|
CEditLeafElement* pPrev = m_pElement->PreviousLeaf();
|
|
if ( pPrev && pPrev->AllowBothSidesOfGap() ) {
|
|
bResult = TRUE;
|
|
}
|
|
}
|
|
}
|
|
return bResult;
|
|
}
|
|
|
|
XP_Bool CEditInsertPoint::IsLineBreak(){
|
|
return IsHardLineBreak() || IsSoftLineBreak();
|
|
}
|
|
|
|
XP_Bool CEditInsertPoint::IsSoftLineBreak(){
|
|
XP_Bool result = FALSE;
|
|
CEditTextElement* pText = CEditTextElement::Cast(m_pElement);
|
|
if ( pText ){
|
|
int iOffset;
|
|
LO_Element* pLOElement;
|
|
if ( pText->GetLOElementAndOffset(m_iPos, m_bStickyAfter,
|
|
pLOElement, iOffset) ){
|
|
if ( pLOElement->type == LO_LINEFEED ){
|
|
result = TRUE;
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
XP_Bool CEditInsertPoint::IsHardLineBreak(){
|
|
return IsStartOfElement() && m_pElement->CausesBreakBefore()
|
|
|| IsEndOfElement() && m_pElement->CausesBreakAfter();
|
|
}
|
|
|
|
XP_Bool CEditInsertPoint::IsSpace() {
|
|
XP_Bool result = FALSE;
|
|
if ( m_pElement->IsA(P_TEXT) ) {
|
|
if ( m_iPos == m_pElement->GetLen() ){
|
|
CEditLeafElement *pNext = m_pElement->TextInContainerAfter();
|
|
if( pNext
|
|
&& pNext->IsA(P_TEXT)
|
|
&& pNext->Text()->GetLen() != 0
|
|
&& pNext->Text()->GetText()[0] == ' ') {
|
|
result = TRUE;
|
|
}
|
|
}
|
|
else if ( m_pElement->Text()->GetText()[m_iPos] == ' ' ) {
|
|
result = TRUE;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
XP_Bool CEditInsertPoint::IsSpaceBeforeOrAfter() {
|
|
XP_Bool result = IsSpace();
|
|
if ( !result ) {
|
|
CEditInsertPoint before = PreviousPosition();
|
|
if ( before != *this ) {
|
|
result = before.IsSpace();
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
CEditInsertPoint CEditInsertPoint::NextPosition(){
|
|
CEditInsertPoint result;
|
|
m_pElement->NextPosition(m_iPos, result.m_pElement, result.m_iPos);
|
|
return result;
|
|
}
|
|
|
|
CEditInsertPoint CEditInsertPoint::PreviousPosition(){
|
|
CEditInsertPoint result;
|
|
m_pElement->PrevPosition(m_iPos, result.m_pElement, result.m_iPos);
|
|
// Work around what is probably a bug in PrevPosition.
|
|
if ( m_iPos == 0 && result.m_iPos == result.m_pElement->GetLen() ) {
|
|
result.m_pElement->PrevPosition(result.m_iPos, result.m_pElement, result.m_iPos);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
void CEditInsertPoint::Print(IStreamOut& stream) {
|
|
stream.Printf("0x%08lx.%d%s", m_pElement, m_iPos, m_bStickyAfter ? "+" : "" );
|
|
}
|
|
#endif
|
|
|
|
|
|
|
|
// CEditSelection
|
|
|
|
CEditSelection::CEditSelection(){
|
|
m_bFromStart = FALSE;
|
|
}
|
|
|
|
CEditSelection::CEditSelection(CEditElement* pStart, intn iStartPos,
|
|
CEditElement* pEnd, intn iEndPos, XP_Bool fromStart)
|
|
: m_start(pStart, iStartPos), m_end(pEnd, iEndPos), m_bFromStart(fromStart)
|
|
{
|
|
}
|
|
|
|
CEditSelection::CEditSelection(const CEditInsertPoint& start,
|
|
const CEditInsertPoint& end, XP_Bool fromStart)
|
|
: m_start(start), m_end(end), m_bFromStart(fromStart)
|
|
{
|
|
}
|
|
|
|
XP_Bool CEditSelection::operator==(const CEditSelection& other ){
|
|
return m_start == other.m_start &&
|
|
m_end == other.m_end && m_bFromStart == other.m_bFromStart;
|
|
}
|
|
|
|
XP_Bool CEditSelection::operator!=(const CEditSelection& other ){
|
|
return ! (*this == other);
|
|
}
|
|
|
|
XP_Bool CEditSelection::EqualRange(CEditSelection& other){
|
|
return m_start == other.m_start && m_end == other.m_end;
|
|
}
|
|
|
|
XP_Bool CEditSelection::IsInsertPoint() {
|
|
/* It's an insert point if the two edges are equal, OR if the
|
|
* start is at the end of an element, and the
|
|
* end is at the start of an element, and
|
|
* the two elements are next to each other,
|
|
* and they're in the same container. (Whew!)
|
|
*/
|
|
return m_start == m_end ||
|
|
( m_start.IsEndOfElement() &&
|
|
m_end.IsStartOfElement() &&
|
|
m_start.m_pElement->LeafInContainerAfter() == m_end.m_pElement
|
|
);
|
|
}
|
|
|
|
CEditInsertPoint* CEditSelection::GetEdge(XP_Bool bEnd){
|
|
if ( bEnd ) return &m_end;
|
|
else return &m_start;
|
|
}
|
|
|
|
CEditInsertPoint* CEditSelection::GetActiveEdge(){
|
|
return GetEdge(!m_bFromStart);
|
|
}
|
|
|
|
CEditInsertPoint* CEditSelection::GetAnchorEdge(){
|
|
return GetEdge(m_bFromStart);
|
|
}
|
|
|
|
XP_Bool CEditSelection::Intersects(CEditSelection& other) {
|
|
return m_start < other.m_end && other.m_start < m_end;
|
|
}
|
|
|
|
XP_Bool CEditSelection::Contains(CEditInsertPoint& point) {
|
|
return m_start <= point && point < m_end;
|
|
}
|
|
|
|
XP_Bool CEditSelection::Contains(CEditSelection& other) {
|
|
return ContainsStart(other) && other.m_end <= m_end;
|
|
}
|
|
|
|
XP_Bool CEditSelection::ContainsStart(CEditSelection& other) {
|
|
return Contains(other.m_start);
|
|
}
|
|
|
|
XP_Bool CEditSelection::ContainsEnd(CEditSelection& other) {
|
|
return Contains(other.m_end) || other.m_end == m_end;
|
|
}
|
|
|
|
XP_Bool CEditSelection::ContainsEdge(CEditSelection& other, XP_Bool bEnd){
|
|
if ( bEnd )
|
|
return ContainsEnd(other);
|
|
else
|
|
return ContainsStart(other);
|
|
}
|
|
|
|
XP_Bool CEditSelection::CrossesOver(CEditSelection& other) {
|
|
XP_Bool bContainsStart = ContainsStart(other);
|
|
XP_Bool bContainsEnd = ContainsEnd(other);
|
|
return bContainsStart ^ bContainsEnd;
|
|
}
|
|
|
|
XP_Bool CEditSelection::ClipTo(CEditSelection& other) {
|
|
// True if the result is defined. No change to this if it isn't
|
|
XP_Bool intersects = Intersects(other);
|
|
if ( intersects ) {
|
|
if ( m_start < other.m_start ) {
|
|
m_start = other.m_start;
|
|
}
|
|
if ( other.m_end < m_end ) {
|
|
m_end = other.m_end;
|
|
}
|
|
}
|
|
return intersects;
|
|
}
|
|
|
|
CEditElement* CEditSelection::GetCommonAncestor(){
|
|
return m_start.m_pElement->GetCommonAncestor(m_end.m_pElement);
|
|
}
|
|
|
|
XP_Bool CEditSelection::CrossesSubDocBoundary(){
|
|
// The intent of this method is "If this selection were cut, would the
|
|
// document become malformed?"
|
|
|
|
// A document could be malformed if the selection crosses the table
|
|
// boundary, or if it would leave a table without it's protective
|
|
// buffer of containers.
|
|
|
|
XP_Bool result = FALSE;
|
|
if ( ! IsInsertPoint() ){
|
|
CEditElement* pStartCell = m_start.m_pElement->GetSubDocOrLayerSkipRoot();
|
|
CEditElement* pEndCell = m_end.m_pElement->GetSubDocOrLayerSkipRoot();
|
|
CEditElement* pClosedEndCell = GetClosedEndContainer()->GetSubDocOrLayerSkipRoot();
|
|
result = pStartCell != pEndCell || pStartCell != pClosedEndCell;
|
|
if ( result ) {
|
|
// This is only OK if the selection spans an entire table.
|
|
Bool bStartBad = FALSE;
|
|
Bool bEndBad = FALSE;
|
|
if ( pStartCell ) {
|
|
CEditSelection all;
|
|
pStartCell->GetTableOrLayerIgnoreSubdoc()->GetAll(all);
|
|
bStartBad = ! Contains(all);
|
|
}
|
|
|
|
if ( pEndCell ) {
|
|
CEditSelection all;
|
|
pEndCell->GetTableOrLayerIgnoreSubdoc()->GetAll(all);
|
|
bEndBad = ! Contains(all);
|
|
}
|
|
result = bStartBad || bEndBad;
|
|
}
|
|
else if ( pStartCell && pEndCell != pClosedEndCell ) {
|
|
// If the selection spans an entire cell, but not an entire table, that's bad
|
|
CEditSelection all;
|
|
pStartCell->GetTableOrLayerIgnoreSubdoc()->GetAll(all);
|
|
result = *this != all;
|
|
}
|
|
else {
|
|
// One further check: If the selection starts at the start of
|
|
// a paragraph, and the selection ends at the end of a paragraph
|
|
// and the previous item is a table, and the
|
|
// next item is not a container, then we can't cut. The reason is
|
|
// that tables need to have containers after them.
|
|
|
|
// Note that m_end is one more than is selected.
|
|
if ( m_start.IsStartOfContainer() &&
|
|
m_end.IsStartOfContainer() ) {
|
|
// The selection spans whole containers.
|
|
CEditContainerElement* pStartContainer = GetStartContainer();
|
|
CEditElement* pPreviousSib = pStartContainer->GetPreviousSibling();
|
|
|
|
if ( pPreviousSib && pPreviousSib->IsTable() ) {
|
|
CEditContainerElement* pEndContainer = GetClosedEndContainer();
|
|
CEditElement* pNextSib = pEndContainer->GetNextSibling();
|
|
if ( !pNextSib || pNextSib->IsEndContainer() || pNextSib->IsTable() ) {
|
|
result = TRUE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
CEditSubDocElement* CEditSelection::GetEffectiveSubDoc(XP_Bool bEnd){
|
|
CEditInsertPoint* pEdge = GetEdge(bEnd);
|
|
CEditSubDocElement* pSubDoc = pEdge->m_pElement->GetSubDoc();
|
|
while ( pSubDoc ) {
|
|
// If the edge is actually the edge of a subdoc, skip out of the subdoc.
|
|
CEditSelection wholeSubDoc;
|
|
pSubDoc->GetAll(wholeSubDoc);
|
|
// If this is a wholeSubDoc Selection, bump up
|
|
if ( *this != wholeSubDoc ) {
|
|
if ( *pEdge != *wholeSubDoc.GetEdge(bEnd) )
|
|
break;
|
|
// If the other end of the selection is is in this same subdoc, stop
|
|
if ( wholeSubDoc.ContainsEdge(*this, !bEnd) )
|
|
break;
|
|
}
|
|
CEditElement* pSubDocParent = pSubDoc->GetParent();
|
|
if ( !pSubDocParent )
|
|
break;
|
|
CEditSubDocElement* pParentSubDoc = pSubDocParent->GetSubDoc();
|
|
if ( ! pParentSubDoc )
|
|
break;
|
|
pSubDoc = pParentSubDoc;
|
|
}
|
|
return pSubDoc;
|
|
}
|
|
|
|
XP_Bool CEditSelection::ExpandToNotCrossStructures(){
|
|
XP_Bool bChanged = FALSE;
|
|
if ( ! IsInsertPoint() ){
|
|
CEditElement* pGrandfatherStart = m_start.m_pElement->GetParent()->GetParent();
|
|
CEditElement* pGrandfatherEnd;
|
|
CEditElement* pEffectiveEndContianer;
|
|
if ( m_end.IsEndOfDocument() || EndsAtStartOfContainer()) {
|
|
pEffectiveEndContianer = m_end.m_pElement->PreviousLeaf()->GetParent();
|
|
}
|
|
else {
|
|
pEffectiveEndContianer = m_end.m_pElement->GetParent();
|
|
}
|
|
pGrandfatherEnd = pEffectiveEndContianer->GetParent();
|
|
if ( pGrandfatherStart != pGrandfatherEnd ){
|
|
bChanged = TRUE;
|
|
CEditElement* pAncestor = pGrandfatherStart->GetCommonAncestor(pGrandfatherEnd);
|
|
if ( ! pAncestor ) {
|
|
XP_ASSERT(FALSE);
|
|
return FALSE;
|
|
}
|
|
// If we're in a table, we need to return the whole table.
|
|
if ( pAncestor->IsTableRow() ) {
|
|
pAncestor = pAncestor->GetTable();
|
|
if ( ! pAncestor ) {
|
|
XP_ASSERT(FALSE);
|
|
return FALSE;
|
|
}
|
|
}
|
|
if ( pAncestor->IsTable() ) {
|
|
pAncestor->GetAll(*this);
|
|
return TRUE;
|
|
}
|
|
CEditLeafElement* pCleanStart = pAncestor->GetChildContaining(m_start.m_pElement)->GetFirstMostChild()->Leaf();
|
|
CEditLeafElement* pCleanEnd = pAncestor->GetChildContaining(pEffectiveEndContianer)->GetLastMostChild()->NextLeaf();
|
|
m_start.m_pElement = pCleanStart;
|
|
m_start.m_iPos = 0;
|
|
if ( pCleanEnd ) {
|
|
m_end.m_pElement = pCleanEnd;
|
|
m_end.m_iPos = 0;
|
|
}
|
|
else {
|
|
XP_ASSERT(FALSE); // Somehow we've lost the end of the document element.
|
|
pCleanEnd = m_end.m_pElement->GetParent()->GetLastMostChild()->Leaf();
|
|
m_end.m_pElement = pCleanEnd;
|
|
m_end.m_iPos = pCleanEnd->Leaf()->GetLen();
|
|
}
|
|
}
|
|
}
|
|
return bChanged;
|
|
}
|
|
|
|
void CEditSelection::ExpandToBeCutable(){
|
|
// Very similar to ExpandToNotCrossDocumentStructures, except that
|
|
// the result is always Cutable.
|
|
if ( ! IsInsertPoint() ){
|
|
CEditElement* pGrandfatherStart = m_start.m_pElement->GetParent()->GetParent();
|
|
CEditElement* pGrandfatherEnd;
|
|
CEditElement* pEffectiveEndContianer;
|
|
if ( m_end.IsEndOfDocument()) {
|
|
pEffectiveEndContianer = m_end.m_pElement->PreviousLeaf()->GetParent();
|
|
}
|
|
else {
|
|
pEffectiveEndContianer = m_end.m_pElement->GetParent();
|
|
}
|
|
pGrandfatherEnd = pEffectiveEndContianer->GetParent();
|
|
if ( pGrandfatherStart != pGrandfatherEnd ){
|
|
CEditElement* pAncestor = pGrandfatherStart->GetCommonAncestor(pGrandfatherEnd);
|
|
if ( ! pAncestor ) {
|
|
XP_ASSERT(FALSE);
|
|
return;
|
|
}
|
|
// If we're in a table, we need to return the whole table.
|
|
if ( pAncestor->IsTableRow() ) {
|
|
pAncestor = pAncestor->GetTable();
|
|
if ( ! pAncestor ) {
|
|
XP_ASSERT(FALSE);
|
|
return;
|
|
}
|
|
}
|
|
if ( pAncestor->IsTable() ) {
|
|
pAncestor->GetAll(*this);
|
|
return;
|
|
}
|
|
CEditLeafElement* pCleanStart = pAncestor->GetChildContaining(m_start.m_pElement)->GetFirstMostChild()->Leaf();
|
|
CEditLeafElement* pCleanEnd = pAncestor->GetChildContaining(pEffectiveEndContianer)->GetLastMostChild()->NextLeaf();
|
|
m_start.m_pElement = pCleanStart;
|
|
m_start.m_iPos = 0;
|
|
if ( pCleanEnd ) {
|
|
m_end.m_pElement = pCleanEnd;
|
|
m_end.m_iPos = 0;
|
|
}
|
|
else {
|
|
XP_ASSERT(FALSE); // Somehow we've lost the end of the document element.
|
|
pCleanEnd = m_end.m_pElement->GetParent()->GetLastMostChild()->Leaf();
|
|
m_end.m_pElement = pCleanEnd;
|
|
m_end.m_iPos = pCleanEnd->Leaf()->GetLen();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CEditSelection::ExpandToIncludeFragileSpaces() {
|
|
// Expand the selection to include spaces that will be
|
|
// trimmed if we did a cut of the original selection.
|
|
if ( ! IsInsertPoint() ){
|
|
CEditInsertPoint before = m_start.PreviousPosition();
|
|
// Either we backed up one position, or we hit the beginning
|
|
// of the document.
|
|
if ( before == m_start || before.IsSpace() ) {
|
|
if ( m_end.IsSpace() ) {
|
|
if ( ! m_end.m_pElement->InFormattedText() ) {
|
|
m_end = m_end.NextPosition();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CEditSelection::ExpandToEncloseWholeContainers(){
|
|
XP_Bool bWasInsertPoint = IsInsertPoint();
|
|
CEditLeafElement* pCleanStart = m_start.m_pElement->GetParent()->GetFirstMostChild()->Leaf();
|
|
m_start.m_pElement = pCleanStart;
|
|
m_start.m_iPos = 0;
|
|
if ( !m_end.IsStartOfContainer() || bWasInsertPoint ) {
|
|
CEditLeafElement* pCleanEnd = m_end.m_pElement->GetParent()->GetLastMostChild()->NextLeaf();
|
|
if ( pCleanEnd ) {
|
|
m_end.m_pElement = pCleanEnd;
|
|
m_end.m_iPos = 0;
|
|
}
|
|
else {
|
|
XP_ASSERT(FALSE); // Somehow we've lost the end of the document element.
|
|
pCleanEnd = m_end.m_pElement->GetParent()->GetLastMostChild()->Leaf();
|
|
m_end.m_pElement = pCleanEnd;
|
|
m_end.m_iPos = pCleanEnd->Leaf()->GetLen();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
XP_Bool CEditSelection::EndsAtStartOfContainer() {
|
|
return m_end.IsStartOfContainer();
|
|
}
|
|
|
|
XP_Bool CEditSelection::StartsAtEndOfContainer() {
|
|
return m_start.IsEndOfContainer();
|
|
}
|
|
|
|
XP_Bool CEditSelection::AnyLeavesSelected(){
|
|
// FALSE if insert point or container end.
|
|
return ! (IsInsertPoint() || IsContainerEnd());
|
|
}
|
|
|
|
XP_Bool CEditSelection::IsContainerEnd(){
|
|
XP_Bool result = FALSE;
|
|
if ( ! IsInsertPoint() && EndsAtStartOfContainer()
|
|
&& StartsAtEndOfContainer() ) {
|
|
CEditContainerElement* pStartContainer = m_start.m_pElement->FindContainer();
|
|
CEditContainerElement* pEndContainer = m_end.m_pElement->FindContainer();
|
|
if ( pStartContainer && pEndContainer ) {
|
|
CEditContainerElement* pNextContainer =
|
|
CEditContainerElement::Cast(pStartContainer->FindNextElement(&CEditElement::FindContainer, 0));
|
|
if ( pNextContainer == pEndContainer) {
|
|
result = TRUE;
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void CEditSelection::ExcludeLastDocumentContainerEnd() {
|
|
// Useful for cut & delete, where you don't replace the final container mark
|
|
if ( ContainsLastDocumentContainerEnd() ){
|
|
CEditLeafElement* pEnd = m_end.m_pElement;
|
|
pEnd = pEnd->PreviousLeaf();
|
|
if ( pEnd ) {
|
|
m_end.m_pElement = pEnd;
|
|
m_end.m_iPos = pEnd->Leaf()->GetLen();
|
|
if ( m_start > m_end ) {
|
|
m_start = m_end;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
XP_Bool CEditSelection::ContainsLastDocumentContainerEnd() {
|
|
// Useful for cut & delete, where you don't replace the final container mark
|
|
XP_Bool bResult = FALSE;
|
|
CEditLeafElement* pEnd = m_end.m_pElement;
|
|
if ( pEnd && pEnd->GetElementType() == eEndElement ){
|
|
bResult = TRUE;
|
|
}
|
|
return bResult;
|
|
}
|
|
|
|
CEditContainerElement* CEditSelection::GetStartContainer() {
|
|
CEditElement* pParent = m_start.m_pElement->GetParent();
|
|
if ( pParent && pParent->IsContainer() ) return pParent->Container();
|
|
else return NULL;
|
|
}
|
|
|
|
CEditContainerElement* CEditSelection::GetClosedEndContainer() {
|
|
CEditElement* pEnd = m_end.m_pElement;
|
|
if ( m_end.IsStartOfContainer() ) {
|
|
// Back up one
|
|
CEditElement* pPrev = pEnd->PreviousLeaf();
|
|
if ( pPrev ) {
|
|
pEnd = pPrev;
|
|
}
|
|
else pEnd = m_start.m_pElement;
|
|
}
|
|
CEditElement* pParent = pEnd->GetParent();
|
|
if ( pParent && pParent->IsContainer() ) return pParent->Container();
|
|
else return NULL;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
void CEditSelection::Print(IStreamOut& stream){
|
|
stream.Printf("start ");
|
|
m_start.Print(stream);
|
|
stream.Printf(" end ");
|
|
m_end.Print(stream);
|
|
stream.Printf(" FromStart %ld", (long)m_bFromStart);
|
|
}
|
|
#endif
|
|
|
|
// Persistent selections
|
|
CPersistentEditInsertPoint::CPersistentEditInsertPoint()
|
|
{
|
|
m_index = -1;
|
|
m_bStickyAfter = TRUE;
|
|
}
|
|
|
|
CPersistentEditInsertPoint::CPersistentEditInsertPoint(ElementIndex index)
|
|
: m_index(index), m_bStickyAfter(TRUE)
|
|
{}
|
|
|
|
CPersistentEditInsertPoint::CPersistentEditInsertPoint(ElementIndex index, XP_Bool bStickyAfter)
|
|
: m_index(index), m_bStickyAfter(bStickyAfter)
|
|
{
|
|
}
|
|
|
|
XP_Bool CPersistentEditInsertPoint::operator==(const CPersistentEditInsertPoint& other ) {
|
|
return m_index == other.m_index;
|
|
}
|
|
|
|
XP_Bool CPersistentEditInsertPoint::operator!=(const CPersistentEditInsertPoint& other ) {
|
|
return ! (*this == other);
|
|
}
|
|
|
|
XP_Bool CPersistentEditInsertPoint::operator<(const CPersistentEditInsertPoint& other ) {
|
|
return m_index < other.m_index;
|
|
}
|
|
|
|
XP_Bool CPersistentEditInsertPoint::operator<=(const CPersistentEditInsertPoint& other ) {
|
|
return *this < other || *this == other;
|
|
}
|
|
|
|
XP_Bool CPersistentEditInsertPoint::operator>=(const CPersistentEditInsertPoint& other ) {
|
|
return ! (*this < other);
|
|
}
|
|
|
|
XP_Bool CPersistentEditInsertPoint::operator>(const CPersistentEditInsertPoint& other ) {
|
|
return ! (*this <= other);
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
void CPersistentEditInsertPoint::Print(IStreamOut& stream) {
|
|
stream.Printf("%ld%s", (long)m_index, m_bStickyAfter ? "+" : "" );
|
|
}
|
|
#endif
|
|
|
|
void CPersistentEditInsertPoint::ComputeDifference(
|
|
CPersistentEditInsertPoint& other, CPersistentEditInsertPoint& delta){
|
|
// delta = this - other;
|
|
delta.m_index = m_index - other.m_index;
|
|
}
|
|
|
|
void CPersistentEditInsertPoint::AddRelative(
|
|
CPersistentEditInsertPoint& delta, CPersistentEditInsertPoint& result){
|
|
// result = this + delta;
|
|
result.m_index = m_index + delta.m_index;
|
|
}
|
|
|
|
XP_Bool CPersistentEditInsertPoint::IsEqualUI(
|
|
const CPersistentEditInsertPoint& other ) {
|
|
return m_index == other.m_index && m_bStickyAfter == other.m_bStickyAfter;
|
|
}
|
|
|
|
// class CPersistentEditSelection
|
|
|
|
CPersistentEditSelection::CPersistentEditSelection()
|
|
{
|
|
m_bFromStart = FALSE;
|
|
}
|
|
|
|
CPersistentEditSelection::CPersistentEditSelection(const CPersistentEditInsertPoint& start, const CPersistentEditInsertPoint& end)
|
|
: m_start(start), m_end(end)
|
|
{
|
|
m_bFromStart = FALSE;
|
|
}
|
|
|
|
ElementIndex CPersistentEditSelection::GetCount() {
|
|
return m_end.m_index - m_start.m_index;
|
|
}
|
|
|
|
XP_Bool CPersistentEditSelection::IsInsertPoint(){
|
|
return m_start == m_end;
|
|
}
|
|
|
|
XP_Bool CPersistentEditSelection::operator==(const CPersistentEditSelection& other ) {
|
|
return SelectedRangeEqual(other) && m_bFromStart == other.m_bFromStart;
|
|
}
|
|
|
|
XP_Bool CPersistentEditSelection::operator!=(const CPersistentEditSelection& other ) {
|
|
return ! ( *this == other );
|
|
}
|
|
|
|
XP_Bool CPersistentEditSelection::SelectedRangeEqual(const CPersistentEditSelection& other ) {
|
|
return m_start == other.m_start && m_end == other.m_end;
|
|
}
|
|
|
|
CPersistentEditInsertPoint* CPersistentEditSelection::GetEdge(XP_Bool bEnd){
|
|
return bEnd ? &m_end : &m_start;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
void CPersistentEditSelection::Print(IStreamOut& stream) {
|
|
stream.Printf("start ");
|
|
m_start.Print(stream);
|
|
stream.Printf(" end ");
|
|
m_end.Print(stream);
|
|
stream.Printf(" FromStart %ld", (long)m_bFromStart);
|
|
}
|
|
#endif
|
|
|
|
// Used for undo
|
|
// change this by the same way that original was changed into modified.
|
|
void CPersistentEditSelection::Map(CPersistentEditSelection& original,
|
|
CPersistentEditSelection& modified){
|
|
CPersistentEditSelection delta;
|
|
CPersistentEditSelection result;
|
|
modified.m_start.ComputeDifference(original.m_start, delta.m_start);
|
|
modified.m_end.ComputeDifference(original.m_end, delta.m_end);
|
|
m_start.AddRelative(delta.m_start, result.m_start);
|
|
m_end.AddRelative(delta.m_end, result.m_end);
|
|
*this = result;
|
|
}
|
|
|
|
//
|
|
// Call this constructor with a maximum number, a series of bits and
|
|
// BIT_ARRAY_END for example: CBitArray a(100, 1 ,4 9 ,7, BIT_ARRAY_END);
|
|
//
|
|
CBitArray::CBitArray(long n, int iFirst, ...) {
|
|
|
|
m_Bits = 0;
|
|
size = 0;
|
|
|
|
int i;
|
|
|
|
if( n ) {
|
|
SetSize(n);
|
|
}
|
|
va_list stack;
|
|
#ifdef OSF1
|
|
va_start( stack, n );
|
|
#else
|
|
va_start( stack, iFirst );
|
|
|
|
(*this)[iFirst] = 1;
|
|
#endif
|
|
|
|
while( (i = va_arg(stack,int)) != BIT_ARRAY_END ){
|
|
(*this)[i] = 1;
|
|
}
|
|
}
|
|
|
|
void CBitArray::SetSize(long n){
|
|
// On Win16 we can't have a size larger that int.
|
|
XP_ASSERT(size < (1L << (16 + 3)));
|
|
uint8 *oldBits = m_Bits;
|
|
m_Bits = new uint8[n/8+1];
|
|
memset(m_Bits,0,(int) (n/8+1));
|
|
if(oldBits){
|
|
memcpy(m_Bits,oldBits,(int) (min(n,size)/8+1));
|
|
delete oldBits;
|
|
}
|
|
size = n;
|
|
}
|
|
|
|
void CBitArray::ClearAll(){
|
|
memset(m_Bits,0, (int) (size/8+1) );
|
|
}
|
|
|
|
/* CLM: Helper to gray UI items not allowed when inside Java Script
|
|
* Note that the return value is FALSE if partial selection,
|
|
* allowing the non-script text to be changed
|
|
* Current Font Size, Color, and Character attributes will suppress
|
|
* setting other attributes, so it is OK to call these when mixed
|
|
*/
|
|
#ifdef USE_SCRIPT
|
|
|
|
XP_Bool EDT_IsJavaScript(MWContext * pMWContext)
|
|
{
|
|
if(!pMWContext) return FALSE;
|
|
XP_Bool bRetVal = FALSE;
|
|
EDT_CharacterData * pData = EDT_GetCharacterData(pMWContext);
|
|
if( pData) {
|
|
bRetVal = ( (0 != (pData->mask & TF_SERVER)) &&
|
|
(0 != (pData->values & TF_SERVER)) ) ||
|
|
( (0 != (pData->mask & TF_STYLE)) &&
|
|
(0 != (pData->values & TF_STYLE)) ) ||
|
|
( (0 != (pData->mask & TF_SCRIPT )) &&
|
|
(0 != (pData->values & TF_SCRIPT)) );
|
|
EDT_FreeCharacterData(pData);
|
|
}
|
|
return bRetVal;
|
|
}
|
|
|
|
#else
|
|
|
|
XP_Bool EDT_IsJavaScript(MWContext *)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
#endif
|
|
|
|
/* Helper to use for enabling Character properties
|
|
* (Bold, Italic, etc., but DONT use for clearing (TF_NONE)
|
|
* or setting Java Script (Server or Client)
|
|
* Tests for:
|
|
* 1. Good edit buffer and not blocked because of some action,
|
|
* 2. Caret or selection is NOT entirely within Java Script,
|
|
* 3. Caret or selection has some text or is mixed selection
|
|
* (thus FALSE if single non-text object is selected)
|
|
*/
|
|
XP_Bool EDT_CanSetCharacterAttribute(MWContext * pMWContext)
|
|
{
|
|
if( !pMWContext || pMWContext->waitingMode || EDT_IsBlocked(pMWContext) ){
|
|
return FALSE;
|
|
}
|
|
|
|
ED_ElementType type = EDT_GetCurrentElementType(pMWContext);
|
|
return ( (type == ED_ELEMENT_TEXT || type == ED_ELEMENT_SELECTION ||
|
|
type >= ED_ELEMENT_TABLE) && !EDT_IsJavaScript(pMWContext) );
|
|
}
|
|
|
|
// TODO: Should probably go through all containers in a selection
|
|
XP_Bool EDT_IsPreformat(MWContext *pMWContext)
|
|
{
|
|
GET_EDIT_BUF_OR_RETURN(pMWContext, pEditBuffer) FALSE;
|
|
CEditInsertPoint ip;
|
|
pEditBuffer->GetInsertPoint(ip);
|
|
if( ip.m_pElement )
|
|
{
|
|
CEditContainerElement *pContainer = ip.m_pElement->FindContainer();
|
|
if( pContainer )
|
|
return pContainer->GetType() == P_PREFORMAT;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
// XP string defines for FontFaces
|
|
|
|
extern "C" {
|
|
#include "xpgetstr.h"
|
|
#define WANT_ENUM_STRING_IDS
|
|
#include "allxpstr.h"
|
|
#undef WANT_ENUM_STRING_IDS
|
|
}
|
|
|
|
int iFontFaces[] = {
|
|
XP_NSFONT_DEFAULT,
|
|
XP_NSFONT_FIXED,
|
|
XP_NSFONT_ARIAL,
|
|
XP_NSFONT_TIMES,
|
|
XP_NSFONT_COURIER
|
|
};
|
|
|
|
// IDs for the list of fonts written to the tags
|
|
// MUST CORRESPOND IN NUMBER AND LOCATION TO iFontFaces!
|
|
int iFontFaceTags[] = {
|
|
0, 0,
|
|
XP_NSFONTLIST_ARIAL,
|
|
XP_NSFONTLIST_TIMES,
|
|
XP_NSFONTLIST_COURIER
|
|
};
|
|
#define ED_NSFONT_MAX ((sizeof(iFontFaceTags))/sizeof(int))
|
|
|
|
// Cache the lists of font faces that we show to user
|
|
// and list of tags (no shown to user)
|
|
static char * pFontFaces = NULL;
|
|
static char * pFontFaceTags = NULL;
|
|
|
|
char *EDT_GetFontFaces()
|
|
{
|
|
if( pFontFaces ){
|
|
return pFontFaces;
|
|
}
|
|
intn iSize = 512;
|
|
int iCur = 0;
|
|
pFontFaces = (char*)XP_ALLOC( iSize );
|
|
|
|
if( !pFontFaces )
|
|
return NULL;
|
|
|
|
pFontFaces[0] = 0;
|
|
pFontFaces[1] = 0;
|
|
|
|
for( int i = 0; i < ED_NSFONT_MAX; i++ ){
|
|
char *pFont = XP_GetString(iFontFaces[i]);
|
|
|
|
if( pFont && *pFont ){
|
|
int iLen = XP_STRLEN( pFont );
|
|
if( iCur + iLen + 2 > iSize ){
|
|
iSize += 512;
|
|
pFontFaces = (char*)XP_REALLOC( pFontFaces, iSize );
|
|
|
|
if( ! pFontFaces ){
|
|
return NULL;
|
|
}
|
|
}
|
|
XP_STRCPY( &pFontFaces[iCur], pFont );
|
|
iCur += iLen+1;
|
|
}
|
|
}
|
|
// Append extra '\0' at end
|
|
pFontFaces[iCur] = 0;
|
|
|
|
return pFontFaces;
|
|
}
|
|
|
|
char *EDT_GetFontFaceTags()
|
|
{
|
|
if( pFontFaceTags ){
|
|
return pFontFaceTags;
|
|
}
|
|
intn iSize = 2024;
|
|
pFontFaceTags = (char*)XP_ALLOC( iSize );
|
|
|
|
if( !pFontFaceTags )
|
|
return NULL;
|
|
|
|
// First 2 strings are ignored - map to default proportional and fixed
|
|
XP_STRCPY(pFontFaceTags, " ");
|
|
XP_STRCPY(&pFontFaceTags[2], " ");
|
|
int iCur = 4; // Start at 4th character - after above strings
|
|
pFontFaceTags[5] = 0; // Add extra 0 in case there are no more strings
|
|
|
|
for( int i = 2; i < ED_NSFONT_MAX; i++ ){
|
|
char *pFont = XP_GetString(iFontFaceTags[i]);
|
|
|
|
if( pFont && *pFont ){
|
|
int iLen = XP_STRLEN( pFont );
|
|
if( iCur + iLen + 2 > iSize ){
|
|
iSize += 2024;
|
|
pFontFaceTags = (char*)XP_REALLOC( pFontFaceTags, iSize );
|
|
|
|
if( ! pFontFaceTags ){
|
|
return NULL;
|
|
}
|
|
}
|
|
XP_STRCPY( &pFontFaceTags[iCur], pFont );
|
|
iCur += iLen+1;
|
|
}
|
|
}
|
|
// Append extra '\0' at end
|
|
pFontFaceTags[iCur] = 0;
|
|
|
|
return pFontFaceTags;
|
|
}
|
|
|
|
#define EDT_CLEAR_BIT(x,tf) x &= ~tf
|
|
#define EDT_SET_BIT(x,tf) x |= tf
|
|
|
|
void EDT_SetFontFace(MWContext * pMWContext, EDT_CharacterData * pCharacterData,
|
|
int iFontIndex, char * pFontFace )
|
|
{
|
|
// Must have one or the other
|
|
if( pMWContext == NULL && pCharacterData == NULL )
|
|
return;
|
|
#if defined(XP_WIN) || defined(XP_OS2)
|
|
// Windows may give us this if click off of the font combobox
|
|
if( (long)pFontFace == -1L )
|
|
return;
|
|
#endif
|
|
|
|
EDT_CharacterData * pData;
|
|
XP_Bool bCanCopy;
|
|
XP_Bool bHaveData;
|
|
|
|
// Only 0 and 1 have any meaning now that XP groups are "automatic"
|
|
if( iFontIndex < 0 || iFontIndex > 1 ){
|
|
iFontIndex = -1;
|
|
}
|
|
if( pCharacterData ){
|
|
// Use supplied structure
|
|
pData = pCharacterData;
|
|
bHaveData = TRUE;
|
|
// If pointer to existing data is same as new, don't copy it
|
|
bCanCopy = pData->pFontFace != pFontFace;
|
|
// We will respect the TF_FONT_FACE bit in existing data,
|
|
// but we are also interested in FIXED if we are doing
|
|
if( pData->mask & TF_FONT_FACE ){
|
|
EDT_SET_BIT(pData->mask, TF_FIXED);
|
|
}
|
|
} else {
|
|
// No data supplied, get from current selection or at caret
|
|
bHaveData = FALSE;
|
|
pData = EDT_GetCharacterData(pMWContext);
|
|
if( !pData )
|
|
return;
|
|
bCanCopy = TRUE;
|
|
// Set the bit to tell that we are interested in font face
|
|
// and fixed width
|
|
pData->mask = TF_FONT_FACE | TF_FIXED;
|
|
}
|
|
|
|
// Remove any existing font face data if not same as new data
|
|
if( pData->pFontFace && bCanCopy){
|
|
XP_FREEIF(pData->pFontFace);
|
|
}
|
|
|
|
// This will force using whatever user string was supplied
|
|
if( pFontFace ){
|
|
// So it will be skipped in test below
|
|
iFontIndex = -1;
|
|
if( bCanCopy) {
|
|
// Translate into XP font "group" if face is found in any group
|
|
char *pTranslated = EDT_TranslateToXPFontFace(pFontFace);
|
|
if( *pTranslated == '_' ){
|
|
// First character may have signal for separator - skip over it
|
|
pTranslated++;
|
|
}
|
|
// Detect the 1st two items -- the Default Variable and Fixed Width fonts
|
|
if( 0 == XP_STRCMP(pTranslated, XP_GetString(iFontFaces[0])) ){
|
|
iFontIndex = 0;
|
|
} else if( 0 == XP_STRCMP(pTranslated, XP_GetString(iFontFaces[1])) ){
|
|
iFontIndex = 1;
|
|
} else {
|
|
// Copy the font face - either same as pFontFace or the entire group if translated
|
|
pData->pFontFace = XP_STRDUP(pTranslated);
|
|
// Set font face tag - note that it is set
|
|
EDT_SET_BIT(pData->values, TF_FONT_FACE);
|
|
// We can't be fixed width if here
|
|
EDT_CLEAR_BIT(pData->values, TF_FIXED);
|
|
}
|
|
}
|
|
}
|
|
|
|
if( iFontIndex == 0 || iFontIndex == 1 ){
|
|
// No font face tag will be set
|
|
EDT_CLEAR_BIT(pData->values, TF_FONT_FACE);
|
|
|
|
if( iFontIndex == 1){
|
|
// Fixed Width
|
|
EDT_SET_BIT(pData->values, TF_FIXED);
|
|
} else {
|
|
// Default proportional font
|
|
EDT_CLEAR_BIT(pData->values, TF_FIXED);
|
|
}
|
|
}
|
|
|
|
if( pMWContext ){
|
|
EDT_SetCharacterData(pMWContext, pData);
|
|
}
|
|
// Free only the data we obtained
|
|
if( !bHaveData ){
|
|
EDT_FreeCharacterData(pData);
|
|
}
|
|
}
|
|
|
|
char * EDT_TranslateToXPFontFace( char * pFontFace )
|
|
{
|
|
char * pFontFaceTags = EDT_GetFontFaceTags();
|
|
if( pFontFaceTags ){
|
|
char * pFace = pFontFace;
|
|
// Skip over initial separator signal
|
|
if( *pFace == '_' ){
|
|
pFace++;
|
|
}
|
|
// Skip over first two strings (each contains " ")
|
|
char * pTag = pFontFaceTags+4;
|
|
int iLen;
|
|
int i = 0;
|
|
while( (iLen = XP_STRLEN(pTag)) > 0 ) {
|
|
char * pComma;
|
|
char * pTagFace = pTag;
|
|
// Scan for comma separating the next font in group
|
|
while( (pComma = XP_STRCHR(pTagFace,',')) != 0 ){
|
|
*pComma = '\0';
|
|
int iFaceLen = XP_STRLEN(pTagFace);
|
|
XP_Bool bFound = (0 == XP_STRCMP(pTagFace, pFace));
|
|
*pComma = ',';
|
|
if( bFound ){
|
|
// We found the font in the list - we're done
|
|
return pTag;
|
|
}
|
|
pTagFace += iFaceLen+1;
|
|
}
|
|
// Check one more time - either no commas found or
|
|
// pFace is on the last font in the group
|
|
if( 0 == XP_STRCMP(pTagFace, pFace) ){
|
|
return pTag;
|
|
}
|
|
|
|
pTag += iLen+1;
|
|
i++;
|
|
}
|
|
}
|
|
// Return exactly what we were passed if not found in font groups
|
|
return pFontFace;
|
|
}
|
|
|
|
|
|
// Cache the list of colors that we show to user
|
|
//static char * pFontColors = NULL;
|
|
|
|
// Use this array instead of pFontColor list
|
|
// This replaces the XP_Strings colors
|
|
uint8 NSColors[MAX_NS_COLORS][3] =
|
|
{ { 255,255,255 },
|
|
{ 204,204,204 },
|
|
{ 192,192,192 },
|
|
{ 153,153,153 },
|
|
{ 102,102,102 },
|
|
{ 51,51,51 },
|
|
{ 0,0,0 },
|
|
{ 255,204,204 },
|
|
{ 255,102,102 },
|
|
{ 255,0,0 },
|
|
{ 204,0,0 },
|
|
{ 153,0,0 },
|
|
{ 102,0,0 },
|
|
{ 51,0,0 },
|
|
{ 255,204,153 },
|
|
{ 255,204,51 },
|
|
{ 255,153,0 },
|
|
{ 255,102,0 },
|
|
{ 204,102,0 },
|
|
{ 153,51,0 },
|
|
{ 102,51,0 },
|
|
{ 255,255,153 },
|
|
{ 255,255,102 },
|
|
{ 255,204,102 },
|
|
{ 255,204,51 },
|
|
{ 204,153,51 },
|
|
{ 153,102,51 },
|
|
{ 102,51,51 },
|
|
{ 255,255,204 },
|
|
{ 255,255,153 },
|
|
{ 255,255,0 },
|
|
{ 255,204,0 },
|
|
{ 153,153,0 },
|
|
{ 102,102,0 },
|
|
{ 51,51,0 },
|
|
{ 153,255,153 },
|
|
{ 102,255,153 },
|
|
{ 51,255,51 },
|
|
{ 0,204,0 },
|
|
{ 0,153,0 },
|
|
{ 0,102,0 },
|
|
{ 0,51,0 },
|
|
{ 153,255,255 },
|
|
{ 51,255,255 },
|
|
{ 102,204,204 },
|
|
{ 0,204,204 },
|
|
{ 51,153,153 },
|
|
{ 51,102,102 },
|
|
{ 0,51,51 },
|
|
{ 204,255,255 },
|
|
{ 102,255,255 },
|
|
{ 51,204,255 },
|
|
{ 51,102,255 },
|
|
{ 51,51,255 },
|
|
{ 0,0,153 },
|
|
{ 0,0,102 },
|
|
{ 204,204,255 },
|
|
{ 153,153,204 },
|
|
{ 102,102,204 },
|
|
{ 102,51,255 },
|
|
{ 102,0,204 },
|
|
{ 51,51,153 },
|
|
{ 51,0,153 },
|
|
{ 255,204,255 },
|
|
{ 255,153,255 },
|
|
{ 204,102,204 },
|
|
{ 204,51,204 },
|
|
{ 153,51,102 },
|
|
{ 102,51,102 },
|
|
{ 51,0,51 } };
|
|
|
|
void EDT_GetNSColor(intn iIndex, LO_Color * pLoColor)
|
|
{
|
|
if( pLoColor && iIndex < MAX_NS_COLORS )
|
|
{
|
|
pLoColor->red = NSColors[iIndex][0];
|
|
pLoColor->green = NSColors[iIndex][1];
|
|
pLoColor->blue = NSColors[iIndex][2];
|
|
}
|
|
}
|
|
|
|
/* Return list of font colors in format: "r,g,b,ColorName" where colors for r,g,b
|
|
* are decimal strings in range 0-255
|
|
*/
|
|
#if defined(XP_UNIX)
|
|
char *EDT_GetFontColors()
|
|
{
|
|
return NULL;
|
|
|
|
#if 0
|
|
if( pFontColors )
|
|
return pFontColors;
|
|
|
|
intn iSize = 512;
|
|
int iCur = 0;
|
|
pFontColors = (char*)XP_ALLOC( iSize );
|
|
|
|
if( !pFontColors )
|
|
return NULL;
|
|
|
|
pFontColors[0] = 0;
|
|
pFontColors[1] = 0;
|
|
|
|
int nIDColor = XP_NSCOLOR_BASE;
|
|
while( nIDColor < XP_NSCOLOR_END){
|
|
char *pColor = XP_GetString(nIDColor);
|
|
|
|
if( pColor && *pColor ){
|
|
int iLen = XP_STRLEN( pColor );
|
|
if( iCur + iLen + 2 > iSize ){
|
|
iSize += 512;
|
|
pFontColors = (char*)XP_REALLOC( pFontColors, iSize );
|
|
|
|
if( ! pFontColors ){
|
|
return NULL;
|
|
}
|
|
}
|
|
XP_STRCPY( &pFontColors[iCur], pColor );
|
|
iCur += iLen+1;
|
|
}
|
|
// Next color ID
|
|
nIDColor++;
|
|
}
|
|
// Append extra '\0' at end
|
|
pFontColors[iCur] = 0;
|
|
return pFontColors;
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
char * EDT_ParseColorString(LO_Color * pLoColor, char * pColorString)
|
|
{
|
|
XP_ASSERT(pLoColor);
|
|
XP_ASSERT(pColorString);
|
|
|
|
char *pComma = XP_STRCHR(pColorString, ',');
|
|
if(!pComma) return pColorString;
|
|
|
|
*pComma = '\0';
|
|
pLoColor->red = (uint8)atoi(pColorString);
|
|
*pComma = ',';
|
|
char *pColor = pComma+1;
|
|
|
|
pComma = XP_STRCHR(pColor, ',');
|
|
if(!pComma) return pColor;
|
|
*pComma = '\0';
|
|
pLoColor->green = (uint8)atoi(pColor);
|
|
*pComma = ',';
|
|
pColor = pComma+1;
|
|
|
|
pComma = XP_STRCHR(pColor, ',');
|
|
// If no comma found, assume color string
|
|
// doesn't have HTML-HEX format and
|
|
// all thats left is the blue value
|
|
if(pComma)
|
|
*pComma = '\0';
|
|
pLoColor->blue = (uint8)atoi(pColor);
|
|
if(pComma)
|
|
{
|
|
*pComma = ',';
|
|
pColor = pComma+1;
|
|
// Return rest of string, assumned to be Hex representation
|
|
return pColor;
|
|
}
|
|
|
|
// Supply a hex translation
|
|
static char pHexColor[16];
|
|
XP_SPRINTF(pHexColor, "#%02X%02X%02X", pLoColor->red, pLoColor->green, pLoColor->blue);
|
|
return pHexColor;
|
|
}
|
|
|
|
/* Scan our list of colors and return index of matching color
|
|
* or -1 if no match found. We NEVER return 0 (default color)
|
|
*/
|
|
int EDT_GetMatchingFontColorIndex(LO_Color * pLOColor)
|
|
{
|
|
for( intn i = 0; i < MAX_NS_COLORS; i++ )
|
|
{
|
|
if( NSColors[i][0] == pLOColor->red &&
|
|
NSColors[i][1] == pLOColor->green &&
|
|
NSColors[i][2] == pLOColor->blue ){
|
|
return (i+1);
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
|
|
#ifdef DEBUG
|
|
|
|
// Automated test routine hook
|
|
|
|
const char* CEditTestManager::m_kTrigger = "$$$Test";
|
|
|
|
CEditTestManager::CEditTestManager(CEditBuffer* pBuffer)
|
|
: m_pBuffer(pBuffer),
|
|
m_bTesting(FALSE),
|
|
m_iTriggerIndex(0),
|
|
m_pSaveBuffer(0),
|
|
m_pTempFileURL(0)
|
|
{
|
|
#ifdef XP_WIN32
|
|
// 5/28/98 ??? This is crashing -- Why???
|
|
// _CrtMemCheckpoint( &m_state ); // In theorey, avoid measuring the data before we were created.
|
|
#endif
|
|
|
|
PowerOnTest();
|
|
}
|
|
|
|
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;
|
|
case 'g':
|
|
GetDocTempDir();
|
|
break;
|
|
case '!':
|
|
PasteINTL();
|
|
break;
|
|
case 'w':
|
|
PasteINTLText();
|
|
break;
|
|
|
|
case 'x':
|
|
CopyDocumentToBuffer();
|
|
break;
|
|
case 'y':
|
|
CopyBufferToDocument();
|
|
break;
|
|
#ifdef EDITOR_JAVA
|
|
case 'j':
|
|
DumpPlugins();
|
|
break;
|
|
case 'k':
|
|
PerformFirstPlugin();
|
|
break;
|
|
case 'l':
|
|
PerformPluginByName();
|
|
break;
|
|
case 'm':
|
|
PerformFirstEncoder();
|
|
break;
|
|
case 'n':
|
|
PerformPreOpen();
|
|
break;
|
|
#endif
|
|
case 's':
|
|
SaveToTempFile();
|
|
break;
|
|
case 't':
|
|
RemoveTempFile();
|
|
break;
|
|
case 'v':
|
|
TestHTMLPaste();
|
|
break;
|
|
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 '8':
|
|
bTestResult = TextFETest();
|
|
break;
|
|
case '?':
|
|
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"
|
|
"g - get doc temp directory. \n"
|
|
));
|
|
#ifdef EDITOR_JAVA
|
|
XP_TRACE(("j - show Composer Plugin and Image Encoder information.\n"
|
|
"k - run the first composer plugin.\n"
|
|
"l - run a composer plugin by name.\n"
|
|
"m - run the first image encoder.\n"
|
|
"n - test the EDT_PreOpen call.\n"
|
|
));
|
|
#endif
|
|
XP_TRACE(("q - test HTML Paste Quoted.\n"
|
|
));
|
|
XP_TRACE(("s - save doc to temporary file.\n"
|
|
));
|
|
XP_TRACE(("t - remove saved temporary file.\n"
|
|
));
|
|
XP_TRACE(("x - save document state.\n"
|
|
"y - restore document state.\n"
|
|
));
|
|
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"
|
|
"8 - write buffer to out.txt as plain text.\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::PowerOnTest() {
|
|
// Test some things that have to be tested by code. This method is
|
|
// called whenever the editor starts up in debug mode. Don't take too long.
|
|
|
|
// Test that we know about all the existing tags.
|
|
for(int i = 0; i < P_MAX; i++ ){
|
|
EDT_TagString(i);
|
|
}
|
|
}
|
|
|
|
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);
|
|
TraceLargeString(buffer.GetText());
|
|
}
|
|
|
|
void TraceLargeString(char* b){
|
|
// have to dump one line at a time. XP_TRACE has a 512 char limit on Windows.
|
|
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;
|
|
}
|
|
}
|
|
|
|
void CEditTestManager::PasteINTL(){
|
|
unsigned char testStringJIS[] = {'J','I','S',' ','[',27,'$','B',30,21,']',' ',0};
|
|
unsigned char testStringEUC1[] = {'E','U','C',' ','[', 0xb0, 0};
|
|
unsigned char tesrStringEUC2[] = {0xa1,']',' ',0};
|
|
unsigned char testStringSJIS[] = {'S','J','I','S',' ','[',0x88,0x9f,']',0};
|
|
|
|
MWContext* pContext = m_pBuffer->m_pContext;
|
|
EDT_PasteQuoteBegin(pContext, TRUE);
|
|
EDT_PasteQuoteINTL(pContext, (char*) testStringJIS, CS_JIS);
|
|
EDT_PasteQuoteINTL(pContext, (char*) testStringEUC1, CS_EUCJP);
|
|
EDT_PasteQuoteINTL(pContext, (char*) tesrStringEUC2, CS_EUCJP);
|
|
EDT_PasteQuoteINTL(pContext, (char*) testStringSJIS, CS_SJIS);
|
|
EDT_PasteQuoteEnd(pContext);
|
|
}
|
|
|
|
void CEditTestManager::PasteINTLText(){
|
|
unsigned char testStringJIS[] = {'J','I','S',' ','[',27,'$','B',30,21,']',' ',0};
|
|
unsigned char testStringEUC[] = {'E','U','C',' ','[', 0xb0, 0xa1,']',' ',0};
|
|
unsigned char testStringSJIS[] = {'S','J','I','S',' ','[',0x88,0x9f,']',0};
|
|
|
|
MWContext* pContext = m_pBuffer->m_pContext;
|
|
EDT_PasteQuoteBegin(pContext, FALSE);
|
|
EDT_PasteQuoteINTL(pContext, (char*) testStringJIS, CS_JIS);
|
|
EDT_PasteQuoteINTL(pContext, (char*) testStringEUC, CS_EUCJP);
|
|
EDT_PasteQuoteINTL(pContext, (char*) testStringSJIS, CS_SJIS);
|
|
EDT_PasteQuoteEnd(pContext);
|
|
}
|
|
|
|
void CEditTestManager::CopyDocumentToBuffer(){
|
|
if ( m_pSaveBuffer){
|
|
delete[] m_pSaveBuffer;
|
|
m_pSaveBuffer = 0;
|
|
}
|
|
m_pBuffer->WriteToBuffer(&m_pSaveBuffer, TRUE);
|
|
XP_TRACE(("Buffer size: %d bytes", XP_STRLEN(m_pSaveBuffer)));
|
|
}
|
|
|
|
void CEditTestManager::CopyBufferToDocument(){
|
|
if ( m_pSaveBuffer == NULL ) {
|
|
XP_TRACE(("The save buffer is empty."));
|
|
}
|
|
else {
|
|
m_pBuffer->ReadFromBuffer(m_pSaveBuffer);
|
|
}
|
|
}
|
|
|
|
static const char* kChunkName[EDT_NA_COUNT] = {
|
|
"character", "word", "line edge", "document", "updown"
|
|
};
|
|
|
|
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
|
|
}
|
|
};
|
|
|
|
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
|
|
}
|
|
}
|
|
};
|
|
|
|
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;
|
|
}
|
|
|
|
static void TextFETestDone(PrintSetup *setup) {
|
|
XP_FileClose(setup->out);
|
|
XP_TRACE(("Finished TextFETest."));
|
|
}
|
|
|
|
|
|
XP_Bool CEditTestManager::TextFETest() {
|
|
PrintSetup setup;
|
|
char *_srcName = 0;
|
|
char *srcName = 0;
|
|
char *srcURL = 0;
|
|
char *destName = 0;
|
|
XP_File destFile = 0;
|
|
URL_Struct *srcURLS = 0;
|
|
XP_Bool ret = FALSE;
|
|
ED_FileError result;
|
|
|
|
//// Write the edit buffer to a temporary file as HTML.
|
|
// Get the filename of a temporary file.
|
|
#ifdef XP_MAC
|
|
_srcName = WH_TempName(xpFileToPost,"ns");
|
|
#else
|
|
_srcName = WH_TempName(xpTemporary,"ns");
|
|
#endif
|
|
if (!_srcName)
|
|
goto CLEAN_UP;
|
|
|
|
// Is this necessary for some Mac magic to work?
|
|
srcName = WH_FilePlatformName(_srcName);
|
|
if (!srcName)
|
|
goto CLEAN_UP;
|
|
|
|
srcURL = XP_PlatformFileToURL(srcName);
|
|
if (!srcURL)
|
|
goto CLEAN_UP;
|
|
|
|
result = EDT_SaveFile(m_pBuffer->m_pContext,
|
|
srcURL,srcURL,
|
|
FALSE,FALSE,FALSE);
|
|
if (result != ED_ERROR_NONE)
|
|
goto CLEAN_UP;
|
|
XP_TRACE(("Created %s",srcURL));
|
|
|
|
|
|
// The converted plain text will go to "out.txt".
|
|
destName = XP_STRDUP("out.txt");
|
|
if (!destName)
|
|
goto CLEAN_UP;
|
|
|
|
destFile = XP_FileOpen(destName,xpFileToPost,XP_FILE_WRITE_BIN);
|
|
if (!destFile)
|
|
goto CLEAN_UP;
|
|
|
|
|
|
//// Call XL_Translate text to convert src to dest, TextFETestDone will be called on
|
|
// completion.
|
|
srcURLS = NET_CreateURLStruct(srcURL,NET_DONT_RELOAD);
|
|
if (!srcURLS) {
|
|
XP_FileClose(destFile);
|
|
goto CLEAN_UP;
|
|
}
|
|
|
|
XL_InitializeTextSetup(&setup);
|
|
setup.url = srcURLS;
|
|
setup.completion = TextFETestDone;
|
|
setup.out = destFile;
|
|
|
|
// Seems that passed in MWcontext is only used to copy some info when making the
|
|
// temporary text context.
|
|
XL_TranslateText(m_pBuffer->m_pContext,srcURLS,&setup);
|
|
ret = TRUE;
|
|
|
|
|
|
// Nasty, a goto.
|
|
CLEAN_UP:
|
|
if (_srcName)
|
|
XP_FREE(_srcName);
|
|
if (srcName)
|
|
XP_FREE(srcName);
|
|
if (srcURL)
|
|
XP_FREE(srcURL);
|
|
if (destName)
|
|
XP_FREE(destName);
|
|
|
|
return ret;
|
|
}
|
|
|
|
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() && UArrowTest(FALSE) && UArrowTest(TRUE)
|
|
&& DArrowTest(FALSE) && 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) || LArrowTest(TRUE) || RArrowTest(FALSE)
|
|
|| RArrowTest(TRUE) || UArrowTest(TRUE) || DArrowTest(TRUE);
|
|
#if 0
|
|
// Too many false positives -- need to know about up/down at first/last line
|
|
result |= UArrowTest(FALSE);
|
|
result |= DArrowTest(FALSE);
|
|
#endif
|
|
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(){
|
|
for ( int select = 0; select < 2; select++ ) {
|
|
for ( int chunk = 0; chunk < EDT_NA_COUNT; chunk++ ) {
|
|
for ( int direction = 0; direction < 2; direction++ ) {
|
|
if ( !NavChunkCrashTest(select, chunk, direction) )
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
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() {
|
|
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
|
|
|
|
void CEditTestManager::DumpPlugins(){
|
|
XP_TRACE(("Dump Plugin database."));
|
|
int32 numCategories = EDT_NumberOfPluginCategories();
|
|
XP_TRACE(("EDT_NumberOfPluginCategories: %d", numCategories));
|
|
for(int category = 0; category < numCategories; category++ ) {
|
|
char* pcsCategoryName = EDT_GetPluginCategoryName(category);
|
|
XP_TRACE(("EDT_GetPluginCategoryName(%d) = \"%s\"", category, pcsCategoryName));
|
|
int plugins = EDT_NumberOfPlugins(category);
|
|
XP_TRACE(("EDT_NumberOfPlugins(%d) = %d", category, plugins));
|
|
for(int plugin = 0; plugin < plugins; plugin++){
|
|
char* pcsPluginName = EDT_GetPluginName(category, plugin);
|
|
XP_TRACE(("EDT_GetPluginName(%d, %d) = \"%s\"", category, plugin, pcsPluginName));
|
|
char* pcsPluginMenuHelp = EDT_GetPluginMenuHelp(category, plugin);
|
|
XP_TRACE(("EDT_GetPluginMenuHelp(%d, %d) = \"%s\"", category, plugin, pcsPluginMenuHelp));
|
|
}
|
|
}
|
|
XP_TRACE(("Dump Image Encoder database."));
|
|
int encoders = EDT_NumberOfEncoders();
|
|
XP_TRACE(("EDT_NumberOfEncoders() = %d", encoders));
|
|
for(int encoder = 0; encoder < encoders; encoder++){
|
|
char* pcsEncoderName = EDT_GetEncoderName(encoder);
|
|
XP_TRACE(("EDT_GetEncoderName(%d) = \"%s\"", encoder, pcsEncoderName));
|
|
|
|
char* pcsEncoderFileExtension = EDT_GetEncoderFileExtension(encoder);
|
|
XP_TRACE(("EDT_GetEncoderFileExtension(%d) = \"%s\"", encoder, pcsEncoderFileExtension));
|
|
|
|
char* pcsEncoderFileType = EDT_GetEncoderFileType(encoder);
|
|
XP_TRACE(("EDT_GetEncoderFileType(%d) = \"%s\"", encoder, pcsEncoderFileType));
|
|
|
|
XP_TRACE(("EDT_GetEncoderNeedsQuantizedSource(%d) = \"%d\"", encoder, EDT_GetEncoderNeedsQuantizedSource(encoder)));
|
|
|
|
char* pcsEncoderMenuHelp = EDT_GetEncoderMenuHelp(encoder);
|
|
XP_TRACE(("EDT_GetEncoderMenuHelp(%d) = \"%s\"", encoder, pcsEncoderMenuHelp));
|
|
|
|
}
|
|
}
|
|
|
|
#ifndef XP_OS2
|
|
static
|
|
#else
|
|
extern "OPTLINK"
|
|
#endif
|
|
void edt_TestImageEncoderCallbackFn(EDT_ImageEncoderStatus status, void* hook) {
|
|
XP_TRACE(("edt_TestImageEncoderCallbackFn Status: %d hook: %08x", status, hook));
|
|
}
|
|
|
|
void CEditTestManager::PerformFirstPlugin(){
|
|
if ( EDT_NumberOfPluginCategories() <= 0 ) {
|
|
XP_TRACE(("EDT_NumberOfPluginCategories: %d", EDT_NumberOfPluginCategories()));
|
|
return;
|
|
}
|
|
if ( EDT_NumberOfPlugins(0) <= 0 ) {
|
|
XP_TRACE(("EDT_NumberOfPlugins(0): %d", EDT_NumberOfPlugins(0)));
|
|
return;
|
|
}
|
|
XP_Bool bItem = EDT_PerformPlugin(m_pBuffer->m_pContext, 0, 0, edt_TestImageEncoderCallbackFn, (void*) 0x1234);
|
|
XP_TRACE(("EDT_PerformPlugin: returned %d\n", bItem));
|
|
}
|
|
|
|
void CEditTestManager::PerformPluginByName(){
|
|
XP_TRACE(("Calling dummyPlugin.does.not.exist"));
|
|
XP_Bool bResult = EDT_PerformPluginByClassName(m_pBuffer->m_pContext, "dummyPlugin.does.not.exist",
|
|
edt_TestImageEncoderCallbackFn, (void*) 12);
|
|
XP_TRACE(("EDT_PerformPluginByClassName returned %d\n", bResult));
|
|
XP_TRACE(("Calling netscape.test.plugin.composer.EditRaw"));
|
|
bResult = EDT_PerformPluginByClassName(m_pBuffer->m_pContext, "netscape.test.plugin.composer.EditRaw",
|
|
edt_TestImageEncoderCallbackFn, (void*) 13);
|
|
XP_TRACE(("EDT_PerformPluginByClassName returned %d\n", bResult));
|
|
}
|
|
|
|
void CEditTestManager::PerformFirstEncoder(){
|
|
if ( EDT_NumberOfEncoders() <= 0 ) {
|
|
XP_TRACE(("EDT_NumberOfEncoders: %d", EDT_NumberOfEncoders()));
|
|
return;
|
|
}
|
|
char** ppPixels = (char**) XP_ALLOC(sizeof(char*));
|
|
char* pPixels = (char*) XP_ALLOC(3);
|
|
ppPixels[0] = pPixels;
|
|
pPixels[0] = 1;
|
|
pPixels[1] = 2;
|
|
pPixels[2] = 3;
|
|
XP_TRACE(("EDT_StartEncoder: starting\n"));
|
|
XP_Bool bItem = EDT_StartEncoder(m_pBuffer->m_pContext, 0, 1, 1, ppPixels,
|
|
"C:\\junk.txt", edt_TestImageEncoderCallbackFn, (void*) 0xfeedface);
|
|
XP_TRACE(("EDT_StartEncoder: returned %d\n", bItem));
|
|
XP_FREE(pPixels);
|
|
XP_FREE(ppPixels);
|
|
}
|
|
|
|
#ifndef XP_OS2
|
|
static
|
|
#else
|
|
extern "OPTLINK"
|
|
#endif
|
|
void edt_TestPreOpenCallbackFn(XP_Bool bUserCancled, char* pURL, void* hook) {
|
|
XP_TRACE(("edt_TestPreOpenCallbackFn bUserCancled: %s url: %s hook: %08x", bUserCancled ? "TRUE" : "FALSE",
|
|
pURL ? pURL : "null", hook));
|
|
}
|
|
|
|
void CEditTestManager::PerformPreOpen(){
|
|
XP_TRACE(("Calling preOpen."));
|
|
EDT_PreOpen(m_pBuffer->m_pContext,"http://PerformPreOpen",
|
|
edt_TestPreOpenCallbackFn, (void*) 13);
|
|
}
|
|
|
|
#endif // EDITOR_JAVA
|
|
|
|
void CEditTestManager::TestHTMLPaste(){
|
|
EDT_PasteQuoteBegin(m_pBuffer->m_pContext, TRUE);
|
|
EDT_PasteQuote(m_pBuffer->m_pContext, "<b>This is bold.</b>");
|
|
EDT_PasteQuote(m_pBuffer->m_pContext, "<i>This is italic.</i>");
|
|
EDT_PasteQuoteEnd(m_pBuffer->m_pContext);
|
|
}
|
|
|
|
void CEditTestManager::SaveToTempFile() {
|
|
EDT_SaveToTempFile(m_pBuffer->m_pContext,(EDT_SaveToTempCallbackFn) SaveToTempFileCB,(void *)m_pBuffer->m_pContext);
|
|
}
|
|
|
|
void CEditTestManager::SaveToTempFileCB(char *pFileURL,void *hook) {
|
|
XP_ASSERT(hook);
|
|
MWContext *pContext = (MWContext *)hook;
|
|
GET_EDIT_BUF_OR_RETURN(pContext, pEditBuffer);
|
|
|
|
CEditTestManager *pManager = pEditBuffer->m_pTestManager;
|
|
if (!pManager) {
|
|
XP_ASSERT(0);
|
|
return;
|
|
}
|
|
XP_FREEIF(pManager->m_pTempFileURL);
|
|
if (pFileURL) {
|
|
pManager->m_pTempFileURL = XP_STRDUP(pFileURL);
|
|
XP_TRACE(("Wrote to temp file %s",pFileURL));
|
|
}
|
|
else
|
|
{
|
|
XP_TRACE(("Failed to write temp file."));
|
|
}
|
|
}
|
|
|
|
void CEditTestManager::RemoveTempFile() {
|
|
if (m_pTempFileURL) {
|
|
XP_TRACE(("Removing file %s",m_pTempFileURL));
|
|
EDT_RemoveTempFile(m_pBuffer->m_pContext,m_pTempFileURL);
|
|
XP_FREEIF(m_pTempFileURL);
|
|
}
|
|
else {
|
|
XP_TRACE(("No temp file has been saved"));
|
|
}
|
|
}
|
|
|
|
void CEditTestManager::GetDocTempDir() {
|
|
CEditCommandLog *pLog = CGlobalHistoryGroup::GetGlobalHistoryGroup()->GetLog(m_pBuffer);
|
|
if (!pLog) {
|
|
return;
|
|
}
|
|
|
|
char *pFile = pLog->GetDocTempDir();
|
|
if (pFile) {
|
|
XP_TRACE(("DocTempDir is %s",pFile));
|
|
XP_FREE(pFile);
|
|
}
|
|
else {
|
|
XP_TRACE(("No document temp dir was created."));
|
|
}
|
|
}
|
|
#endif // DEBUG
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// CParseState / CPrintState
|
|
//-----------------------------------------------------------------------------
|
|
#define PRE_BODY 0
|
|
#define IN_BODY 1
|
|
#define POST_BODY 2
|
|
|
|
CParseState::CParseState()
|
|
:m_bInTitle(FALSE),
|
|
m_iDocPart(0),
|
|
m_inJavaScript(0),
|
|
m_baseFontSize(0),
|
|
m_pNextText(0),
|
|
m_pJavaScript(0),
|
|
m_pPostBody(0){
|
|
}
|
|
|
|
XP_Bool CParseState::InBody(){
|
|
return m_iDocPart == IN_BODY;
|
|
}
|
|
|
|
void CParseState::StartBody(){
|
|
m_iDocPart = IN_BODY;
|
|
}
|
|
|
|
void CParseState::EndBody(){
|
|
m_iDocPart = POST_BODY;
|
|
}
|
|
|
|
CStreamOutMemory* CParseState::GetStream(){
|
|
if ( m_iDocPart < POST_BODY ) {
|
|
// m_pJavaScript is mis-named. It's actually
|
|
// a bag we hold all our unprocessed head tags in.
|
|
if( m_pJavaScript == 0 ){
|
|
m_pJavaScript = new CStreamOutMemory();
|
|
}
|
|
return m_pJavaScript;
|
|
}
|
|
else {
|
|
if( m_pPostBody == 0 ){
|
|
m_pPostBody = new CStreamOutMemory();
|
|
}
|
|
return m_pPostBody;
|
|
}
|
|
}
|
|
|
|
CParseState::~CParseState() {
|
|
Free(m_pJavaScript);
|
|
Free(m_pPostBody);
|
|
delete m_pNextText;
|
|
}
|
|
|
|
void CParseState::Free(CStreamOutMemory*& pStream){
|
|
if ( pStream ) {
|
|
// COutMemoryStreams don't delete their text.
|
|
XP_HUGE_CHAR_PTR pData = pStream->GetText();
|
|
if ( pData ) {
|
|
XP_HUGE_FREE(pData);
|
|
}
|
|
delete pStream;
|
|
pStream = NULL;
|
|
}
|
|
}
|
|
|
|
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_iDocPart = 0;
|
|
m_bInTitle = FALSE;
|
|
m_inJavaScript = 0;
|
|
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;
|
|
m_bEncodeSelectionAsComment = FALSE;
|
|
}
|
|
|
|
void CPrintState::PrintSelectionComment(XP_Bool bEnd, XP_Bool bStickyAfter){
|
|
if ( !bEnd ) {
|
|
m_pBuffer->PasteHTMLHook(this);
|
|
}
|
|
m_pOut->Printf("<!-- %s%s -->", bEnd ? EDT_SELECTION_END_COMMENT : EDT_SELECTION_START_COMMENT,
|
|
bStickyAfter ? "+" : "");
|
|
}
|
|
|
|
XP_Bool CPrintState::ShouldPrintSelectionComments(CEditLeafElement* pElement){
|
|
return m_bEncodeSelectionAsComment && (
|
|
ShouldPrintSelectionComment(pElement, FALSE)
|
|
|| ShouldPrintSelectionComment(pElement, TRUE));
|
|
}
|
|
|
|
XP_Bool CPrintState::ShouldPrintSelectionComment(CEditLeafElement* pElement, XP_Bool bEnd){
|
|
return m_bEncodeSelectionAsComment && m_selection.GetEdge(bEnd)->m_pElement == pElement;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// 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 );
|
|
pNewLink->bAdjusted = FALSE;
|
|
if( pExtra ){
|
|
pNewLink->pExtra = XP_STRDUP( pExtra );
|
|
}
|
|
else {
|
|
pNewLink->pExtra = 0;
|
|
}
|
|
return pNewLink;
|
|
}
|
|
|
|
// When a target name changes, call this to change links to that target
|
|
XP_Bool CEditLinkManager::FixupLinksToTarget(char *pOldName, char *pNewName)
|
|
{
|
|
// Check if both strings exist and are different
|
|
if( !pOldName || !pNewName || 0 == XP_STRCMP(pOldName, pNewName) )
|
|
return FALSE;
|
|
|
|
XP_Bool bResult = TRUE;
|
|
for (int i = 0; i < m_links.Size(); i++ )
|
|
{
|
|
ED_Link *pLink;
|
|
if( (pLink = m_links[i]) != 0 && *pLink->hrefStr == '#' )
|
|
{
|
|
// Point to 1 character past the #
|
|
char *pTarget = pLink->hrefStr + 1;
|
|
intn iNewLen = XP_STRLEN(pNewName);
|
|
if( 0 == XP_STRCMP(pTarget, pOldName) )
|
|
{
|
|
if( iNewLen > (intn)XP_STRLEN(pOldName) )
|
|
{
|
|
// New name is longer - allocate more space
|
|
XP_FREE(pLink->hrefStr);
|
|
pLink->hrefStr = (char*)XP_ALLOC(iNewLen + 2);
|
|
if( pLink->hrefStr )
|
|
{
|
|
*pLink->hrefStr = '#';
|
|
pTarget = pLink->hrefStr + 1;
|
|
}
|
|
}
|
|
XP_STRCPY(pTarget, pNewName);
|
|
bResult = TRUE;
|
|
}
|
|
}
|
|
}
|
|
return bResult;
|
|
}
|
|
|
|
void CEditLinkManager::AdjustAllLinks( char *pOldURL, char* pNewURL, ED_HREFList *badLinks ){
|
|
int i;
|
|
for (i = 0; i < m_links.Size(); i++ ){
|
|
ED_Link *pLink;
|
|
if( (pLink = m_links[i]) != 0 ){
|
|
AdjustLink(&pLink->hrefStr,pOldURL,pNewURL,badLinks);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Used for adjusting links, images, etc.
|
|
// pNewBase can be NULL, the other args shouldn't be.
|
|
//
|
|
// Change *ppURL so that the URL will still point to the same location
|
|
// after changing the base URL.
|
|
// If there is no way to adjust ppURL, ppURL will not change and we add it to badLinks.
|
|
// E.g. if ppURL relative to pOldBase points to a local file and
|
|
// pNewBase is a remote site, we can't adjust the URL.
|
|
void CEditLinkManager::AdjustLink(char **ppURL,char *pOldBase, char *pNewBase, ED_HREFList *badLinks){
|
|
|
|
if (!ppURL || !*ppURL) {
|
|
return;
|
|
}
|
|
|
|
if ((*ppURL)[0] == '#' || (*ppURL)[0] == '`') {
|
|
// do nothing, but not an error.
|
|
return;
|
|
}
|
|
|
|
// Will make url
|
|
// absolute if the source and dest are on the same machine, just different
|
|
// drives. If on the same drive, will make a relative URL.
|
|
// If on different drives or different machine, leave URL alone, return FALSE.
|
|
|
|
// Make absolute relative to source URL
|
|
char *pAbsURL = NET_MakeAbsoluteURL( pOldBase, *ppURL );
|
|
if (!pAbsURL) {
|
|
// Add to list of bad links.
|
|
AddHREFUnique(badLinks,*ppURL);
|
|
return;
|
|
}
|
|
char *pNewURL = NULL;
|
|
|
|
if( pNewBase ){
|
|
// Try to make relative to dest URL.
|
|
int result = NET_MakeRelativeURL( pNewBase, pAbsURL, &pNewURL );
|
|
|
|
// If we can't make it relative.
|
|
if (result != NET_URL_SAME_DIRECTORY &&
|
|
result != NET_URL_SAME_DEVICE) {
|
|
|
|
if (NET_URL_Type(pAbsURL) == FILE_TYPE_URL &&
|
|
NET_URL_Type(pNewBase) != FILE_TYPE_URL) {
|
|
// No point in adjusting URL or making relative, local file is not
|
|
// visible to a remote machine. So don't change anything.
|
|
XP_FREEIF(pAbsURL);
|
|
XP_FREEIF(pNewURL);
|
|
|
|
// Add to list of bad links.
|
|
AddHREFUnique(badLinks,*ppURL);
|
|
return;
|
|
}
|
|
// else pNewURL will be a copy of pAbsURL, so just continue.
|
|
// I.e. we have made the URL absolute.
|
|
}
|
|
// result is same directory or same device.
|
|
else if (result == NET_URL_SAME_DEVICE && (*ppURL)[0] == '/') {
|
|
// Absolute pathing. Don't force it to be relative. It'll work in the
|
|
// new location.
|
|
XP_FREEIF(pAbsURL);
|
|
XP_FREEIF(pNewURL);
|
|
return;
|
|
}
|
|
// More aggressive absolute pathing. If the new relative url goes all the
|
|
// way up to the root, just make it start from the root instead of having a
|
|
// bunch of ../../../ Bug 50500.
|
|
// E.g. pAbsURL = http://host/images/bob.gif,
|
|
// pNewBase = http://host/dir1/dir2/dir3/doc.html
|
|
// instead of setting pNewURL to
|
|
// ../../../images/bob.gif, use
|
|
// /images/bob.gif
|
|
else if (result == NET_URL_SAME_DEVICE) {
|
|
// Count sets of ../ at the beginning of pNewURL.
|
|
int dotDotCount = 0;
|
|
char *pSeek = pNewURL;
|
|
while (pSeek && *pSeek && !XP_STRNCMP(pSeek,"../",3)) {
|
|
dotDotCount++;
|
|
pSeek +=3;
|
|
}
|
|
|
|
char *basePath = NET_ParseURL(pNewBase,GET_PATH_PART);
|
|
if (!basePath) {
|
|
XP_ASSERT(0);
|
|
return;
|
|
}
|
|
|
|
/* Find location of drive separator */
|
|
char *basePtr = (char*) XP_STRCHR(basePath, '|');
|
|
if (!basePtr) {
|
|
basePtr = basePath;
|
|
// basePtr now points to first '/' in pNewBase before directories
|
|
|
|
// Count number of subdirectories in pNewBase.
|
|
int subDirCount = -1; // Don't count first slash.
|
|
while (basePtr && *basePtr) {
|
|
if (*basePtr == '/') {
|
|
subDirCount++;
|
|
}
|
|
basePtr++;
|
|
}
|
|
XP_FREE(basePath);
|
|
|
|
if (dotDotCount >= subDirCount && dotDotCount > 0) {
|
|
if (dotDotCount > subDirCount) {
|
|
// Error, Net_MakeRelativeURL added too many ../
|
|
XP_ASSERT(0);
|
|
}
|
|
|
|
// pSeek is pointing to first char after all the ../
|
|
char *pTemp = XP_STRDUP(pSeek - 1); // Move back to get the slash.
|
|
XP_FREE(pNewURL);
|
|
pNewURL = pTemp;
|
|
}
|
|
}
|
|
else {
|
|
// there is a drive separator, so this trick won't work.
|
|
// use pNewURL as is.
|
|
XP_FREE(basePath);
|
|
}
|
|
}
|
|
} else {
|
|
// No new base is supplied - so we just return absolute URL
|
|
XP_FREEIF(*ppURL);
|
|
*ppURL = pAbsURL;
|
|
return;
|
|
}
|
|
|
|
// else pNewURL is a correct relative URL. Or an absolute URL if result == NET_URL_FAIL.
|
|
|
|
XP_FREEIF( pAbsURL );
|
|
XP_FREEIF(*ppURL);
|
|
*ppURL = pNewURL;
|
|
}
|
|
|
|
ED_LinkId CEditLinkManager::Add( char *pHREF, char *pExtra ){
|
|
int i = 0;
|
|
int lastFree = -1;
|
|
while( i < m_links.Size() ){
|
|
ED_Link* pLink = m_links[i];
|
|
if( pLink != 0 ){
|
|
// need a more intelegent way of comparing HREFs
|
|
if( XP_STRCMP( pLink->hrefStr, pHREF) == 0 ) {
|
|
// Links the same. How about pExtra ?
|
|
if ( (!!pExtra == !!pLink->pExtra)
|
|
&& ((pExtra &&
|
|
XP_STRCMP( pExtra, pLink->pExtra ) == 0 )
|
|
|| pExtra == 0 ) ){
|
|
pLink->iRefCount++;
|
|
return pLink;
|
|
}
|
|
}
|
|
}
|
|
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_FREEIF( id->pExtra );
|
|
XP_FREEIF( id->hrefStr );
|
|
XP_FREE( id );
|
|
}
|
|
}
|
|
|
|
void CEditLinkManager::AddHREFUnique(ED_HREFList *badLinks,char *pURL) {
|
|
for (int n = 0; n < badLinks->Size(); n++) {
|
|
// Just comparing links for equality by string comparison.
|
|
if (!XP_STRCMP((*badLinks)[n],pURL)) {
|
|
// Already in list.
|
|
return;
|
|
}
|
|
}
|
|
badLinks->Add(XP_STRDUP(pURL));
|
|
}
|
|
|
|
XP_Bool EDT_SelectTableElement(MWContext *pMWContext, int32 x, int32 y,
|
|
LO_Element *pLoElement,
|
|
ED_HitType iHitType,
|
|
XP_Bool bModifierKeyPressed,
|
|
XP_Bool bExtendSelection)
|
|
{
|
|
GET_EDIT_BUF_OR_RETURN(pMWContext, pEditBuffer) 0;
|
|
return pEditBuffer->SelectTableElement(x, y, pLoElement, iHitType,
|
|
bModifierKeyPressed, bExtendSelection);
|
|
}
|
|
|
|
ED_HitType EDT_ExtendTableCellSelection(MWContext *pMWContext, int32 x, int32 y)
|
|
{
|
|
GET_EDIT_BUF_OR_RETURN(pMWContext, pEditBuffer) ED_HIT_NONE;
|
|
return pEditBuffer->ExtendTableCellSelection(x, y);
|
|
}
|
|
|
|
// Dynamic Sizing Object
|
|
|
|
CSizingObject::CSizingObject() :
|
|
m_pBuffer(0),
|
|
m_pLoElement(0),
|
|
m_iStyle(0),
|
|
m_iXOrigin(0),
|
|
m_iYOrigin(0),
|
|
m_iXMouseOffset(0),
|
|
m_iYMouseOffset(0),
|
|
m_iStartWidth(0),
|
|
m_iStartHeight(0),
|
|
m_iViewWidth(0),
|
|
m_iViewHeight(0),
|
|
m_bWidthPercent(0),
|
|
m_bHeightPercent(0),
|
|
m_bPercentOriginal(0),
|
|
m_bFirstTime(1),
|
|
m_iAddCols(0),
|
|
m_iAddRows(0),
|
|
m_bCenterSizing(0)
|
|
{
|
|
}
|
|
|
|
XP_Bool CSizingObject::Create(CEditBuffer *pBuffer,
|
|
LO_Element *pLoElement,
|
|
int iSizingStyle,
|
|
int32 xVal, int32 yVal,
|
|
XP_Bool bModifierKeyPressed, XP_Rect *pRect){
|
|
XP_ASSERT(pBuffer);
|
|
XP_ASSERT(pRect);
|
|
XP_ASSERT(iSizingStyle);
|
|
m_pBuffer = pBuffer;
|
|
m_pLoElement = pLoElement;
|
|
m_iStyle = iSizingStyle;
|
|
|
|
if( !m_pLoElement )
|
|
{
|
|
#ifdef LAYERS
|
|
m_pLoElement = LO_XYToElement(pBuffer->m_pContext, xVal, yVal, 0);
|
|
#else
|
|
m_pLoElement = LO_XYToElement(pBuffer->m_pContext, xVal, yVal);
|
|
#endif
|
|
}
|
|
if( !m_pLoElement )
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
// Get view window size for "100%" sizing
|
|
FE_GetDocAndWindowPosition(pBuffer->m_pContext, &m_iXOrigin, &m_iYOrigin, &m_iViewWidth, &m_iViewHeight);
|
|
if( !m_iViewWidth || !m_iViewHeight )
|
|
{
|
|
return FALSE;
|
|
}
|
|
// Get extra margin
|
|
int32 iMarginWidth;
|
|
int32 iMarginHeight;
|
|
LO_GetDocumentMargins(pBuffer->m_pContext, &iMarginWidth, &iMarginHeight);
|
|
m_iViewWidth -= 2 * iMarginWidth;
|
|
m_iViewHeight -= 2 * iMarginHeight;
|
|
|
|
if( m_pLoElement->lo_any.type == LO_CELL )
|
|
{
|
|
LO_Element *pElement = NULL;
|
|
LO_Element *pLastElement = NULL;
|
|
|
|
// When resizing a column, we must find a cell that doesn't have COLSPAN > 1
|
|
if( m_iStyle == ED_SIZE_RIGHT && lo_GetColSpan(m_pLoElement) > 1 )
|
|
{
|
|
int32 iRightEdge = m_pLoElement->lo_cell.x + m_pLoElement->lo_cell.width;
|
|
|
|
pElement = lo_GetFirstAndLastCellsInTable(m_pBuffer->m_pContext, m_pLoElement, &pLastElement);
|
|
if( pElement )
|
|
{
|
|
do {
|
|
if( pElement && pElement->lo_any.type == LO_CELL &&
|
|
(pElement->lo_cell.x + pElement->lo_cell.width) == iRightEdge &&
|
|
lo_GetColSpan(pElement) == 1 )
|
|
{
|
|
m_pLoElement = pElement;
|
|
break;
|
|
}
|
|
if( pElement != pLastElement )
|
|
pElement = pElement->lo_any.next;
|
|
}
|
|
while( pElement != pLastElement );
|
|
}
|
|
}
|
|
if( m_iStyle == ED_SIZE_BOTTOM && lo_GetRowSpan(m_pLoElement) > 1 )
|
|
{
|
|
int32 iBottomEdge = m_pLoElement->lo_cell.y + m_pLoElement->lo_cell.height;
|
|
|
|
if( !pElement )
|
|
pElement = lo_GetFirstAndLastCellsInTable(m_pBuffer->m_pContext, m_pLoElement, &pLastElement);
|
|
if( pElement )
|
|
{
|
|
do {
|
|
if( pElement && pElement->lo_any.type == LO_CELL &&
|
|
(pElement->lo_cell.y + pElement->lo_cell.height) == iBottomEdge &&
|
|
lo_GetRowSpan(pElement) == 1 )
|
|
{
|
|
m_pLoElement = pElement;
|
|
break;
|
|
}
|
|
if( pElement != pLastElement )
|
|
pElement = pElement->lo_any.next;
|
|
}
|
|
while( pElement != pLastElement );
|
|
}
|
|
}
|
|
}
|
|
|
|
LO_Any *pAny = &(m_pLoElement->lo_any);
|
|
|
|
m_iParentWidth = 0;
|
|
// Check for sizing of table cell or nested table
|
|
if( pAny->type == LO_TABLE )
|
|
{
|
|
// Check for possible nested table by finding if it has a parent cell
|
|
LO_CellStruct *pParentCell = lo_GetParentCell(m_pBuffer->m_pContext, m_pLoElement);
|
|
if( pParentCell )
|
|
{
|
|
// Width = that of cell containing table
|
|
m_iParentWidth = pParentCell->width;
|
|
m_iWidthMsgID = XP_EDT_PERCENT_CELL;
|
|
}
|
|
}
|
|
else if( pAny->type == LO_CELL )
|
|
{
|
|
//
|
|
LO_TableStruct *pParentTable = lo_GetParentTable(m_pBuffer->m_pContext, m_pLoElement);
|
|
if( pParentTable )
|
|
{
|
|
// Get the width usable for percent calculation (minus borders and cell spacing)
|
|
m_iParentWidth = lo_CalcTableWidthForPercentMode(m_pLoElement);
|
|
m_iWidthMsgID = XP_EDT_PERCENT_TABLE;
|
|
}
|
|
}
|
|
|
|
if( m_iParentWidth == 0 )
|
|
{
|
|
m_iParentWidth = m_iViewWidth;
|
|
m_iWidthMsgID = XP_EDT_PERCENT_WINDOW;
|
|
}
|
|
|
|
// SHOULD WE DO THIS???
|
|
m_pBuffer->MoveAndHideCaretInTable(m_pLoElement);
|
|
|
|
// Calculate the rect in View's coordinates;
|
|
// ASSUMES PIXELS ONLY -- NO CONVERSION DONE
|
|
m_Rect.left = pAny->x + pAny->x_offset - m_iXOrigin;
|
|
m_Rect.top = pAny->y + pAny->y_offset - m_iYOrigin;
|
|
|
|
// Recent default value of flag used
|
|
// for image status string, e.g.: (% of original width)
|
|
m_bPercentOriginal = FALSE;
|
|
|
|
// Get percent-mode flag for edit element and adjust table/cell data
|
|
switch ( m_pLoElement->type )
|
|
{
|
|
case LO_IMAGE:
|
|
{
|
|
// Get the edit element
|
|
CEditElement * pElement = m_pLoElement->lo_any.edit_element;
|
|
if( !pElement ) return FALSE;
|
|
|
|
if( pElement->IsIcon() )
|
|
{
|
|
// All "unknown" tags are handled here
|
|
pElement->GetWidth(&m_bWidthPercent, &m_iStartWidth);
|
|
pElement->GetHeight(&m_bHeightPercent, &m_iStartHeight);
|
|
} else if( pElement->IsImage() )
|
|
{
|
|
EDT_ImageData * pImageData = pElement->Image()->GetImageData();
|
|
//pBuffer->GetImageData();
|
|
if( pImageData )
|
|
{
|
|
m_bWidthPercent = pImageData->bWidthPercent;
|
|
m_bHeightPercent = pImageData->bHeightPercent;
|
|
|
|
// Save the original height and width of the image
|
|
// if we have it
|
|
if( pImageData->iOriginalWidth )
|
|
{
|
|
m_iStartWidth = pImageData->iOriginalWidth;
|
|
m_iStartHeight = pImageData->iOriginalHeight;
|
|
// Constrain relative to "original" dimensions
|
|
m_bPercentOriginal = TRUE;
|
|
}
|
|
EDT_FreeImageData(pImageData);
|
|
}
|
|
// LO_ImageStruct includes border -- adjust
|
|
m_Rect.left += ((LO_ImageStruct*)pAny)->border_width;
|
|
m_Rect.top += ((LO_ImageStruct*)pAny)->border_width;
|
|
}
|
|
break;
|
|
}
|
|
case LO_HRULE:
|
|
{
|
|
// Get the edit element
|
|
CEditElement * pElement = m_pLoElement->lo_any.edit_element;
|
|
if( !pElement ) return FALSE;
|
|
|
|
EDT_HorizRuleData * pHData = pElement->HorizRule()->GetData(); //pBuffer->GetHorizRuleData();
|
|
if( pHData )
|
|
{
|
|
m_bWidthPercent = pHData->bWidthPercent;
|
|
if( pHData->align == ED_ALIGN_CENTER )
|
|
m_bCenterSizing = TRUE;
|
|
|
|
//Note: we will get width and "size" (height) in pixels below
|
|
m_iStartHeight = pHData->size;
|
|
EDT_FreeHorizRuleData(pHData);
|
|
}
|
|
break;
|
|
}
|
|
case LO_TABLE:
|
|
{
|
|
CEditTableElement *pTable =
|
|
(CEditTableElement*)edt_GetTableElementFromLO_Element( m_pLoElement, LO_TABLE );
|
|
if( pTable )
|
|
{
|
|
EDT_TableData *pTableData = pTable->GetData();
|
|
if( pTableData )
|
|
{
|
|
switch( iSizingStyle )
|
|
{
|
|
case ED_SIZE_RIGHT:
|
|
// Use width determined by layout
|
|
m_bWidthPercent = pTableData->bWidthPercent;
|
|
if( m_bWidthPercent )
|
|
{
|
|
m_iStartWidth = (pAny->width * 100) / m_iParentWidth;
|
|
} else {
|
|
m_iStartWidth = pAny->width;
|
|
}
|
|
break;
|
|
case ED_SIZE_BOTTOM:
|
|
// Use Height determined by layout
|
|
m_bHeightPercent = pTableData->bHeightPercent;
|
|
if( m_bHeightPercent )
|
|
{
|
|
m_iStartHeight = (pAny->height * 100) / m_iViewHeight;
|
|
} else {
|
|
m_iStartHeight = pAny->height;
|
|
}
|
|
break;
|
|
case ED_SIZE_ADD_COLS:
|
|
// When adding columns, show selection only
|
|
// around "new" columns, so rect.left = table->right
|
|
m_Rect.left += pAny->width;
|
|
m_Rect.right = m_Rect.left + 1;
|
|
m_Rect.bottom = m_Rect.top + pAny->height;
|
|
m_iStartHeight = pAny->height;
|
|
break;
|
|
|
|
case ED_SIZE_ADD_ROWS:
|
|
// As with columns, rect is around "new" rows
|
|
m_Rect.top += pAny->height;
|
|
m_Rect.bottom = m_Rect.top + 1;
|
|
m_Rect.right = m_Rect.left + pAny->width;
|
|
m_iStartWidth = pAny->width;
|
|
break;
|
|
}
|
|
EDT_FreeTableData(pTableData);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case LO_CELL:
|
|
{
|
|
CEditTableCellElement *pCell =
|
|
(CEditTableCellElement*)edt_GetTableElementFromLO_Element( m_pLoElement, LO_CELL );
|
|
if( pCell )
|
|
{
|
|
EDT_TableCellData * pCellData = pCell->GetData(0);
|
|
if( pCellData )
|
|
{
|
|
m_bWidthPercent = pCellData->bWidthPercent;
|
|
m_bHeightPercent = pCellData->bHeightPercent;
|
|
EDT_FreeTableCellData(pCellData);
|
|
|
|
if( iSizingStyle == ED_SIZE_RIGHT )
|
|
{
|
|
if( m_bWidthPercent )
|
|
{
|
|
// IMPORTANT: The "width" we use for cell sizing INCLUDES the
|
|
// border and padding values (as does the LO_Element value),
|
|
// This is NOT the same as the HTML "WIDTH" param or the Editor's
|
|
// pCellData->width. Same goes for Height
|
|
m_iStartWidth = (pAny->width * 100) / m_iParentWidth;
|
|
} else {
|
|
// Use width determined by layout
|
|
m_iStartWidth = pAny->width;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if( m_bHeightPercent )
|
|
{
|
|
m_iStartHeight = (pAny->height * 100) / m_iViewHeight;
|
|
} else {
|
|
m_iStartHeight = pAny->height;
|
|
}
|
|
}
|
|
|
|
// We will select the entire row or column, so get size of table
|
|
LO_TableStruct * pLoTable = lo_GetParentTable(pBuffer->m_pContext, m_pLoElement);
|
|
if( pLoTable )
|
|
{
|
|
int32 iAdjust = min(pLoTable->border_width, 4 - pLoTable->inter_cell_space);
|
|
if( iSizingStyle == ED_SIZE_RIGHT )
|
|
{
|
|
// Prefered top and bottom is just inside table border
|
|
m_Rect.top = pLoTable->y + pLoTable->y_offset - m_iYOrigin + pLoTable->border_width;
|
|
m_Rect.bottom = m_Rect.top + pLoTable->height - 2*pLoTable->border_width;
|
|
|
|
// Back off top and bottom if intercell space is too small
|
|
if( pLoTable->inter_cell_space < 4 && iAdjust > 0 )
|
|
{
|
|
m_Rect.top -= iAdjust;
|
|
m_Rect.bottom += iAdjust;
|
|
}
|
|
m_Rect.right = m_Rect.left + pAny->width;
|
|
}
|
|
else
|
|
{
|
|
// Prefered left and right is just inside table border
|
|
m_Rect.left = pLoTable->x + pLoTable->x_offset - m_iXOrigin + pLoTable->border_width;
|
|
m_Rect.right = m_Rect.left + pLoTable->width - 2*pLoTable->border_width;
|
|
|
|
// Back off if intercell space is too small
|
|
if( pLoTable->inter_cell_space < 4 && iAdjust > 0 )
|
|
{
|
|
m_Rect.left -= iAdjust;
|
|
m_Rect.right += iAdjust;
|
|
}
|
|
m_Rect.bottom = m_Rect.top + pAny->height;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
if( !m_bWidthPercent )
|
|
{
|
|
// We aren't doing percent, so set message ID for pixels
|
|
m_iWidthMsgID = XP_EDT_PIXELS;
|
|
}
|
|
|
|
// Finish calculating object rect if not set for table sizing above
|
|
if( iSizingStyle != ED_SIZE_ADD_COLS && iSizingStyle != ED_SIZE_ADD_ROWS &&
|
|
m_pLoElement->type != LO_CELL )
|
|
{
|
|
m_Rect.right = m_Rect.left + pAny->width;
|
|
m_Rect.bottom = m_Rect.top + pAny->height;
|
|
}
|
|
|
|
// If we didn't set above, use current size or minimum of 1
|
|
if( m_iStartWidth == 0 )
|
|
{
|
|
m_iStartWidth = max(1, pAny->width);
|
|
}
|
|
if( m_iStartHeight == 0 )
|
|
{
|
|
m_iStartHeight = max(1, pAny->height);
|
|
}
|
|
|
|
// Calculate the offset of actual object edge to
|
|
// the location of the mouse cursor
|
|
switch( iSizingStyle )
|
|
{
|
|
case ED_SIZE_LEFT:
|
|
m_iXMouseOffset = m_Rect.left - pAny->x_offset + m_iXOrigin - xVal;
|
|
break;
|
|
case ED_SIZE_RIGHT:
|
|
m_iXMouseOffset = m_Rect.right - pAny->x_offset + m_iXOrigin - xVal;
|
|
break;
|
|
case ED_SIZE_TOP:
|
|
m_iYMouseOffset = m_Rect.top - pAny->y_offset + m_iYOrigin - yVal;
|
|
break;
|
|
case ED_SIZE_BOTTOM:
|
|
m_iYMouseOffset = m_Rect.bottom - pAny->y_offset + m_iYOrigin - yVal;
|
|
break;
|
|
}
|
|
|
|
// Call this now primarily to start the
|
|
// status line display of sizing information
|
|
GetSizingRect(xVal, yVal, bModifierKeyPressed, &m_Rect);
|
|
//NOTE: If above offsets are correct, then m_Rect shouldn't change
|
|
// during call to GetSizingRect
|
|
|
|
if( pRect )
|
|
*pRect = m_Rect;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
#define EDT_NEW_ROW_HEIGHT 24
|
|
#define EDT_NEW_COL_WIDTH 24
|
|
|
|
PRIVATE
|
|
void CalcAddRowRect(int32 iRows, XP_Rect *pTableRect, XP_Rect *pRect)
|
|
{
|
|
pRect->left = pTableRect->left + 6;
|
|
pRect->right = pTableRect->right - 6;
|
|
pRect->top = pTableRect->top + (iRows * EDT_NEW_ROW_HEIGHT) + 2;
|
|
pRect->bottom = pRect->top;
|
|
}
|
|
|
|
PRIVATE
|
|
void CalcAddColRect(int32 iCols, XP_Rect *pTableRect, XP_Rect *pRect)
|
|
{
|
|
pRect->top = pTableRect->top + 6;
|
|
pRect->bottom = pTableRect->bottom - 6;
|
|
pRect->left = pTableRect->left + (iCols * EDT_NEW_COL_WIDTH) + 2;
|
|
pRect->right = pRect->left;
|
|
}
|
|
|
|
// Corner must be > any single side value
|
|
#define EDT_IS_SIZING_CORNER(style) (style == ED_SIZE_TOP_LEFT || \
|
|
style == ED_SIZE_TOP_RIGHT || \
|
|
style == ED_SIZE_BOTTOM_LEFT || \
|
|
style == ED_SIZE_BOTTOM_RIGHT )
|
|
|
|
XP_Bool CSizingObject::GetSizingRect(int32 xVal, int32 yVal, XP_Bool bModifierKeyPressed, XP_Rect *pRect)
|
|
{
|
|
XP_ASSERT(pRect);
|
|
|
|
// Adjust input X an Y by amount calculated in Create
|
|
// so we resize as if cursor started exactly on the correct edge
|
|
// instead of just close to it
|
|
xVal += m_iXMouseOffset;
|
|
yVal += m_iYMouseOffset;
|
|
|
|
int i;
|
|
// We never allow lock aspect for table sizing
|
|
// Instead, modifier is used to distinguish between sizing the last column or row
|
|
// and sizing the whole table
|
|
// For non-table objects, we lock aspect ratio if Modifier Key IS NOT pressed
|
|
XP_Bool bLockAspectRatio = m_pLoElement->type != LO_TABLE && m_pLoElement->type != LO_CELL &&
|
|
EDT_IS_SIZING_CORNER(m_iStyle) && !bModifierKeyPressed;
|
|
|
|
int32 iViewX = xVal - m_iXOrigin;
|
|
int32 iViewY = yVal - m_iYOrigin;
|
|
|
|
// Calculate the new rectangle
|
|
// And don't allow dragging past opposite side:
|
|
XP_Rect new_rect = m_Rect;
|
|
|
|
int32 iAmountMoved = 0;
|
|
if( m_iStyle == ED_SIZE_RIGHT )
|
|
iAmountMoved = max(m_Rect.left, iViewX) - m_Rect.right;
|
|
else if( m_iStyle == ED_SIZE_LEFT )
|
|
iAmountMoved = m_Rect.left - min(m_Rect.right, iViewX);
|
|
|
|
if( m_bWidthPercent )
|
|
{
|
|
// In % mode, limit largest value to get 100% of width or height
|
|
// Calc. the pixel value corresponding to just under 101% width or height,
|
|
// so roundoff will always allow achieving 100%
|
|
int32 iFullWidth = ((101 * m_iParentWidth) / 100) - 1;
|
|
int32 iFullHeight = ((101 * m_iViewHeight) / 100) - 1;
|
|
|
|
if( m_iStyle & ED_SIZE_TOP )
|
|
{
|
|
new_rect.top = min(m_Rect.bottom, max(iViewY,m_Rect.bottom-iFullHeight));
|
|
}
|
|
if( m_iStyle & ED_SIZE_BOTTOM )
|
|
{
|
|
new_rect.bottom = max(m_Rect.top, min(iViewY,m_Rect.top+iFullHeight));
|
|
}
|
|
if( m_iStyle & ED_SIZE_LEFT )
|
|
{
|
|
if( m_bCenterSizing )
|
|
{
|
|
new_rect.left -= iAmountMoved;
|
|
new_rect.right += iAmountMoved;
|
|
}
|
|
else
|
|
new_rect.left = min(m_Rect.right, max(iViewX,m_Rect.right-iFullWidth));
|
|
|
|
}
|
|
if( m_iStyle & ED_SIZE_RIGHT )
|
|
{
|
|
if( m_bCenterSizing )
|
|
{
|
|
new_rect.left -= iAmountMoved;
|
|
new_rect.right += iAmountMoved;
|
|
}
|
|
else
|
|
new_rect.right = max(m_Rect.left, iViewX);
|
|
}
|
|
} else {
|
|
if( m_iStyle & ED_SIZE_TOP )
|
|
{
|
|
new_rect.top = min(m_Rect.bottom, iViewY);
|
|
}
|
|
if( m_iStyle & ED_SIZE_BOTTOM || m_iStyle == ED_SIZE_ADD_ROWS )
|
|
{
|
|
new_rect.bottom = max(m_Rect.top, iViewY);
|
|
}
|
|
if( m_iStyle & ED_SIZE_LEFT )
|
|
{
|
|
if( m_bCenterSizing )
|
|
{
|
|
new_rect.left -= iAmountMoved;
|
|
new_rect.right += iAmountMoved;
|
|
}
|
|
else
|
|
new_rect.left = min(m_Rect.right, iViewX);
|
|
}
|
|
if( m_iStyle & ED_SIZE_RIGHT || m_iStyle == ED_SIZE_ADD_COLS )
|
|
{
|
|
if( m_bCenterSizing )
|
|
{
|
|
new_rect.left -= iAmountMoved;
|
|
new_rect.right += iAmountMoved;
|
|
}
|
|
else
|
|
new_rect.right = max(m_Rect.left, iViewX);
|
|
}
|
|
}
|
|
int iNewWidth = new_rect.right - new_rect.left;
|
|
int iNewHeight = new_rect.bottom - new_rect.top;
|
|
|
|
// When sizing both width and height,
|
|
// fixup coordinates to lock aspect ratio
|
|
//
|
|
if(bLockAspectRatio)
|
|
{
|
|
int iLockedWidth = (iNewHeight * m_iStartWidth) / m_iStartHeight;
|
|
int iLockedHeight = (iNewWidth * m_iStartHeight) / m_iStartWidth;
|
|
|
|
if( iNewWidth < iLockedWidth )
|
|
{
|
|
// New width is too small to match new height - adjust
|
|
if( m_iStyle & ED_SIZE_LEFT ){
|
|
new_rect.left = new_rect.right - iLockedWidth;
|
|
} else {
|
|
new_rect.right = new_rect.left + iLockedWidth;
|
|
}
|
|
} else if( iNewHeight < iLockedHeight )
|
|
{
|
|
// New height is too small to match new width
|
|
if( m_iStyle & ED_SIZE_TOP )
|
|
{
|
|
new_rect.top = new_rect.bottom - iLockedHeight;
|
|
} else {
|
|
new_rect.bottom = new_rect.top + iLockedHeight;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Return new rect to caller
|
|
*pRect = new_rect;
|
|
XP_Bool bRectChanged = new_rect.top != m_Rect.top ||
|
|
new_rect.bottom != m_Rect.bottom ||
|
|
new_rect.left != m_Rect.left ||
|
|
new_rect.right != m_Rect.right;
|
|
|
|
// Continue if rect is different from last rect requested
|
|
// or its the first time here (so status is displayed on initial mouse down)
|
|
if( bRectChanged || m_bFirstTime )
|
|
{
|
|
m_Rect = new_rect;
|
|
|
|
// Display status message with data for user:
|
|
char pMsg[512] = "";
|
|
char pPercentOfWhat[256] = "";
|
|
int32 iWidth = m_Rect.right - m_Rect.left;
|
|
int32 iHeight = m_Rect.bottom - m_Rect.top;
|
|
int32 iPercent = 0;
|
|
|
|
XP_Bool bDoWidth = (m_iStyle & ED_SIZE_LEFT) || (m_iStyle & ED_SIZE_RIGHT);
|
|
XP_Bool bDoHeight = (m_iStyle & ED_SIZE_TOP) || (m_iStyle & ED_SIZE_BOTTOM);
|
|
|
|
// Show only dimension(s) for whats changing: (Width vs. Height vs both)
|
|
// First part of status shows: "Width = xx[pixesl|%of window width]"
|
|
// and/or similar string for Height.
|
|
//
|
|
if( bDoWidth )
|
|
{
|
|
iPercent = (iWidth * 100 ) / m_iStartWidth;
|
|
if(m_bWidthPercent)
|
|
{
|
|
// Convert to % format
|
|
iWidth = (iWidth * 100) / m_iParentWidth;
|
|
}
|
|
else
|
|
{
|
|
// Tables are problematic. We use full cell width, including padding and borders,
|
|
// because it much easier to calculate percent mode values,
|
|
// but the HTML WIDTH and HEIGHT params only measure CONTENT area
|
|
// Should we display the same content size value when resizing?
|
|
// (That's what user sees in the Width and Height values in the property dialogs)
|
|
}
|
|
|
|
// "Width = x"
|
|
PR_snprintf(pMsg, 128, XP_GetString(XP_EDT_WIDTH_EQUALS), iWidth);
|
|
// "pixels" or "% of window"
|
|
|
|
strcat(pMsg, XP_GetString(m_iWidthMsgID));
|
|
|
|
// The % of original [width] and/or [height] string
|
|
// Current logic assumes constraining aspect ratio when sizing corners,
|
|
// so separate Width, Height percentages are not shown
|
|
XP_STRCPY(pPercentOfWhat, XP_GetString(XP_EDT_WIDTH));
|
|
}
|
|
if( bDoHeight )
|
|
{
|
|
// Since corners are constrained to aspect ratio,
|
|
// just use Width's calculation if already done
|
|
if( !bDoWidth )
|
|
{
|
|
iPercent = (iHeight * 100 ) / m_iStartHeight;
|
|
}
|
|
if(m_bHeightPercent)
|
|
{
|
|
iHeight = (iHeight * 100) / m_iViewHeight;
|
|
}
|
|
|
|
// "Height = x"
|
|
PR_snprintf(pMsg+XP_STRLEN(pMsg), 128,
|
|
XP_GetString(XP_EDT_HEIGHT_EQUALS), iHeight);
|
|
// "pixels" or "% of window"
|
|
strcat(pMsg, XP_GetString(m_bHeightPercent ? (int)XP_EDT_PERCENT_WINDOW :
|
|
(int)XP_EDT_PIXELS));
|
|
|
|
if( bDoWidth )
|
|
{
|
|
strcat(pPercentOfWhat, XP_GetString(XP_EDT_AND));
|
|
}
|
|
strcat(pPercentOfWhat, XP_GetString(XP_EDT_HEIGHT));
|
|
}
|
|
if( bDoWidth || bDoHeight )
|
|
{
|
|
// Build string to report relative size change:
|
|
// (% of [original|previous] [width | height | width and height])
|
|
PR_snprintf(pMsg+XP_STRLEN(pMsg), 128,
|
|
XP_GetString(m_bPercentOriginal ? (int)XP_EDT_PERCENT_ORIGINAL :
|
|
(int)XP_EDT_PERCENT_PREVIOUS),
|
|
iPercent, pPercentOfWhat);
|
|
} else if( m_iStyle == ED_SIZE_ADD_COLS )
|
|
{
|
|
int iAddCols = max(1, iNewWidth / EDT_NEW_COL_WIDTH);
|
|
PR_snprintf(pMsg, 128, XP_GetString(XP_EDT_ADD_COLUMNS_STATUS), iAddCols);
|
|
XP_Rect rect;
|
|
|
|
// Erase all existing lines
|
|
// NOTE: We can't draw/erase just the "new" line because
|
|
// we may skip lines if mouse is moved fast, causing a mess
|
|
for( i=1; i< m_iAddCols; i++ )
|
|
{
|
|
CalcAddColRect(i, &new_rect, &rect);
|
|
if( bRectChanged )
|
|
FE_DisplayAddRowOrColBorder(m_pBuffer->m_pContext, &rect, TRUE);
|
|
}
|
|
// Draw all new lines based on new number of columns
|
|
for( i=1; i< iAddCols; i++ )
|
|
{
|
|
CalcAddColRect(i, &new_rect, &rect);
|
|
if( bRectChanged )
|
|
FE_DisplayAddRowOrColBorder(m_pBuffer->m_pContext, &rect, FALSE);
|
|
}
|
|
m_iAddCols = iAddCols;
|
|
} else if( m_iStyle == ED_SIZE_ADD_ROWS )
|
|
{
|
|
int iAddRows = max(1, iNewHeight / EDT_NEW_ROW_HEIGHT);
|
|
PR_snprintf(pMsg, 128, XP_GetString(XP_EDT_ADD_ROWS_STATUS), iAddRows);
|
|
XP_Rect rect;
|
|
|
|
// Erase all existing lines
|
|
for( i=1; i< m_iAddRows; i++ )
|
|
{
|
|
CalcAddRowRect(i, &new_rect, &rect);
|
|
if( bRectChanged )
|
|
FE_DisplayAddRowOrColBorder(m_pBuffer->m_pContext, &rect, TRUE);
|
|
}
|
|
// Draw all new lines based on new number of columns
|
|
for( i=1; i< iAddRows; i++ )
|
|
{
|
|
CalcAddRowRect(i, &new_rect, &rect);
|
|
if( bRectChanged )
|
|
FE_DisplayAddRowOrColBorder(m_pBuffer->m_pContext, &rect, FALSE);
|
|
}
|
|
m_iAddRows = iAddRows;
|
|
}
|
|
|
|
FE_Progress(m_pBuffer->m_pContext, pMsg);
|
|
|
|
m_bFirstTime = FALSE;
|
|
|
|
// Indicate that rect changed
|
|
return bRectChanged;
|
|
}
|
|
// Rect is same as last time
|
|
return FALSE;
|
|
}
|
|
|
|
void CSizingObject::ResizeObject()
|
|
{
|
|
// Erase visual feedback when adding rows or columns
|
|
EraseAddRowsOrCols();
|
|
int32 iWidth=0, iWidthPixels=0, iHeightPixels=0, iHeight=0;
|
|
|
|
// Get the element being sized
|
|
CEditLeafElement *pElement = (CEditLeafElement*)(m_pLoElement->lo_any.edit_element);
|
|
|
|
if( !(m_iStyle == ED_SIZE_ADD_ROWS || m_iStyle == ED_SIZE_ADD_COLS) )
|
|
{
|
|
iWidthPixels = m_Rect.right - m_Rect.left;
|
|
iHeightPixels = m_Rect.bottom - m_Rect.top;
|
|
|
|
// Convert to percent if that's what we are using
|
|
// Note that we do not change that mode when returning new size
|
|
if( m_bWidthPercent )
|
|
{
|
|
iWidth = (iWidthPixels * 100) / m_iParentWidth;
|
|
} else {
|
|
iWidth = iWidthPixels;
|
|
}
|
|
if( m_bHeightPercent )
|
|
{
|
|
iHeight = (iHeightPixels * 100) / m_iViewHeight;
|
|
} else {
|
|
iHeight = iHeightPixels;
|
|
}
|
|
}
|
|
|
|
// Change the appropriate element's width and/or height
|
|
switch ( m_pLoElement->type )
|
|
{
|
|
case LO_IMAGE:
|
|
{
|
|
if( pElement && pElement->IsIcon() )
|
|
{
|
|
// TODO: ALL "UNKNOWN" TAGS ARE HANDLED HERE
|
|
// Only change dimension if we were sizing it
|
|
if( !(m_iStyle & ED_SIZE_LEFT ||
|
|
m_iStyle & ED_SIZE_RIGHT) )
|
|
{
|
|
iWidth = 0; // Signal to not change width...
|
|
}
|
|
if( !(m_iStyle & ED_SIZE_TOP ||
|
|
m_iStyle & ED_SIZE_BOTTOM) )
|
|
{
|
|
iHeight = 0; // ...or height
|
|
}
|
|
if( iWidth > 0 || iHeight > 0 )
|
|
{
|
|
m_pBuffer->BeginBatchChanges(kSetUnknownTagDataCommandID);
|
|
pElement->SetSize(m_bWidthPercent, iWidth, m_bHeightPercent, iHeight);
|
|
m_pBuffer->EndBatchChanges();
|
|
}
|
|
m_pBuffer->Relayout(pElement, 0);
|
|
} else if( pElement && pElement->IsImage() )
|
|
{
|
|
EDT_ImageData * pImageData = pElement->Image()->GetImageData();
|
|
if( pImageData )
|
|
{
|
|
|
|
// Only change dimension if we were sizing it
|
|
if( m_iStyle & ED_SIZE_TOP ||
|
|
m_iStyle & ED_SIZE_BOTTOM )
|
|
{
|
|
pImageData->iHeight = iHeight;
|
|
}
|
|
if( m_iStyle & ED_SIZE_LEFT ||
|
|
m_iStyle & ED_SIZE_RIGHT )
|
|
{
|
|
pImageData->iWidth = iWidth;
|
|
}
|
|
m_pBuffer->BeginBatchChanges(kSetImageDataCommandID);
|
|
// We are not changing image URL, so third param
|
|
// (keep-images-with-doc) shouldn't matter
|
|
pElement->Image()->SetImageData(pImageData);
|
|
edt_FreeImageData(pImageData);
|
|
m_pBuffer->Relayout(pElement,0);
|
|
m_pBuffer->EndBatchChanges();
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case LO_HRULE:
|
|
{
|
|
if( pElement )
|
|
{
|
|
EDT_HorizRuleData * pHData = pElement->HorizRule()->GetData(); //m_pBuffer->GetHorizRuleData();
|
|
if( pHData )
|
|
{
|
|
// Only change dimension if we were sizing it
|
|
if( m_iStyle & ED_SIZE_TOP ||
|
|
m_iStyle & ED_SIZE_BOTTOM )
|
|
{
|
|
pHData->size = iHeight;
|
|
}
|
|
if( m_iStyle & ED_SIZE_LEFT ||
|
|
m_iStyle & ED_SIZE_RIGHT )
|
|
{
|
|
pHData->iWidth = iWidth;
|
|
}
|
|
m_pBuffer->BeginBatchChanges(kSetHorizRuleDataCommandID);
|
|
pElement->HorizRule()->SetData(pHData);
|
|
EDT_FreeHorizRuleData(pHData);
|
|
m_pBuffer->Relayout(pElement,0);
|
|
m_pBuffer->EndBatchChanges();
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case LO_TABLE:
|
|
{
|
|
CEditTableElement *pTable =
|
|
(CEditTableElement*)edt_GetTableElementFromLO_Element( m_pLoElement, LO_TABLE );
|
|
if( pTable )
|
|
{
|
|
if( m_iStyle == ED_SIZE_ADD_ROWS ||
|
|
m_iStyle == ED_SIZE_ADD_COLS )
|
|
{
|
|
m_pBuffer->BeginBatchChanges(m_iStyle == ED_SIZE_ADD_COLS ?
|
|
kInsertTableColumnCommandID : kInsertTableRowCommandID);
|
|
|
|
// Be sure caret is already in the table,
|
|
// but don't show caret or scroll window
|
|
m_pBuffer->MoveAndHideCaretInTable(m_pLoElement);
|
|
|
|
// Move to last cell of table
|
|
while(m_pBuffer->NextTableCell())
|
|
;
|
|
|
|
if( m_iStyle == ED_SIZE_ADD_ROWS )
|
|
m_pBuffer->InsertTableRows(NULL, TRUE, m_iAddRows);
|
|
else
|
|
m_pBuffer->InsertTableColumns(NULL, TRUE, m_iAddCols);
|
|
|
|
// InsertTable... will relayout
|
|
//m_pBuffer->Relayout(pTable, 0);
|
|
|
|
m_pBuffer->EndBatchChanges();
|
|
}
|
|
else
|
|
{
|
|
|
|
// Sizing table width or height
|
|
EDT_TableData * pTableData = pTable->GetData();
|
|
XP_Bool bChangeWidth = FALSE;
|
|
XP_Bool bChangeHeight = FALSE;
|
|
if( pTableData )
|
|
{
|
|
XP_Bool bChangeTable = FALSE;
|
|
if( (m_iStyle & ED_SIZE_RIGHT) && iWidth != m_iStartWidth )
|
|
{
|
|
pTableData->bWidthDefined = TRUE;
|
|
pTableData->iWidth = iWidth;
|
|
pTableData->iWidthPixels = iWidthPixels;
|
|
bChangeWidth = TRUE;
|
|
}
|
|
if( (m_iStyle & ED_SIZE_BOTTOM) && iHeight != m_iStartHeight )
|
|
{
|
|
pTableData->bHeightDefined = TRUE;
|
|
pTableData->iHeight = iHeight;
|
|
pTableData->iHeightPixels = iHeightPixels;
|
|
bChangeHeight = TRUE;
|
|
}
|
|
if( bChangeWidth || bChangeHeight )
|
|
{
|
|
m_pBuffer->BeginBatchChanges(kSetTableDataCommandID);
|
|
pTable->SetData(pTableData);
|
|
// Use this instead of Relayout to properly
|
|
// adjust sizing params before relayout, then restore after
|
|
m_pBuffer->ResizeTable(pTable, bChangeWidth, bChangeHeight);
|
|
m_pBuffer->EndBatchChanges();
|
|
}
|
|
EDT_FreeTableData(pTableData);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case LO_CELL:
|
|
{
|
|
CEditTableCellElement *pCell =
|
|
(CEditTableCellElement*)edt_GetTableElementFromLO_Element( m_pLoElement, LO_CELL );
|
|
if( pCell )
|
|
{
|
|
CEditTableElement *pTable = pCell->GetParentTable();
|
|
// Should never happen!
|
|
if( !pTable )
|
|
break;
|
|
|
|
XP_Bool bChangeWidth = FALSE;
|
|
XP_Bool bChangeHeight = FALSE;
|
|
|
|
m_pBuffer->BeginBatchChanges(kSetTableCellDataCommandID);
|
|
|
|
if( (m_iStyle & ED_SIZE_RIGHT) && iWidth != m_iStartWidth )
|
|
{
|
|
bChangeWidth = TRUE;
|
|
EDT_TableCellData *pData = pCell->GetData();
|
|
if( pData )
|
|
{
|
|
pData->bWidthDefined = TRUE;
|
|
pData->iWidthPixels = iWidthPixels;
|
|
// Resize all cells sharing or spanning the right border
|
|
pCell->SetColumnWidthRight(pTable, m_pLoElement, pData);
|
|
EDT_FreeTableCellData(pData);
|
|
}
|
|
}
|
|
|
|
if( (m_iStyle & ED_SIZE_BOTTOM) && iHeight != m_iStartHeight )
|
|
{
|
|
bChangeHeight = TRUE;
|
|
EDT_TableCellData *pData = pCell->GetData();
|
|
if( pData )
|
|
{
|
|
pData->bHeightDefined = TRUE;
|
|
pData->iHeightPixels = iHeightPixels;
|
|
// Resize all cells sharing or spanning the bottom border
|
|
pCell->SetRowHeightBottom(pTable, m_pLoElement, pData);
|
|
EDT_FreeTableCellData(pData);
|
|
}
|
|
}
|
|
if( bChangeWidth || bChangeHeight )
|
|
{
|
|
// Relayout the entire table
|
|
// (This modifies current table and cell params
|
|
// to facilitate resizing, then restores after Relayout)
|
|
m_pBuffer->ResizeTableCell(pTable, bChangeWidth, bChangeHeight);
|
|
}
|
|
m_pBuffer->EndBatchChanges();
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void CSizingObject::EraseAddRowsOrCols()
|
|
{
|
|
int i;
|
|
XP_Rect rect;
|
|
if( m_iStyle == ED_SIZE_ADD_COLS )
|
|
{
|
|
// Erase all existing lines
|
|
// NOTE: We can't draw/erase just the "new" line because
|
|
// we may skip lines if mouse is moved fast, causing a mess
|
|
for( i=1; i< m_iAddCols; i++ )
|
|
{
|
|
CalcAddColRect(i, &m_Rect, &rect);
|
|
FE_DisplayAddRowOrColBorder(m_pBuffer->m_pContext, &rect, TRUE);
|
|
}
|
|
} else if( m_iStyle == ED_SIZE_ADD_ROWS )
|
|
{
|
|
// Erase all existing lines
|
|
for( i=1; i< m_iAddRows; i++ )
|
|
{
|
|
CalcAddRowRect(i, &m_Rect, &rect);
|
|
FE_DisplayAddRowOrColBorder(m_pBuffer->m_pContext, &rect, TRUE);
|
|
}
|
|
}
|
|
}
|
|
|
|
CSizingObject::~CSizingObject()
|
|
{
|
|
// Assure that next comparison to a new rect
|
|
// will be different
|
|
m_Rect.left = -1;
|
|
FE_Progress(m_pBuffer->m_pContext, "");
|
|
|
|
// Don't leave object in selected state
|
|
m_pBuffer->ClearSelection(TRUE, FALSE);
|
|
}
|
|
///////////////////////////////////////////////////
|
|
|
|
// 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;
|
|
m_bSuspended = FALSE;
|
|
m_bCalledWhileSuspended = FALSE;
|
|
}
|
|
|
|
void CAutoSaveTimer::SetEditBuffer(CEditBuffer* pBuffer){
|
|
m_pBuffer = pBuffer;
|
|
// Sanity check, is this an OK Buffer?
|
|
XP_ASSERT(m_pBuffer->m_pRoot);
|
|
}
|
|
|
|
void CAutoSaveTimer::OnCallback() {
|
|
if( m_bSuspended ){
|
|
m_bCalledWhileSuspended = TRUE;
|
|
} else {
|
|
// Sanity check -- is this an OK Buffer?
|
|
if ( ! m_pBuffer || ! m_pBuffer->m_pRoot ) {
|
|
XP_ASSERT(FALSE);
|
|
return;
|
|
}
|
|
// Skip autosave if currently suspended
|
|
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;
|
|
}
|
|
|
|
void CAutoSaveTimer::Suspend(){
|
|
m_bSuspended = TRUE;
|
|
}
|
|
|
|
void CAutoSaveTimer::Resume(){
|
|
m_bSuspended = FALSE;
|
|
// We need to restart the timer ONLY
|
|
// if we were called during a suspended period
|
|
if( m_bCalledWhileSuspended ){
|
|
m_bCalledWhileSuspended = FALSE;
|
|
} else {
|
|
Restart();
|
|
}
|
|
}
|
|
|
|
CEditDocState::CEditDocState() {
|
|
m_pBuffer = NULL;
|
|
m_version = 0;
|
|
}
|
|
|
|
CEditDocState::~CEditDocState() {
|
|
if (m_pBuffer) {
|
|
XP_HUGE_FREE(m_pBuffer);
|
|
}
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
void CEditDocState::Print(IStreamOut& stream) {
|
|
if (!m_pBuffer) return;
|
|
|
|
stream.Printf("%d bytes", XP_STRLEN(m_pBuffer));
|
|
// have to dump one line at a time. XP_TRACE has a 512 char limit on Windows.
|
|
char* b = m_pBuffer;
|
|
while ( *b != '\0' ) {
|
|
char* b2 = b;
|
|
while ( *b2 != '\0' && *b2 != '\n'){
|
|
b2++;
|
|
}
|
|
char old = *b2;
|
|
*b2 = '\0';
|
|
stream.Printf("%s\n", b);
|
|
*b2 = old;
|
|
b = b2 + 1;
|
|
if ( old == '\0' ) break;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
|
|
|
|
/* Take the document for current context and change appropriate
|
|
* params to make it look like a "Untitled" new document
|
|
* Allows loading any document as a "Template"
|
|
*/
|
|
void EDT_ConvertCurrentDocToNewDoc(MWContext * pMWContext)
|
|
{
|
|
GET_WRITABLE_EDIT_BUF_OR_RETURN(pMWContext, pEditBuffer);
|
|
pEditBuffer->ConvertCurrentDocToNewDoc();
|
|
}
|
|
|
|
char * EDT_GetFilename(char * pURL, XP_Bool bMustHaveExtension)
|
|
{
|
|
// No string or its empty
|
|
if( !pURL || !*pURL ){
|
|
return NULL;
|
|
}
|
|
|
|
// Isolate just the filename of source URL
|
|
char * pFilename = XP_STRRCHR(pURL, '/');
|
|
|
|
// Maybe its a local filename?
|
|
if(!pFilename){
|
|
pFilename = XP_STRRCHR(pURL, '\\');
|
|
}
|
|
|
|
// Or just after host or drive letter?
|
|
if(!pFilename){
|
|
pFilename = XP_STRCHR(pURL, ':');
|
|
}
|
|
|
|
if(pFilename){
|
|
// Filename starts just after "/"
|
|
pFilename++;
|
|
} else {
|
|
// Use what we have - maybe not a full URL spec?
|
|
pFilename = pURL;
|
|
}
|
|
char * pResult = NULL;
|
|
|
|
if( pFilename && *pFilename ){
|
|
// Don't include any stuff after filename
|
|
char * ques = XP_STRCHR(pFilename, '?');
|
|
char * hash = XP_STRCHR(pFilename, '#');
|
|
if(ques){
|
|
*ques = '\0';
|
|
}
|
|
if(hash){
|
|
*hash = '\0';
|
|
}
|
|
|
|
// Check if we must have a period to be considered a filename
|
|
// This allows text without "." to be considered a subdirectory, not filename
|
|
if( *pFilename ){
|
|
if( !bMustHaveExtension ||
|
|
(bMustHaveExtension && XP_STRCHR(pFilename, '.')) ){
|
|
// Copy just the filename if we have one
|
|
pResult = XP_STRDUP(pFilename);
|
|
}
|
|
}
|
|
|
|
// set the values back
|
|
if(ques){
|
|
*ques = '?';
|
|
}
|
|
if(hash){
|
|
*hash = '#';
|
|
}
|
|
}
|
|
return pResult;
|
|
}
|
|
|
|
char * EDT_ReplaceFilename(char * pBaseURL, char * pURL, XP_Bool bMustHaveExtension)
|
|
{
|
|
char * pResult = NULL;
|
|
if( !pBaseURL || !*pBaseURL ){
|
|
return NULL;
|
|
}
|
|
// Find the start of filename in base -
|
|
// copy to new string
|
|
// **** We overload the meaning of bMustHaveExtension to
|
|
// also cause any filename on the base to have an extension,
|
|
// else any name without trailing slash, such as //chainsaw/mydir",
|
|
// will be "corrected" by adding trailing slash
|
|
if( bMustHaveExtension ){
|
|
char * pBaseFilename = EDT_GetFilename(pBaseURL, bMustHaveExtension);
|
|
int iLen = XP_STRLEN(pBaseURL);
|
|
if( !pBaseFilename && pBaseURL[iLen-1] != '/' ){
|
|
// No trailing slash found, copy string and append one
|
|
pResult = (char*)XP_ALLOC(iLen+1);
|
|
if( !pResult ){
|
|
return NULL;
|
|
}
|
|
XP_STRCPY(pResult, pBaseURL);
|
|
pResult[iLen] = '/';
|
|
pResult[iLen+1] = '\0';
|
|
}
|
|
XP_FREEIF(pBaseFilename);
|
|
}
|
|
if( !pResult ){
|
|
char * pBaseEnd = XP_STRRCHR(pBaseURL, '/');
|
|
|
|
if( !pBaseEnd )
|
|
pBaseEnd = XP_STRCHR(pBaseURL, ':');
|
|
if( !pBaseEnd )
|
|
return NULL;
|
|
|
|
pBaseEnd++;
|
|
char save_char = *pBaseEnd;
|
|
*pBaseEnd = '\0';
|
|
|
|
// Allocate result and copy the base part
|
|
StrAllocCat(pResult, pBaseURL);
|
|
|
|
// Restore character
|
|
*pBaseEnd = save_char;
|
|
}
|
|
if( pResult && pURL ){
|
|
// Get just the filename of source URL
|
|
char * pFilename = EDT_GetFilename(pURL, bMustHaveExtension);
|
|
if( pFilename ){
|
|
// Append the filename to the base
|
|
StrAllocCat(pResult, pFilename);
|
|
XP_FREE(pFilename);
|
|
}
|
|
}
|
|
|
|
//Note: If no pURL, pBaseURL is returned stripped of its filename
|
|
return pResult;
|
|
}
|
|
|
|
#if defined(SingleSignon)
|
|
static XP_Bool GetSingleSignonData(MWContext * pMWContext, char *pLocation, char **ppUsername, char **ppPassword)
|
|
{
|
|
if( !pLocation )
|
|
return FALSE;
|
|
|
|
char *pUsernameSingleSignon = NULL;
|
|
char *pPasswordSingleSignon = NULL;
|
|
SI_RestoreOldSignonDataFromBrowser(pMWContext, pLocation, FALSE,
|
|
&pUsernameSingleSignon, &pPasswordSingleSignon);
|
|
if (pUsernameSingleSignon)
|
|
{
|
|
if( ppUsername )
|
|
*ppUsername = pUsernameSingleSignon;
|
|
else
|
|
XP_FREE(pUsernameSingleSignon);
|
|
|
|
if( ppPassword )
|
|
*ppPassword = pPasswordSingleSignon;
|
|
else if( pPasswordSingleSignon )
|
|
XP_FREE(pPasswordSingleSignon);
|
|
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
#endif
|
|
|
|
char * EDT_GetDefaultPublishURL(MWContext * pMWContext, char **ppFilename, char **ppUsername, char **ppPassword)
|
|
{
|
|
XP_ASSERT(pMWContext);
|
|
char * pURL = LO_GetBaseURL(pMWContext);
|
|
if( !pURL ){
|
|
return NULL;
|
|
}
|
|
XP_Bool bLastPublishFailed = EDT_IsSameURL(CEditSaveObject::m_pFailedPublishUrl, pURL, NULL, NULL);
|
|
|
|
if( ppFilename ){
|
|
if( EDT_IS_NEW_DOCUMENT(pMWContext) ){
|
|
// New page: empty filename
|
|
*ppFilename = NULL;
|
|
} else {
|
|
// Extract current filename part
|
|
*ppFilename = EDT_GetFilename(pURL, TRUE);
|
|
|
|
// On win16 force to lower case.
|
|
#ifdef XP_WIN16
|
|
char *pConvert = *ppFilename;
|
|
while (pConvert && *pConvert) {
|
|
*pConvert = XP_TO_LOWER(*pConvert);
|
|
pConvert++;
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
// Default: Clear password
|
|
if( ppPassword ){
|
|
*ppPassword = NULL;
|
|
}
|
|
|
|
#if defined(SingleSignon)
|
|
// Check if we saved a username/password for this URL
|
|
// (We will find it only if we are editing a remote URL)
|
|
if( GetSingleSignonData(pMWContext, pURL, ppUsername, ppPassword) )
|
|
// If we found a name, we assume the location part of
|
|
// URL is correct, so just return that
|
|
return EDT_ReplaceFilename(pURL, NULL, TRUE);
|
|
#endif
|
|
|
|
if( !bLastPublishFailed && !EDT_IS_NEW_DOCUMENT(pMWContext) ){
|
|
int iType = NET_URL_Type(pURL);
|
|
if( iType == FTP_TYPE_URL ||
|
|
iType == HTTP_TYPE_URL ||
|
|
iType == SECURE_HTTP_TYPE_URL ) {
|
|
// We are editing a remote page, so try to return it to that location
|
|
// (We don't know the password)
|
|
// Truncate current URL at the filename
|
|
return EDT_ReplaceFilename(pURL, NULL, TRUE);
|
|
}
|
|
}
|
|
|
|
char *pPrefURL = NULL;
|
|
|
|
if( bLastPublishFailed ){
|
|
// use the last location saved - this will be the location we failed at before
|
|
PREF_CopyCharPref("editor.publish_last_loc", &pPrefURL);
|
|
} else {
|
|
// Use the last successfully-used location saved in preferences
|
|
PREF_CopyCharPref("editor.publish_history_0", &pPrefURL);
|
|
}
|
|
// Be sure to check for empty pref string
|
|
if( !pPrefURL || !*pPrefURL ){
|
|
XP_FREEIF(pPrefURL);
|
|
// No last-used location -- use the default location
|
|
PREF_CopyCharPref("editor.publish_location", &pPrefURL);
|
|
}
|
|
|
|
char * pLocation = 0;
|
|
char * pUsername = ppUsername ? *ppUsername : 0;
|
|
NET_ParseUploadURL( pPrefURL, &pLocation, &pUsername, NULL );
|
|
|
|
#if defined(SingleSignon)
|
|
// Check if we saved a username/password for the pref location
|
|
if(! GetSingleSignonData(pMWContext, pLocation, ppUsername, ppPassword) )
|
|
#endif
|
|
{
|
|
if (ppUsername)
|
|
*ppUsername = pUsername;
|
|
}
|
|
XP_FREEIF(pPrefURL);
|
|
|
|
return pLocation;
|
|
}
|
|
|
|
//
|
|
// Keep this localized here. These two functions are the only ones
|
|
// that need to know this value.
|
|
// NOTE: CALLER MUST FREE THE RETURNED STRINGS
|
|
#define MAX_PUBLISH_LOCATIONS 20
|
|
|
|
#ifdef XP_WIN
|
|
XP_Bool
|
|
EDT_GetPublishingHistory(MWContext *pMWContext,
|
|
unsigned n,
|
|
char** ppLocation,
|
|
char** ppUsername,
|
|
char** ppPassword)
|
|
#else
|
|
XP_Bool
|
|
EDT_GetPublishingHistory(unsigned n,
|
|
char** ppLocation,
|
|
char** ppUsername,
|
|
char** ppPassword)
|
|
#endif
|
|
{
|
|
char prefname[32];
|
|
char* prefvalue = NULL;
|
|
|
|
if (n >= MAX_PUBLISH_LOCATIONS)
|
|
return FALSE;
|
|
|
|
XP_SPRINTF(prefname, "editor.publish_history_%d", n);
|
|
|
|
if( PREF_CopyCharPref(prefname, &prefvalue) == -1 ||
|
|
prefvalue == NULL || prefvalue[0] == '\0')
|
|
return FALSE;
|
|
|
|
if (ppLocation != NULL)
|
|
*ppLocation = NULL; /* else NET_ParseUploadURL() will try to free! */
|
|
if (ppUsername != NULL)
|
|
*ppUsername = NULL;
|
|
|
|
XP_Bool return_value;
|
|
return_value = NET_ParseUploadURL(prefvalue, ppLocation, ppUsername, NULL);
|
|
|
|
if (!return_value)
|
|
{
|
|
XP_FREE(prefvalue);
|
|
return FALSE;
|
|
}
|
|
|
|
if (ppPassword != NULL)
|
|
{
|
|
#ifdef XP_WIN
|
|
#if defined(SingleSignon)
|
|
// Check if we saved a username/password for this URL via SingleSignon
|
|
GetSingleSignonData(pMWContext, *ppLocation, ppUsername, ppPassword);
|
|
#endif
|
|
#endif
|
|
}
|
|
XP_FREEIF(prefvalue);
|
|
return TRUE;
|
|
}
|
|
|
|
void edt_SyncPublishingHistory(MWContext *pMWContext)
|
|
{
|
|
char prefname[32];
|
|
char* prefvalue = NULL;
|
|
char* pLastLoc = NULL;
|
|
int i;
|
|
|
|
/*
|
|
* Last location should be set when you start a publish.
|
|
* Now copy this to the top (first) of the history.
|
|
* Abort if pref doesn't exist or is empty
|
|
*/
|
|
if( PREF_CopyCharPref("editor.publish_last_loc", &pLastLoc) == -1 ||
|
|
!pLastLoc || !*pLastLoc )
|
|
return;
|
|
|
|
/*
|
|
* First scan the list to find pref that matches new item
|
|
*/
|
|
for (i = 0; i < (MAX_PUBLISH_LOCATIONS - 1); i++) {
|
|
XP_SPRINTF(prefname, "editor.publish_history_%d", i);
|
|
if( PREF_CopyCharPref(prefname, &prefvalue) == -1 ||
|
|
!prefvalue || !*prefvalue ||
|
|
XP_STRCMP(prefvalue, pLastLoc) == 0) {
|
|
//
|
|
// No history or this history matches the last publish location.
|
|
//
|
|
break;
|
|
}
|
|
XP_FREEIF(prefvalue);
|
|
}
|
|
XP_FREEIF(prefvalue);
|
|
|
|
/*
|
|
* Now shift everything up a slot.
|
|
*/
|
|
for (; i > 0; i--) {
|
|
XP_SPRINTF(prefname, "editor.publish_history_%d", i-1);
|
|
PREF_CopyCharPref(prefname, &prefvalue);
|
|
XP_SPRINTF(prefname, "editor.publish_history_%d", i);
|
|
PREF_SetCharPref(prefname, prefvalue);
|
|
XP_FREEIF(prefvalue);
|
|
}
|
|
|
|
/*
|
|
* Now set the zero'th one.
|
|
*/
|
|
PREF_SetCharPref("editor.publish_history_0", pLastLoc);
|
|
XP_FREEIF(pLastLoc);
|
|
}
|
|
|
|
XP_Bool EDT_GetUserDefaultPublishData(MWContext *pMWContext, char **ppLocation, char **ppUsername, char **ppPassword)
|
|
{
|
|
char * pDefaultLocation = NULL;
|
|
PREF_CopyCharPref("editor.publish_location", &pDefaultLocation);
|
|
if( !pDefaultLocation){
|
|
return FALSE;
|
|
}
|
|
char *pLocation = NULL;
|
|
char *pUsername = NULL;
|
|
|
|
// Parse the preference string to extract Username
|
|
NET_ParseUploadURL( pDefaultLocation, &pLocation, &pUsername, NULL );
|
|
|
|
#if defined(SingleSignon)
|
|
// Check if we saved a username/password for the pref location
|
|
if( !GetSingleSignonData(pMWContext, *ppLocation, ppUsername, ppPassword) )
|
|
#endif
|
|
{
|
|
if (ppUsername)
|
|
*ppUsername = pUsername;
|
|
else
|
|
XP_FREEIF(pUsername);
|
|
}
|
|
if( ppLocation )
|
|
*ppLocation = pLocation;
|
|
else
|
|
XP_FREEIF(pLocation);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
XP_Bool EDT_GetNetcenterPublishData(MWContext *pMWContext, char **ppLocation, char **ppUsername, char **ppPassword)
|
|
{
|
|
//TODO: FINISH THIS
|
|
return FALSE;
|
|
}
|
|
|
|
// Cache the Edit History data
|
|
static char** ppEditHistoryURLs = NULL;
|
|
static char** ppEditHistoryTitles = NULL;
|
|
|
|
// NOTE: Caller must NOT free the returned strings - they are locally cached
|
|
XP_Bool
|
|
EDT_GetEditHistory(MWContext * pMWContext, unsigned n,
|
|
char** ppUrl, char** ppTitle)
|
|
{
|
|
char prefname[32];
|
|
unsigned i;
|
|
|
|
// if context is NULL, bail!
|
|
if( !pMWContext )
|
|
return FALSE;
|
|
|
|
// Initialize list if not done so already
|
|
if( !ppEditHistoryURLs )
|
|
EDT_SyncEditHistory(pMWContext);
|
|
|
|
if( !ppEditHistoryURLs )
|
|
return FALSE;
|
|
|
|
// Check if current URL is matches the requested or more recent items in the list
|
|
if( pMWContext )
|
|
{
|
|
History_entry * pEntry = SHIST_GetCurrent(&(pMWContext->hist));
|
|
if( pEntry && pEntry->address )
|
|
{
|
|
for( i = 0; i <= n; i++ ){
|
|
XP_SPRINTF(prefname, "editor.url_history.URL_%d", i);
|
|
if( ppEditHistoryURLs[i] &&
|
|
EDT_IsSameURL(pEntry->address, ppEditHistoryURLs[i], 0, 0) )
|
|
{
|
|
// The current doc was found, so skip over it
|
|
n++;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Default in case we don't set the location
|
|
if( ppUrl )
|
|
*ppUrl = NULL;
|
|
|
|
if( n >= MAX_EDIT_HISTORY_LOCATIONS )
|
|
return FALSE;
|
|
|
|
if( !ppEditHistoryURLs[n] )
|
|
return FALSE;
|
|
|
|
// Return URL and Title to caller
|
|
if( ppUrl )
|
|
*ppUrl = ppEditHistoryURLs[n];
|
|
|
|
if( ppTitle )
|
|
*ppTitle = ppEditHistoryTitles[n];
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static char pEmpty[] = "";
|
|
|
|
void
|
|
EDT_SyncEditHistory(MWContext * pMWContext)
|
|
{
|
|
char prefname[32];
|
|
char* prefvalue = NULL;
|
|
char* pTitle = pEmpty;
|
|
int i;
|
|
|
|
if(!pMWContext)
|
|
return;
|
|
|
|
// We are not in a happy state if there's no history item
|
|
History_entry * pEntry = SHIST_GetCurrent(&(pMWContext->hist));
|
|
if(!pEntry || !pEntry->address || !*pEntry->address )
|
|
return;
|
|
|
|
// Test for new document URL: file://Untitled and ignore it
|
|
if( !XP_STRCMP(XP_GetString(XP_EDIT_NEW_DOC_NAME), pEntry->address) ||
|
|
!XP_STRCMP(XP_GetString(XP_EDIT_NEW_DOC_URL), pEntry->address))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Initialize the local arrays from the current preference list
|
|
if( !ppEditHistoryURLs )
|
|
{
|
|
ppEditHistoryURLs = (char**)XP_ALLOC( MAX_EDIT_HISTORY_LOCATIONS * sizeof(char*) );
|
|
if( !ppEditHistoryURLs )
|
|
return;
|
|
|
|
ppEditHistoryTitles = (char**)XP_ALLOC( MAX_EDIT_HISTORY_LOCATIONS * sizeof(char*) );
|
|
if( !ppEditHistoryTitles )
|
|
{
|
|
XP_FREEIF(ppEditHistoryURLs);
|
|
return;
|
|
}
|
|
|
|
for( i = 0; i < MAX_EDIT_HISTORY_LOCATIONS; i++ )
|
|
{
|
|
XP_SPRINTF(prefname, "editor.url_history.URL_%d", i);
|
|
if(PREF_CopyCharPref(prefname, &prefvalue) != -1 &&
|
|
prefvalue && *prefvalue )
|
|
{
|
|
ppEditHistoryURLs[i] = prefvalue;
|
|
prefvalue = NULL;
|
|
|
|
} else {
|
|
ppEditHistoryURLs[i] = NULL;
|
|
XP_FREEIF(prefvalue);
|
|
}
|
|
|
|
XP_SPRINTF(prefname, "editor.url_history.TITLE_%d", i);
|
|
if(PREF_CopyCharPref(prefname, &prefvalue) != -1 &&
|
|
prefvalue && *prefvalue )
|
|
{
|
|
ppEditHistoryTitles[i] = prefvalue;
|
|
prefvalue = NULL;
|
|
} else {
|
|
ppEditHistoryTitles[i] = NULL;
|
|
XP_FREEIF(prefvalue);
|
|
}
|
|
}
|
|
}
|
|
// We failed to initialize - quit
|
|
if( !ppEditHistoryURLs )
|
|
return;
|
|
|
|
if( pEntry->title && *pEntry->title ){
|
|
pTitle = pEntry->title;
|
|
}
|
|
|
|
/*
|
|
* First scan the list to find pref URL that matches current page
|
|
*/
|
|
for (i = 0; i < (MAX_EDIT_HISTORY_LOCATIONS - 1); i++)
|
|
{
|
|
XP_SPRINTF(prefname, "editor.url_history.URL_%d", i);
|
|
if( !ppEditHistoryURLs[i] ||
|
|
EDT_IsSameURL(ppEditHistoryURLs[i], pEntry->address, 0, 0) ) {
|
|
//
|
|
// No history or this history matches the current last-edited URL
|
|
//
|
|
break;
|
|
}
|
|
}
|
|
// Now i = the index to last-edited URL or last possible URL in the list
|
|
if( i == (MAX_EDIT_HISTORY_LOCATIONS-1) )
|
|
{
|
|
// We will be replacing the last item, so delete it
|
|
// so delete it
|
|
XP_FREEIF(ppEditHistoryURLs[i]);
|
|
XP_FREEIF(ppEditHistoryTitles[i]);
|
|
}
|
|
|
|
/*
|
|
* Now shift everything one item.
|
|
*/
|
|
for (; i > 0; i--)
|
|
{
|
|
XP_SPRINTF(prefname, "editor.url_history.URL_%d", i);
|
|
// Not sure if this can handle null string, so lets not try!
|
|
PREF_SetCharPref(prefname, ppEditHistoryURLs[i-1] ? ppEditHistoryURLs[i-1] : pEmpty);
|
|
|
|
ppEditHistoryURLs[i] = ppEditHistoryURLs[i-1];
|
|
|
|
XP_SPRINTF(prefname, "editor.url_history.TITLE_%d", i);
|
|
PREF_SetCharPref(prefname, ppEditHistoryTitles[i-1] ? ppEditHistoryTitles[i-1] : pEmpty);
|
|
|
|
ppEditHistoryTitles[i] = ppEditHistoryTitles[i-1];
|
|
}
|
|
|
|
/*
|
|
* Now set the zero'th one -- the current URL
|
|
*/
|
|
PREF_SetCharPref("editor.url_history.URL_0", pEntry->address);
|
|
PREF_SetCharPref("editor.url_history.TITLE_0", pTitle);
|
|
|
|
ppEditHistoryURLs[0] = XP_STRDUP(pEntry->address);
|
|
|
|
|
|
if( pTitle && *pTitle )
|
|
{
|
|
ppEditHistoryTitles[0] = XP_STRDUP(pTitle);
|
|
} else {
|
|
ppEditHistoryTitles[0] = NULL;
|
|
}
|
|
}
|
|
|
|
void edt_UpdateEditHistoryTitle(MWContext * pMWContext, char * pTitle)
|
|
{
|
|
if( pMWContext )
|
|
{
|
|
History_entry * pEntry = SHIST_GetCurrent(&(pMWContext->hist));
|
|
if(pEntry && pEntry->address && !*pEntry->address)
|
|
{
|
|
for (int i = 0; i < MAX_EDIT_HISTORY_LOCATIONS; i++)
|
|
{
|
|
char prefname[32];
|
|
XP_SPRINTF(prefname, "editor.url_history.URL_%d", i);
|
|
if( ppEditHistoryURLs[i] &&
|
|
EDT_IsSameURL(ppEditHistoryURLs[i], pEntry->address, 0, 0) )
|
|
{
|
|
XP_FREEIF(ppEditHistoryTitles[i]);
|
|
if( pTitle )
|
|
ppEditHistoryTitles[i] = pTitle;
|
|
|
|
// Also update the preference value
|
|
char prefname[32];
|
|
XP_SPRINTF(prefname, "editor.url_history.TITLE_%d", i);
|
|
|
|
// Not sure if this can handle null string, so lets not try!
|
|
PREF_SetCharPref(prefname, pTitle ? pTitle : pEmpty);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
char * EDT_GetPageTitleFromFilename(char * pFilename)
|
|
{
|
|
char * pTitle = NULL;
|
|
if( pFilename ){
|
|
// Get Full "filename" and we don't need an extension
|
|
pTitle = EDT_GetFilename(pFilename, FALSE);
|
|
|
|
if( pTitle ){
|
|
// Don't include extension
|
|
char * period = XP_STRCHR(pTitle, '.');
|
|
if(period){
|
|
// Truncate at the beginning of extension
|
|
*period = '\0';
|
|
}
|
|
}
|
|
}
|
|
return pTitle;
|
|
}
|
|
|
|
XP_Bool EDT_IsImageURL(char *pImageURL)
|
|
{
|
|
// Check for all known image file extensions
|
|
// without regard for case
|
|
if( pImageURL && *pImageURL &&
|
|
(strcasestr(pImageURL, ".jpeg") ||
|
|
strcasestr(pImageURL, ".jpg") ||
|
|
strcasestr(pImageURL, ".gif") ||
|
|
strcasestr(pImageURL, ".png") ||
|
|
strcasestr(pImageURL, ".xbmp")) )
|
|
{
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
static int StrcmpModuloSlashes(const char* s1, const char* s2)
|
|
{
|
|
if (!s1)
|
|
return -1;
|
|
if (!s2)
|
|
return 1;
|
|
while(1)
|
|
{
|
|
if (*s1 != *s2 || *s1 == 0)
|
|
return *s1 - *s2;
|
|
if (*s1 == '/')
|
|
{
|
|
while (*(++s1) == '/')
|
|
;
|
|
while (*(++s2) == '/')
|
|
;
|
|
continue;
|
|
}
|
|
++s1;
|
|
++s2;
|
|
}
|
|
}
|
|
|
|
// True if both urls are the same, ignores any username/password
|
|
// information. Does caseless comparison for file:// URLs
|
|
// on windows and mac.
|
|
// On Unix, collapses consecutive multiple slashes.
|
|
// url1 and url2 are relative to base1 and base2, respectively.
|
|
// If url1 or url2 is already absolute, base1 or base2 can
|
|
// be passed in as NULL.
|
|
XP_Bool EDT_IsSameURL(char *url1,char *url2,char *base1,char *base2) {
|
|
// Check for NULL or empty strings.
|
|
if (!url1 || !url2 || !*url1 || !*url2) {
|
|
return FALSE;
|
|
}
|
|
|
|
// Used passed in base URLs if provided.
|
|
char *pAbs1 = NULL;
|
|
char *pAbs2 = NULL;
|
|
if (base1) {
|
|
pAbs1 = NET_MakeAbsoluteURL(base1,url1);
|
|
}
|
|
if (base2) {
|
|
pAbs2 = NET_MakeAbsoluteURL(base2,url2);
|
|
}
|
|
// Strip any username/password info from urls in making comparison if
|
|
// they are http, https, or ftp.
|
|
char *pUrl1 = edt_StripUsernamePassword(pAbs1 ? pAbs1 : url1);
|
|
char *pUrl2 = edt_StripUsernamePassword(pAbs2 ? pAbs2 : url2);
|
|
edt_StripAtHashOrQuestionMark(pUrl1);
|
|
edt_StripAtHashOrQuestionMark(pUrl2);
|
|
|
|
XP_Bool bRetVal = FALSE;
|
|
if (pUrl1 && pUrl2) {
|
|
//char *pBaseUrl1 = NET_ParseURL(pUrl1, GET_PATH_PART);
|
|
//char *pBaseUrl2 = NET_ParseURL(pUrl2, GET_PATH_PART);
|
|
#if defined(XP_WIN) || defined(XP_MAC) || defined(XP_OS2)
|
|
// Strip off Target (named Anchors)
|
|
// Local file URLs are equivalent if they differ only
|
|
// by case.
|
|
if (NET_URL_Type(pUrl1) == FILE_TYPE_URL) {
|
|
bRetVal = (XP_STRCASECMP( pUrl1, pUrl2 ) == 0);
|
|
}
|
|
else {
|
|
bRetVal = (XP_STRCMP( pUrl1, pUrl2 ) == 0 );
|
|
}
|
|
#else
|
|
// Use regular strcmp.
|
|
bRetVal = (StrcmpModuloSlashes( pUrl1, pUrl2 ) == 0 );
|
|
#endif
|
|
}
|
|
|
|
XP_FREEIF(pUrl1);
|
|
XP_FREEIF(pUrl2);
|
|
XP_FREEIF(pAbs1);
|
|
XP_FREEIF(pAbs2);
|
|
return bRetVal;
|
|
}
|
|
|
|
// Replace the current selection with supplied text
|
|
void EDT_ReplaceText(MWContext *pContext, char * pReplaceText, XP_Bool bReplaceAll,
|
|
char *pTextToLookFor, XP_Bool bCaseless, XP_Bool bBackward, XP_Bool bDoWrap)
|
|
{
|
|
GET_WRITABLE_EDIT_BUF_OR_RETURN(pContext, pEditBuffer);
|
|
pEditBuffer->ReplaceLoop( pReplaceText, bReplaceAll,
|
|
pTextToLookFor, bCaseless, bBackward, bDoWrap );
|
|
}
|
|
|
|
// Block of functions moved from EDITOR.CPP for Win16 Build
|
|
// NOT USED???
|
|
#ifdef FIND_REPLACE
|
|
|
|
XP_Bool EDT_FindAndReplace(MWContext *pContext,
|
|
EDT_FindAndReplaceData *pData ){
|
|
GET_WRITABLE_EDIT_BUF_OR_RETURN(pContext, pEditBuffer) FALSE;
|
|
return pEditBuffer->FindAndReplace( pData );
|
|
|
|
}
|
|
#endif // FIND_REPLACE
|
|
|
|
// Table Sizing, Selection, Add Row/Col interface
|
|
ED_HitType EDT_GetTableHitRegion(MWContext *pContext, int32 xVal, int32 yVal,
|
|
LO_Element **ppElement, XP_Bool bModifierKeyPressed)
|
|
{
|
|
GET_EDIT_BUF_OR_RETURN(pContext, pEditBuffer) ED_HIT_NONE;
|
|
return pEditBuffer->GetTableHitRegion(xVal, yVal, ppElement, bModifierKeyPressed);
|
|
}
|
|
|
|
ED_HitType EDT_GetSelectedTableElement(MWContext *pContext, LO_Element **ppElement){
|
|
return EDT_GetTableHitRegion(pContext, 0, 0, ppElement, FALSE);
|
|
}
|
|
|
|
// Object Sizer
|
|
ED_SizeStyle EDT_CanSizeObject(MWContext *pContext, LO_Element *pElement,
|
|
int32 xVal, int32 yVal){
|
|
GET_EDIT_BUF_OR_RETURN(pContext, pEditBuffer) 0;
|
|
return pEditBuffer->CanSizeObject(pElement, xVal, yVal);
|
|
}
|
|
|
|
XP_Bool EDT_IsSizing(MWContext *pContext){
|
|
GET_EDIT_BUF_OR_RETURN(pContext, pEditBuffer) FALSE;
|
|
return pEditBuffer->IsSizing();
|
|
}
|
|
|
|
ED_SizeStyle EDT_StartSizing(MWContext *pContext, LO_Element *pElement, int32 xVal, int32 yVal,
|
|
XP_Bool bModifierKeyPressed, XP_Rect *pRect){
|
|
GET_WRITABLE_EDIT_BUF_OR_RETURN(pContext, pEditBuffer) 0;
|
|
return pEditBuffer->StartSizing(pElement, xVal, yVal, bModifierKeyPressed, pRect);
|
|
}
|
|
|
|
XP_Bool EDT_GetSizingRect(MWContext *pContext, int32 xVal, int32 yVal,
|
|
XP_Bool bModifierKeyPressed, XP_Rect *pRect){
|
|
GET_WRITABLE_EDIT_BUF_OR_RETURN(pContext, pEditBuffer) FALSE;
|
|
return pEditBuffer->GetSizingRect(xVal, yVal, bModifierKeyPressed, pRect);
|
|
}
|
|
|
|
void EDT_EndSizing(MWContext *pContext){
|
|
GET_WRITABLE_EDIT_BUF_OR_RETURN(pContext, pEditBuffer);
|
|
pEditBuffer->EndSizing();
|
|
}
|
|
|
|
void EDT_CancelSizing(MWContext *pContext){
|
|
GET_WRITABLE_EDIT_BUF_OR_RETURN(pContext, pEditBuffer);
|
|
pEditBuffer->CancelSizing();
|
|
}
|
|
|
|
ED_BufferOffset EDT_GetInsertPointOffset( MWContext *pContext ){
|
|
GET_EDIT_BUF_OR_RETURN(pContext, pEditBuffer) 0;
|
|
CPersistentEditInsertPoint p;
|
|
pEditBuffer->GetInsertPoint( p );
|
|
return (ED_BufferOffset) p.m_index;
|
|
}
|
|
|
|
void EDT_SetInsertPointToOffset( MWContext *pContext, ED_BufferOffset i,
|
|
int32 iLen ){
|
|
GET_WRITABLE_EDIT_BUF_OR_RETURN(pContext, pEditBuffer);
|
|
CPersistentEditInsertPoint p((ElementIndex)i);
|
|
|
|
if( iLen != 0 ){
|
|
CPersistentEditInsertPoint p1((ElementIndex)i+iLen);
|
|
CPersistentEditSelection perSel;
|
|
perSel = CPersistentEditSelection( p, p1 );
|
|
CEditSelection sel = pEditBuffer->PersistentToEphemeral( perSel );
|
|
pEditBuffer->SetSelection( sel );
|
|
}
|
|
else {
|
|
pEditBuffer->SetInsertPoint( p );
|
|
}
|
|
}
|
|
|
|
void EDT_OffsetToLayoutElement( MWContext *pContext, ED_BufferOffset i,
|
|
LO_Element * *element, int32 *caretPos){
|
|
GET_WRITABLE_EDIT_BUF_OR_RETURN(pContext, pEditBuffer);
|
|
CPersistentEditInsertPoint p((ElementIndex)i);
|
|
|
|
CEditInsertPoint eP = pEditBuffer->PersistentToEphemeral(p);
|
|
*element = eP.m_pElement->GetLayoutElement();
|
|
*caretPos = eP.m_iPos;
|
|
}
|
|
|
|
ED_BufferOffset EDT_LayoutElementToOffset( MWContext *pContext,
|
|
LO_Element *element, int32 caretPos){
|
|
GET_WRITABLE_EDIT_BUF_OR_RETURN(pContext, pEditBuffer) -1;
|
|
|
|
if (element->type != LO_TEXT)
|
|
return -1;
|
|
|
|
CEditInsertPoint insertPoint(element->lo_text.edit_element, caretPos);
|
|
|
|
CPersistentEditInsertPoint p = pEditBuffer->EphemeralToPersistent(insertPoint);
|
|
|
|
return p.m_index;
|
|
}
|
|
|
|
LO_TextBlock* EDT_GetTextBlock(MWContext *, LO_Element *le)
|
|
{
|
|
LO_TextBlock *block;
|
|
CEditTextElement *pText;
|
|
|
|
block = NULL;
|
|
|
|
if( ( le->type == LO_TEXT ) && ( le->lo_any.edit_element != NULL )
|
|
&& le->lo_any.edit_element->IsA( P_TEXT ) ){
|
|
pText = le->lo_any.edit_element->Text();
|
|
block = pText->GetTextBlock();
|
|
}
|
|
|
|
return block;
|
|
}
|
|
|
|
void EDT_GetSelectionOffsets(MWContext *pContext, ED_BufferOffset* pStart, ED_BufferOffset* pEnd){
|
|
if ( ! pStart || !pEnd ) {
|
|
XP_ASSERT(FALSE);
|
|
return;
|
|
}
|
|
GET_EDIT_BUF_OR_RETURN(pContext, pEditBuffer);
|
|
CPersistentEditSelection s;
|
|
pEditBuffer->GetSelection( s );
|
|
*pStart = (ED_BufferOffset) s.m_start.m_index;
|
|
*pEnd = (ED_BufferOffset) s.m_end.m_index;
|
|
}
|
|
|
|
XP_Bool EDT_SelectFirstMisspelledWord( MWContext *pContext ){
|
|
GET_WRITABLE_EDIT_BUF_OR_RETURN(pContext, pEditBuffer) FALSE;
|
|
return pEditBuffer->FindNextMisspelledWord( TRUE, TRUE, 0 );
|
|
}
|
|
|
|
XP_Bool EDT_SelectNextMisspelledWord( MWContext *pContext ){
|
|
GET_WRITABLE_EDIT_BUF_OR_RETURN(pContext, pEditBuffer) FALSE;
|
|
return pEditBuffer->FindNextMisspelledWord( FALSE, TRUE, 0 );
|
|
}
|
|
|
|
void EDT_ReplaceMisspelledWord( MWContext *pContext, char* pOldWord,
|
|
char*pNewWord, XP_Bool bAll ){
|
|
GET_EDIT_BUF_OR_RETURN(pContext, pEditBuffer);
|
|
pEditBuffer->BeginBatchChanges(kPasteTextCommandID);
|
|
pEditBuffer->IterateMisspelledWords( CEditBuffer::EMSW_REPLACE,
|
|
pOldWord, pNewWord, bAll );
|
|
pEditBuffer->EndBatchChanges();
|
|
}
|
|
|
|
void EDT_IgnoreMisspelledWord( MWContext *pContext, char* pOldWord,
|
|
XP_Bool bAll ){
|
|
GET_EDIT_BUF_OR_RETURN(pContext, pEditBuffer);
|
|
pEditBuffer->IterateMisspelledWords( CEditBuffer::EMSW_IGNORE,
|
|
pOldWord, 0, bAll );
|
|
}
|
|
|
|
|
|
XP_HUGE_CHAR_PTR EDT_GetPositionalText( MWContext* pContext ){
|
|
GET_EDIT_BUF_OR_RETURN(pContext, pEditBuffer) 0;
|
|
return pEditBuffer->GetPositionalText();
|
|
}
|
|
|
|
void EDT_SetCharacterDataAtOffset( MWContext *pContext, EDT_CharacterData *pData,
|
|
ED_BufferOffset iBufOffset, int32 iLen ){
|
|
GET_WRITABLE_EDIT_BUF_OR_RETURN(pContext, pEditBuffer);
|
|
pEditBuffer->SetCharacterDataAtOffset(pData, iBufOffset, iLen );
|
|
}
|
|
|
|
void EDT_SetRefresh( MWContext* pContext, XP_Bool bRefreshOn ){
|
|
GET_EDIT_BUF_OR_RETURN(pContext, pEditBuffer);
|
|
pEditBuffer->SetRefresh( bRefreshOn );
|
|
}
|
|
|
|
|
|
// Warning this deletes (and recreates) the CEditBuffer if we ChangeEncoding
|
|
XP_Bool EDT_SetEncoding(MWContext* pContext, int16 csid){
|
|
GET_EDIT_BUF_OR_RETURN(pContext, pEditBuffer) FALSE;
|
|
return pEditBuffer->SetEncoding(csid);
|
|
}
|
|
|
|
/*
|
|
* Extract the Extra HTML string from the ED_Element pointer in an image struct
|
|
* (ED_Element is a void* to external users)
|
|
* Caller must XP_FREE result
|
|
*/
|
|
char * EDT_GetExtraHTML_FromImage(LO_ImageStruct *pImage){
|
|
if( pImage ){
|
|
if( pImage->edit_element == 0 ){
|
|
// We don't have an edit element - must be from Navigator,
|
|
// so we have to pick out stuff that would end up in CEditImageElement's extra data
|
|
// BUT the original PA_TAG data is no longer around.
|
|
// The only item we will grab is the USEMAP data
|
|
// TODO: Get other associated data such as JavaScript?
|
|
// To do that, we need a new function to reconstruct a tag from LO_ImageStruct
|
|
|
|
if ( pImage->image_attr && pImage->image_attr->usemap_name ){
|
|
char * pRet = 0;
|
|
pRet = PR_sprintf_append( pRet, "USEMAP=");
|
|
pRet = PR_sprintf_append( pRet, pImage->image_attr->usemap_name);
|
|
return pRet;
|
|
}
|
|
}else if( pImage->edit_element->IsImage() ){
|
|
EDT_ImageData * pImageData = ((CEditImageElement*)pImage->edit_element)->GetImageData();
|
|
if( pImageData->pExtra && *pImageData->pExtra ){
|
|
return XP_STRDUP(pImageData->pExtra);
|
|
}
|
|
EDT_FreeImageData(pImageData);
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
PRBool EDT_EncryptState( MWContext *pContext ){
|
|
CEditBuffer *pEditBuffer = LO_GetEDBuffer( pContext );
|
|
|
|
if (pEditBuffer == NULL) return PR_FALSE;
|
|
|
|
return pEditBuffer->m_bEncrypt;
|
|
}
|
|
|
|
void EDT_EncryptToggle( MWContext *pContext ){
|
|
CEditBuffer *pEditBuffer = LO_GetEDBuffer( pContext );
|
|
|
|
if (pEditBuffer == NULL) return;
|
|
|
|
pEditBuffer->m_bEncrypt = pEditBuffer->m_bEncrypt ? PR_FALSE : PR_TRUE;
|
|
}
|
|
|
|
void EDT_EncryptSet( MWContext *pContext ){
|
|
CEditBuffer *pEditBuffer = LO_GetEDBuffer( pContext );
|
|
|
|
if (pEditBuffer == NULL) return;
|
|
|
|
pEditBuffer->m_bEncrypt = PR_TRUE;
|
|
}
|
|
|
|
void EDT_EncryptReset( MWContext *pContext ){
|
|
CEditBuffer *pEditBuffer = LO_GetEDBuffer( pContext );
|
|
|
|
if (pEditBuffer == NULL) return;
|
|
|
|
pEditBuffer->m_bEncrypt = PR_FALSE;
|
|
}
|
|
|
|
XP_Bool EDT_AdjustTableRectForCaption(LO_TableStruct *pTable, XP_Rect *pRect)
|
|
{
|
|
if( !pTable || !pRect )
|
|
return FALSE;
|
|
|
|
// If table has a caption, we want highlighting to surround the caption as well
|
|
LO_Element *pElement = (LO_Element*)pTable;
|
|
// Find the first cell
|
|
while( pElement && pElement->type != LO_CELL )
|
|
pElement = pElement->lo_any.next;
|
|
if( pElement )
|
|
{
|
|
XP_Bool bCaptionBeforeTable = pElement->lo_cell.isCaption;
|
|
XP_Bool bCaptionAfterTable = FALSE;
|
|
if( !bCaptionBeforeTable )
|
|
{
|
|
// Search for a caption at the end
|
|
do {
|
|
if( pElement && pElement->type == LO_CELL &&
|
|
pElement->lo_cell.isCaption )
|
|
{
|
|
bCaptionAfterTable = TRUE;
|
|
break;
|
|
}
|
|
pElement = pElement->lo_any.next;
|
|
}
|
|
while( pElement && pElement->type != LO_LINEFEED );
|
|
|
|
}
|
|
if( bCaptionBeforeTable || bCaptionAfterTable )
|
|
{
|
|
// This is the full height, including caption
|
|
int32 iHeight = pTable->line_height + pTable->border_bottom_width + pTable->inter_cell_space;
|
|
|
|
// Adjust top or bottom to include caption area
|
|
if( bCaptionBeforeTable )
|
|
pRect->top = pRect->bottom - iHeight;
|
|
else
|
|
pRect->bottom = pRect->top + iHeight;
|
|
|
|
return TRUE;
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
void EDT_SavePublishUsername(MWContext *pContext, char *pAddress, char *pUsername)
|
|
{
|
|
// Save the location and username given to us by NetLib
|
|
// when it prompts for a username and password
|
|
if( pContext && pAddress && *pAddress && pUsername )
|
|
{
|
|
GET_EDIT_BUF_OR_RETURN(pContext, pEditBuffer);
|
|
// Copy the address and strip off the filename from the supplied URL
|
|
char *pNewLocation = EDT_ReplaceFilename(pAddress, 0, TRUE);
|
|
|
|
if( pNewLocation )
|
|
{
|
|
char *pPrefLocation = 0;
|
|
// Insert the username into the location string and save in prefs
|
|
if( NET_MakeUploadURL(&pPrefLocation, pNewLocation, pUsername, 0) )
|
|
PREF_SetCharPref("editor.publish_last_loc", pPrefLocation);
|
|
|
|
XP_FREEIF(pPrefLocation);
|
|
XP_FREE(pNewLocation);
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif // EDITOR
|