/* *@@sourcefile textview.c: * all-new XTextView control as well as device-independent * text formatting and printing. Whoa. * * Text view control * * This is a read-only control to display any given text * (PSZ) in any given font. As opposed to a multi-line entry * field (MLE), this can handle multiple fonts and character * and paragraph formatting. Also, this thing sets its scroll * bars right, which is one of the most annoying bugs in the * MLE control. * * This is currently in the process of turning into a full-fledged * "rich text" control. For example, the WarpIN "Readme" pages * use this control. * * This is all new with V0.9.1. Great changes have been made * with V0.9.3. * * To use the text view control, you must call txvRegisterTextView * in your application first. This registers the WC_XTEXTVIEW * window class with PM. * * Then create your XTextView using WinCreateWindow. The * XTextView control has an XTEXTVIEWCDATA control data * structure which optionally can be passed to WinCreateWindow * like this: * + XTEXTVIEWCDATA xtxvCData; + memset(&xtxvCData, 0, sizeof(xtxvCData)); + xtxvCData.cbData = sizeof(xtxvCData); + xtxvCData.flStyle = XTXF_VSCROLL; + xtxvCData.ulXBorder = 20; + xtxvCData.ulYBorder = 20; + G_hwndProcView = WinCreateWindow(hwndClient, // parent + WC_XTEXTVIEW, // class + "", // title, always ignored + WS_VISIBLE, // style flags + 0, 0, 100, 100, // pos and size + hwndClient, // owner + HWND_TOP, // z-order + ID_PROCINFO, // win ID + &xtxvCData, // control data + 0); // presparams + * Setting the text to be displayed * * The text to be displayed must be passed to the control using * the standard WinSetWindowText function (upon which PM sends a * WM_SETWINDOWPARMS message to the control), which is then * automatically formatted and painted. * * However, since the XTextView control is extremely capable, * a few things need to be noted: * * -- The text view assumes that lines terminate with \n ONLY. * The \r char is used for soft line breaks (start new line * in the same paragraph, similar to the HTML BR tag). If * you give the control the usual OS/2 \r\n sequence, you * get large spacings. Use txvStripLinefeeds to strip the * \r characters before setting the text. * * In short, to give the control any text, do this: + + PSZ psz = ... // whatever, load string + txvStripLinefeeds(&psz); // reallocates + WinSetWindowText(hwndTextView, psz); * * -- The control uses the \xFF (255) character internally as * an escape code for formatting commands. See "Escape codes" * below. If your text contains this character, you should * overwrite all occurences with spaces, or they will be * considered an escape code, which will cause problems. * * -- If you don't care about learning all the escape codes, * you can automatically have HTML code converted to the * XTextView format using txvConvertFromHTML, which will * automatically insert all the codes right from plain * HTML. In the above code, use txvConvertFromHTML instead * of txvStripLinefeeds. * * Code page support * * The XTextView control assumes that the text given to it uses * the same codepage as the message queue (thread) on which * the control is running. So if you need codepage support, * issue WinSetCp before creating the text view control. * * Text formatting * * The XTextView control has a default paragraph format which * determines how text is formatted. If you don't change this * format, the control performs no word-wrapping and displays * all text "as is", that is, practically no formatting is * performed. * * You can change the default paragraph format by sending * TXM_SETPARFORMAT to the control. This takes a XFMTPARAGRAPH * structure for input. * * To quickly enable word-wrapping only, we have the extra * TXM_SETWORDWRAP message. This changes the word-wrapping flag * in the default paragraph format only so you don't have to * mess with all the rest. * * The XTextView control is extremely fast in formatting. It * does pre-calculations once so that resizing the text * window does not perform a full reformat, but a quick * format based on the pre-calculations. * * Presently, formatting is done synchronously. It is planned * to put formatting into a separate thread. Performance is * acceptable already now unless very large texts (> 200 KB) * are formatted (tested on a PII-400 machine). * * Presentation Parameters * * The XTextView control recognizes the following presentation * parameters: * * -- PP_BACKGROUNDCOLOR; if not set, SYSCLR_DIALOGBACKGROUND * (per default gray) is used. * * -- PP_FOREGROUNDCOLOR: if not set, SYSCLR_WINDOWSTATICTEXT * (per default blue) is used to signify that the text * cannot be worked on. * * -- PP_FONTNAMESIZE: default font. This is the system font, * if not set. * * This implies that fonts and colors can be dropped on the * control in the normal way. Font changes will cause a reformat. * * Changing those presentation parameters is equivalent to * changing the corresponding fields in the default paragraph * format using TXM_SETPARFORMAT. * * Escape codes * * All XTextView escape codes start with a \xFF (255) character, * followed by at least one more character. The escape sequences * are variable in length and can have parameters. For details, * see textview.h where all these are listed. * * Escape codes are evaluated by txvFormatText during formatting. * * If you choose to give the text view control a text which * contains escape codes, you better make sure that you get the * exact codes right, or the text view control can crash. The * control has been optimized for speed, so no checking is done * on escape sequences. * * Device-independent text formatting * * If the features of the XTextView control satisfy your needs, * there's not much to worry about. However, if you're interested * in formatting the text yourself, here's more: * * This file has the txvFormatText function, which is capable * of formatting an input string into any HPS. This works for * windows (used by the text view control) and printers (used * by txvPrint). Word-wrapping is supported. This is used by * the XTextView control internally whenever (re)formatting is * needed: either when the text is set or the formatting parameters * (fonts, margins, etc.) have changed. * * These functions are designed to be used in a two-step process: * first format the text (using txvFormatText), then paint it * (using txvPaintText) for viewing or printing. * This speeds up painting dramatically, because formatting * might take some time. * * Note: Version numbering in this file relates to XWorkplace version * numbering. * *@@header "helpers\textview.h" * *@@added V0.9.1 (2000-02-13) [umoeller] */ /* * Copyright (C) 2000-2008 Ulrich M”ller. * This program is part of the XWorkplace package. * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, in version 2 as it comes in the COPYING * file of the XWorkplace main distribution. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #define OS2EMX_PLAIN_CHAR // this is needed for "os2emx.h"; if this is defined, // emx will define PSZ as _signed_ char, otherwise // as unsigned char #define OS2EMX_PLAIN_CHAR // this is needed for "os2emx.h"; if this is defined, // emx will define PSZ as _signed_ char, otherwise // as unsigned char #define INCL_WINWINDOWMGR #define INCL_WINFRAMEMGR #define INCL_WINMESSAGEMGR #define INCL_WININPUT #define INCL_WINRECTANGLES #define INCL_WINPOINTERS #define INCL_WINSYS #define INCL_WINSCROLLBARS #define INCL_WINSTDFONT #define INCL_WINCOUNTRY #define INCL_DEV #define INCL_SPL #define INCL_SPLDOSPRINT #define INCL_GPIPRIMITIVES #define INCL_GPILCIDS #define INCL_GPILOGCOLORTABLE #define INCL_GPITRANSFORMS #define INCL_GPIREGIONS #define INCL_ERRORS #include #include #include #include #include "setup.h" // code generation and debugging options #include "helpers\comctl.h" #include "helpers\gpih.h" #include "helpers\linklist.h" #include "helpers\stringh.h" #include "helpers\winh.h" #include "helpers\xstring.h" // extended string helpers #include "helpers\textview.h" #include "helpers\textv_html.h" #pragma hdrstop /* *@@category: Helpers\PM helpers\Window classes\XTextView control * See textview.c. */ /* ****************************************************************** * * Device-independent functions * ********************************************************************/ /* *@@ txvInitFormat: * */ VOID txvInitFormat(PXFORMATDATA pxfd) { memset(pxfd, 0, sizeof(XFORMATDATA)); lstInit(&pxfd->llRectangles, TRUE); // auto-free items lstInit(&pxfd->llWords, TRUE); // auto-free items xstrInit(&pxfd->strOrigText, 0); // WarpIN V1.0.18 xstrInit(&pxfd->strViewText, 0); } /* *@@ SetSubFont: * *@@added V0.9.3 (2000-05-06) [umoeller] */ STATIC VOID SetSubFont(HPS hps, PXFMTFONT pFont, ULONG ulPointSize, PSZ pszFaceName, ULONG flFormat) { CHAR ac[256]; ULONG ul; POINTL ptlStart = {0, 0}, aptl[257]; if (pFont->lcid) { // font already loaded: if (GpiQueryCharSet(hps) == pFont->lcid) // font currently selected: GpiSetCharSet(hps, LCID_DEFAULT); GpiDeleteSetId(hps, pFont->lcid); } if (pszFaceName) pFont->lcid = gpihFindFont(hps, ulPointSize, TRUE, // family, not face name pszFaceName, flFormat, &pFont->FontMetrics); else pFont->lcid = LCID_DEFAULT; // 0 GpiSetCharSet(hps, pFont->lcid); if (pFont->FontMetrics.fsDefn & FM_DEFN_OUTLINE) // is outline font: gpihSetPointSize(hps, ulPointSize); for (ul = 0; ul < 256; ul++) ac[ul] = ul; GpiQueryCharStringPosAt(hps, &ptlStart, 0, 254, // starting at one ac + 1, // starting at one NULL, aptl); // now compute width of every char for (ul = 1; ul < 256; ul++) { pFont->alCX[ul] = aptl[ul+1].x - aptl[ul].x; } } /* *@@ SetFormatFont: * creates logical fonts from the specified * font information. * *@@added V0.9.3 (2000-05-06) [umoeller] */ STATIC VOID SetFormatFont(HPS hps, // in: HPS to select default font into PXFMTCHARACTER pxfmtc, // in/out: format data ULONG ulPointSize, // in: font point size (e.g. 12) or 0 PSZ pszFaceName) // in: font face name (e.g. "Courier") or NULL { pxfmtc->lPointSize = ulPointSize; // regular SetSubFont(hps, &pxfmtc->fntRegular, ulPointSize, pszFaceName, 0); // bold SetSubFont(hps, &pxfmtc->fntBold, ulPointSize, pszFaceName, FATTR_SEL_BOLD); // italics SetSubFont(hps, &pxfmtc->fntItalics, ulPointSize, pszFaceName, FATTR_SEL_ITALIC); // bold italics SetSubFont(hps, &pxfmtc->fntBoldItalics, ulPointSize, pszFaceName, FATTR_SEL_BOLD | FATTR_SEL_ITALIC); } /* *@@ AppendCharNoCheck: * *@@added V0.9.3 (2000-05-07) [umoeller] */ STATIC VOID AppendCharNoCheck(char **ppszNew, PULONG pcbNew, char **ppTarget, char c) { ULONG cbSizeThis = *ppTarget - *ppszNew; if (cbSizeThis >= *pcbNew) { // more mem needed: *pcbNew += 10000; *ppszNew = (PSZ)realloc(*ppszNew, *pcbNew); // if first call, pszNew is NULL, and realloc // behaves just like malloc // adjust target, because ptr might have changed *ppTarget = *ppszNew + cbSizeThis; } **ppTarget = c; (*ppTarget)++; } /* *@@ txvStripLinefeeds: * this removes all linefeeds (\r) from * the specified string to prepare it * for display with the text view control. * * This also replaces tabs (\t) with ulTabSize spaces. * * The buffer gets reallocated by this function, so it * must be free()'able. * *@@added V0.9.3 (2000-05-07) [umoeller] *@@changed V0.9.20 (2002-08-10) [umoeller]: now stripping \xFF too *@@changed V1.0.18 (2008-11-24) [pr]: fixes \xFF stripping @@fixes 1116 */ VOID txvStripLinefeeds(char **ppszText, ULONG ulTabSize) { PSZ pSource = *ppszText; ULONG cbNew = 1000; PSZ pszNew = (PSZ)malloc(cbNew); PSZ pTarget = pszNew; ULONG ul; while (*pSource) { switch (*pSource) { case '\r': pSource++; break; case '\t': for (ul = 0; ul < ulTabSize; ul++) AppendCharNoCheck(&pszNew, &cbNew, &pTarget, ' '); // skip the tab pSource++; break; case TXVESC_CHAR: // V0.9.20 (2002-08-10) [umoeller] AppendCharNoCheck(&pszNew, &cbNew, &pTarget, ' '); pSource += 3; // V1.0.18 break; default: AppendCharNoCheck(&pszNew, &cbNew, &pTarget, *pSource++); } } AppendCharNoCheck(&pszNew, &cbNew, &pTarget, '\n'); AppendCharNoCheck(&pszNew, &cbNew, &pTarget, 0); free(*ppszText); *ppszText = pszNew; } /* ****************************************************************** * * Device-independent text formatting * ********************************************************************/ /* *@@ strhFindEOL2: * finds the end of a line. * * An "end of line" means the next \r, \n, or \0 character * after *ppszSearchIn. * * This returns the pointer to that exact character, which * can be equal or higher than *ppszSearchIn. * This should never return NULL because at some point, * there will be a null byte in your string (unless you have * a heap problem). * * If the EOL character is not null (\0), *ppszSearchIN is * advanced to the first character of the _next_ line. This * can be the EOL pointer plus one if you have a UNIX-style * string (\n only at the end of each line) or EOL + 2 for * DOS and OS/2-style EOLs (which have \r\n at the end of * each line). * *@added V0.9.3 (2000-05-06) [umoeller] */ STATIC PSZ strhFindEOL2(PSZ *ppszSearchIn, // in: where to search PULONG pulOffset) // out: offset (ptr can be NULL) { PSZ pThis = *ppszSearchIn, prc = NULL; while (TRUE) { if ( (*pThis == '\r') || (*pThis == '\n') || (*pThis == 0) ) { prc = pThis; break; } pThis++; } // before modifying pointer, store offset if (pulOffset) *pulOffset = prc - *ppszSearchIn; if (*prc == 0) { // null byte (end of string): *ppszSearchIn = prc; } else { // not null byte (end of string): // skip following newline characters if (*prc == '\r') { if ( *(prc+1) == '\n') // we have a \r char next, // that's the DOS and OS/2 format (\r\n): // skip that too *ppszSearchIn = prc + 2; else *ppszSearchIn = prc + 1; } else if (*prc == '\n') // UNIX format (used by HTML formatter also): *ppszSearchIn = prc + 1; } // now: // 1) prc points to the \r, \n, or \0 character (EOL) // 2) *ppszSearchIn has been advanced to the first character // of the next line or points to the \0 character return prc; } /* #define TXVFRECTF_EMPTY 0x0001 #define TXVFRECTF_PARAGRAPHDONE 0x0002 #define TXVFRECTF_WORDSLEFT 0x0004 #define TXVFRECTF_STOPPEDONESCAPE 0x0008 #define TXVFRECTF_ENDOFTEXT 0x0010 */ /* *@@ FORMATLINEBUF: * worker structure to store various data * in txvFormatText in between CreateWord * calls. This has been created for speed * so we don't have to pass all these on * the stack all the time. * *@@added V0.9.3 (2000-05-06) [umoeller] */ typedef struct _FORMATLINEBUF { PSZ pLastChar; // ptr to null terminator in text // formatting data; this is set by txvFormatText according // to escape characters and read by txvCreateRectangle XFMTPARAGRAPH fmtp; PXFMTCHARACTER pfmtc; // pointer to character formatting data PXFMTFONT pfmtf; // pointer to font to use BOOL fPre, fBold, fItalics; // current anchor PCSZ pcszCurrentLinkTarget; // this is != NULL if we're currently in a link block // and points to an item in XFORMATDATA.llLinks // (simply copied to the word structs that are created) // data copied to TXVWORD LONG lcid; LONG lPointSize; ULONG flChar; // any combination of CHS_UNDERSCORE and CHS_STRIKEOUT // counters, ... LONG lXCurrent; // current X position while adding words to rectangle } FORMATLINEBUF, *PFORMATLINEBUF; /* *@@ CreateWord: * * -- If the word ends with one or several spaces, * ppStartOfWord is set to the beginning of the * next word (non-space character). * pWord->ulFlags is set to 0. * * -- If the word ends with an escape character, * ppStartOfWord is set to point to the escape, * which must be handled by the caller. * pWord->ulFlags is set to TXVWORDF_GLUEWITHNEXT. * * -- If the word ends with a \n or \r, * ppStartOfWord is set to the beginning of the * next line (first char after \n or \r). This * may be another \n or \r, but the first one * is skipped. * pWord->ulFlags is set to TXVWORDF_LINEBREAK or * TXVWORDF_LINEFEED. * *@@added V0.9.3 (2000-05-14) [umoeller] *@@changed V0.9.20 (2002-08-10) [umoeller]: rewrote link implementation */ STATIC PTXVWORD CreateWord(HPS hps, PSZ *ppStartOfWord, PFORMATLINEBUF pflbuf) { PTXVWORD pWord = NULL; // find next word: if (**ppStartOfWord) { PSZ pWordStart = *ppStartOfWord, pWordEnd = NULL; PSZ pCheck = *ppStartOfWord; ULONG cChars = 0; // cCheck = 0; pWord = (PTXVWORD)malloc(sizeof(TXVWORD)); memset(pWord, 0, sizeof(TXVWORD)); // this includes fIsEscapeSequence = FALSE; pWord->pStart = pWordStart; // initially, this has pWordStart pointing // to *ppStartOfWord. If a word is found, // pWordStart is set to the first char of // the word and pWordEnd receives the // pointer to the first character after the word (probably space) if (strhGetWord(&pWordStart, // in/out pflbuf->pLastChar, " ", "\x0d\x0a \xFF", // in: end chars; includes our escape! &pWordEnd)) // out: first char after word { // whoa, found a word: while (*pWordEnd == ' ') pWordEnd++; cChars = (pWordEnd - *ppStartOfWord); } if (cChars) { POINTL aptlText[TXTBOX_COUNT]; // cChars is != 0 if strhGetWord succeeded AND the // line is not empty, so go on // cCheck = cChars; // advance input pointer *ppStartOfWord = pWordEnd; GpiQueryTextBox(hps, // no. of chars since start of word: cChars, // first char: pCheck, TXTBOX_COUNT, (PPOINTL)&aptlText); pWord->cChars = cChars; pWord->ulFlags = 0; if (cChars) pWord->ulCXWithSpaces = aptlText[TXTBOX_TOPRIGHT].x; else pWord->ulCXWithSpaces = 0; pWord->ulCY = aptlText[TXTBOX_TOPRIGHT].y - aptlText[TXTBOX_BOTTOMRIGHT].y; // store base line ofs; aptlText[TXTBOX_BOTTOMRIGHT].y is negative // if the string has any characters drawn below the base line, e.g. // for the "g" and "y" characters pWord->ulBaseLineOfs = -aptlText[TXTBOX_BOTTOMRIGHT].y; } else { // no word found or empty line: pWord->ulCY = pflbuf->pfmtf->FontMetrics.lMaxBaselineExt; } switch (**ppStartOfWord) { case TXVESC_CHAR: // '\xFF': pWord->ulFlags = TXVWORDF_GLUEWITHNEXT; break; case '\n': pWord->ulFlags = TXVWORDF_LINEBREAK; (*ppStartOfWord)++; // skip \n break; case '\r': pWord->ulFlags = TXVWORDF_LINEFEED; (*ppStartOfWord)++; // skip \r break; } pWord->lcid = pflbuf->pfmtf->lcid; pWord->lPointSize = pflbuf->lPointSize; pWord->flChar = pflbuf->flChar; pWord->pcszLinkTarget = pflbuf->pcszCurrentLinkTarget; // 0 if none } return pWord; } /* *@@ ProcessEscapes: * gets called when txvFormatText stops on an * escape character (\xFF). This evaluates the * escape sequence, reacts accordingly, and * advances *ppCurrent to after the escape * sequence so that regular processing can * continue. * * There are two types of escape sequences: * * -- Those which are only relevant during word processing, * such as character formatting attributes (bold, italics, * font, size, ...). Those affect the TXVWORD structure * directly and are thus never evaluated in step 2, * rectangles correlation. * * -- Those which affect spacings, margins, etc. (paragraph * formatting). These need to be re-evaluated even during * "quick" format, without words being recalculated, because * those spacings affect the output rectangles. * * If one of those sequences is encountered, this function * appends a special TXVWORD structure to XFORMATDATA.llWords. * *@@added V0.9.3 (2000-05-07) [umoeller] */ STATIC PTXVWORD ProcessEscapes(char **ppCurrent, // in/out: current position; initially points to esc char PXFORMATDATA pxfd, // in/out: formatting data PFORMATLINEBUF pflbuf, // in/out: formatting buffer BOOL fWordsProcessed) // FALSE during step 1 (words processing), // TRUE during step 2 (rectangles correlation) { PTXVWORD pEscapeWord = NULL; // this is set to TRUE either above or by txvCreateRectangle if // an escape character was found; txvCreateRectangle // then sets pCurrent to the escape character (\xFF) CHAR cCode1 = *((*ppCurrent) + 1); CHAR cCode2 = *((*ppCurrent) + 2); ULONG ulSkip = 3; // per default, skip \xFF plus two CHAR szDecimal[10]; LONG lDecimal; BOOL fCreateWord = FALSE, fPaintEscapeWord = FALSE; switch (cCode1) { case 1: // change font: // three decimals follow specifying the font memcpy(szDecimal, (*ppCurrent) + 2, 3); szDecimal[3] = 0; lDecimal = atoi(szDecimal); if (lDecimal == 0) pflbuf->pfmtc = &pxfd->fmtcStandard; else if (lDecimal == 1) pflbuf->pfmtc = &pxfd->fmtcCode; ulSkip = 5; break; case 2: // B or /B if (cCode2 == 1) pflbuf->fBold = TRUE; else pflbuf->fBold = FALSE; break; case 3: // I or /I if (cCode2 == 1) pflbuf->fItalics = TRUE; else pflbuf->fItalics = FALSE; break; case 4: // U or /U if (cCode2 == 1) pflbuf->flChar |= CHS_UNDERSCORE; else pflbuf->flChar &= ~CHS_UNDERSCORE; break; case 5: // STRIKE or /STRIKE if (cCode2 == 1) pflbuf->flChar |= CHS_STRIKEOUT; else pflbuf->flChar &= ~CHS_STRIKEOUT; break; case 6: // A HREF= (link) // changed implementation V0.9.20 (2002-08-10) [umoeller] { // this is variable in length and terminated with // another 0xFF char; what's in between is the // link target name and gets appended to // XFORMATDATA.llLinks PSZ pEnd; if (pEnd = strchr((*ppCurrent) + 2, 0xFF)) { PSZ pszNewLink = strhSubstr((*ppCurrent) + 2, pEnd); lstAppendItem(&pxfd->llLinks, pszNewLink); pflbuf->pcszCurrentLinkTarget = pszNewLink; ulSkip = pEnd - *ppCurrent + 1; } } break; case 7: // /A HREF (end of link) pflbuf->pcszCurrentLinkTarget = NULL; ulSkip = 2; break; case 8: // A NAME= (anchor name) { // this is variable in length and terminated with // another 0xFF char; we completely ignore this // here and just skip the anchor name, this is // only used with TXM_JUMPTOANCHORNAME, which then // searches the buffer PSZ pEnd; if (pEnd = strchr((*ppCurrent) + 2, 0xFF)) { ulSkip = pEnd - *ppCurrent + 1; // store this with the other words so we can // find this word later fCreateWord = TRUE; // and store this with the rectangles fPaintEscapeWord = TRUE; } } break; case 0x10: // relative point size in percent // three characters follow specifying the // percentage memcpy(szDecimal, (*ppCurrent) + 2, 3); szDecimal[3] = 0; lDecimal = atoi(szDecimal); pflbuf->lPointSize = pflbuf->pfmtc->lPointSize * lDecimal / 100; ulSkip = 5; break; case 0x20: // left margin changed: memcpy(szDecimal, (*ppCurrent) + 2, 4); // four decimals xxxx szDecimal[4] = 0; lDecimal = atoi(szDecimal); // this is based on the current average font width, so // find this: pflbuf->fmtp.lLeftMargin = (lDecimal * pflbuf->lPointSize); ulSkip = 6; fCreateWord = TRUE; // for rectangle correlation break; case 0x21: // first line margin changed: memcpy(szDecimal, (*ppCurrent) + 2, 4); // +xxx, -xxx szDecimal[4] = 0; lDecimal = atoi(szDecimal); // this is based on the current average font width, so // find this: pflbuf->fmtp.lFirstLineMargin = (lDecimal * pflbuf->lPointSize); ulSkip = 6; fCreateWord = TRUE; // for rectangle correlation break; case 0x22: // tab: forward current X to left margin pflbuf->lXCurrent = pflbuf->fmtp.lLeftMargin; ulSkip = 2; fCreateWord = TRUE; // for rectangle correlation break; case 0x23: // marker: store this in output, this needs // to be painted fCreateWord = TRUE; fPaintEscapeWord = TRUE; ulSkip = 3; break; case 0x30: // spacing before paragraph: // four chars follow with either "####" or decimal spacing memcpy(szDecimal, (*ppCurrent) + 2, 4); szDecimal[4] = 0; if (memcmp(szDecimal, "####", 4) == 0) // reset to default: pflbuf->fmtp.lSpaceBefore = pxfd->fmtpStandard.lSpaceBefore; else { lDecimal = atoi(szDecimal); pflbuf->fmtp.lSpaceBefore = lDecimal; } ulSkip = 6; fCreateWord = TRUE; // for rectangle correlation break; case 0x31: // spacing before paragraph: // four chars follow with either "####" or decimal spacing memcpy(szDecimal, (*ppCurrent) + 2, 4); szDecimal[4] = 0; if (memcmp(szDecimal, "####", 4) == 0) // reset to default: pflbuf->fmtp.lSpaceAfter = pxfd->fmtpStandard.lSpaceAfter; else { lDecimal = atoi(szDecimal); pflbuf->fmtp.lSpaceAfter = lDecimal; } ulSkip = 6; fCreateWord = TRUE; // for rectangle correlation break; case 0x32: // word-wrapping: // here follows a single char being "0" or "1" if ( *((*ppCurrent) + 2) == '0') pflbuf->fmtp.fWordWrap = FALSE; else pflbuf->fmtp.fWordWrap = TRUE; fCreateWord = TRUE; // for rectangle correlation } if (fCreateWord) // append for rectangle correlation? if (!fWordsProcessed) // are we processing words still (step 1)? { // yes: append to list for rectangle correlation later pEscapeWord = (PTXVWORD)malloc(sizeof(TXVWORD)); memset(pEscapeWord, 0, sizeof(TXVWORD)); // mark as escape sequence pEscapeWord->pStart = *ppCurrent; pEscapeWord->cChars = ulSkip; pEscapeWord->cEscapeCode = *(*ppCurrent + 1); pEscapeWord->fPaintEscapeWord = fPaintEscapeWord; pEscapeWord->pcszLinkTarget = pflbuf->pcszCurrentLinkTarget; // V0.9.20 (2002-08-10) [umoeller] // NULL if none if (fPaintEscapeWord) { pEscapeWord->lX = pflbuf->lXCurrent; pEscapeWord->lcid = pflbuf->pfmtf->lcid; pEscapeWord->lPointSize = pflbuf->lPointSize; pEscapeWord->flChar = pflbuf->flChar; } lstAppendItem(&pxfd->llWords, pEscapeWord); } if (!fWordsProcessed) // if we're still processing words, advance // current pointer by the escape length *ppCurrent += ulSkip; return pEscapeWord; } /* *@@ txvFormatText: * this is the core function to text formatting, which * must be done before the text can be painted into an * HPS. See the top of textview.c for details. * * Even though this function does not seem to have a * lot of parameters, it is extremely powerful. This * function handles paragraph and character formatting * automatically. See XFMTPARAGRAPH and XFMTCHARACTER * for possible formatting attributes, which are part * of the XFORMATDATA structure passed to this function. * * "Formatting" means splitting up any zero-terminated * string (XFORMATDATA.pszViewText) into a possibly * large list of TXVRECTANGLE structures, which each * hold a rectangle to be painted. This allows for * extremely fast painting. * * Each TXVRECTANGLE in turn holds several "words" to * be painted. A word consists of a TXVWORD structure * and is normally a sequence of characters between * spaces, \n and \r characters. As an exception, if * escape sequences come up, such a "word" is split up * into several words because character formatting * (font, size, ...) is done on a per-word basis when painting. * * This approach allows for quicker word-wrapping when only * the output (paint) rectangle is changed because we don't * have to re-calculate all the character widths (TXVWORD) once we * got the words. Instead, when re-formatting, we just recompose * the rectangles based on the words we calculated already. * Of course, when character widths change (e.g. because * fonts are changed), everything has to be redone. * * Processing depends on the current formatting settings * of the XFORMATDATA structure passed to this func and * can become quite complicated: * * -- In the simplest possible formatting mode, that is, if * word wrapping is disabled, each such TXVRECTANGLE * structure will hold one paragraph from the text * (that is, the text between two \n chars). * * -- If word wrapping is enabled, each paragraph in the text * can consist of several such rectangles if the paragraph * does not fit into one line. In that case, we create * one XFMTRECTANGLE for each line which is needed to * display the paragraph word-wrapped. * * This uses an XFORMATDATA structure for input and output * (besides the other parameters). * * On input, specify the following: * * -- hps: window or printer HPS. This is used for * formatting only, but nothing is painted. * * -- XFORMATDATA.pszViewText: the text to be formatted. * This must follow certain conventions; the \xFF, * \r, and \n characters have a special meaning. * See the top of textview.c for details. * * -- XFORMATDATA.fmtpStandard, fmtcStandard, fmtcCode: * paragraph and character formatting attributes. * For the simplest possible formatting, memset * all these to 0. Word-wrapping depends on * the paragraph formats. * * -- prclView: rectangle for which formatting should take * place. When this is called for a screen window, * this should be the visible area of the window * (WinQueryWindowRect). * When this is called with a printer PS, this should * be the size of a printer page. * * This function updates the following: * * -- XFORMATDATA.llWords: list of TXVWORD structures, * holding all the "words" in the text as described * above. This list can grow very long, but only needs * to be recalculated when fonts change. * * -- XFORMATDATA.llRectangles: list of TXVRECTANGLE * structures, correlating the words on the words list * to paint rectangles. * * -- XFORMATDATA.szlWorkspace: total width * and height of the "workspace", i.e. the total space * needed to display the text (in pels). This might * be smaller, the same, or larger than prclView, * depending on whether the text fits into prclView. * * When displaying text, you should display scroll bars * if the workspace is larger than the window (prclView). * * When printing, if the workspace is larger than the * printer page (prclView), you will need to call * txvPaintText several times for each page. * * All coordinates are in world space (PU_PELS). * *@@changed V0.9.3 (2000-05-06) [umoeller]: largely rewritten; now handling paragraph and character formats *@@changed V1.0.18 (2008-11-16) [pr]: bodge formatting to remove unwanted scroll bars @@fixes 1086 *@@todo TXVWORDF_GLUEWITHNEXT */ VOID txvFormatText(HPS hps, // in: HPS whose font is used for // calculating text dimensions PXFORMATDATA pxfd, // in: formatting data PRECTL prclView, // in: rectangle to format for (window or printer page) BOOL fFullRecalc) // in: re-calculate word list too? (must be TRUE on the first call) { /* ULONG ulWinCX = (prclView->xRight - prclView->xLeft), ulWinCY = (prclView->yTop - prclView->yBottom); */ lstClear(&pxfd->llRectangles); if (fFullRecalc) lstClear(&pxfd->llWords); pxfd->szlWorkspace.cx = 0; pxfd->szlWorkspace.cy = 0; if (pxfd->strViewText.cbAllocated) { ULONG ulTextLen = pxfd->strViewText.ulLength; FORMATLINEBUF flbuf; LONG lcidLast = -99, lPointSizeLast = -99; memset(&flbuf, 0, sizeof(flbuf)); // copy default paragraph formatting memcpy(&flbuf.fmtp, &pxfd->fmtpStandard, sizeof(flbuf.fmtp)); // set font flbuf.pfmtc = &pxfd->fmtcStandard; flbuf.lPointSize = pxfd->fmtcStandard.lPointSize; flbuf.pLastChar = pxfd->strViewText.psz + ulTextLen; if (ulTextLen) { ULONG cWords = 0; if (fFullRecalc) { /* * step 1: create words * */ PSZ pCurrent = pxfd->strViewText.psz; // loop until null terminator while (*pCurrent) { PTXVWORD pWord; if (flbuf.fBold) { if (flbuf.fItalics) flbuf.pfmtf = &flbuf.pfmtc->fntBoldItalics; else flbuf.pfmtf = &flbuf.pfmtc->fntBold; } else if (flbuf.fItalics) flbuf.pfmtf = &flbuf.pfmtc->fntItalics; else flbuf.pfmtf = &flbuf.pfmtc->fntRegular; // set font for subsequent calculations, // if changed (this includes the first call) if (lcidLast != flbuf.pfmtf->lcid) { GpiSetCharSet(hps, flbuf.pfmtf->lcid); lcidLast = flbuf.pfmtf->lcid; // force recalc of point size lPointSizeLast = -99; } if (lPointSizeLast != flbuf.lPointSize) { if (flbuf.pfmtf->FontMetrics.fsDefn & FM_DEFN_OUTLINE) // is outline font: gpihSetPointSize(hps, flbuf.lPointSize); lPointSizeLast = flbuf.lPointSize; } if (pWord = CreateWord(hps, &pCurrent, // advanced to next word &flbuf)) { lstAppendItem(&pxfd->llWords, pWord); /* { CHAR szWord[3000]; strhncpy0(szWord, pWord->pStart, min(pWord->cChars, sizeof(szWord))); _Pmpf(("Found word '%s'", szWord)); } */ cWords++; while (*pCurrent == TXVESC_CHAR) // '\xFF') { // handle escapes; // this advances pCurrent depending on the // escape sequence length and might append // another "word" for the escape sequence // if it's relevant for rectangle correlation ProcessEscapes(&pCurrent, pxfd, &flbuf, FALSE); // fWordsProcessed } } else break; } } // end if (fFullRecalc) else cWords = lstCountItems(&pxfd->llWords); /* * step 2: create rectangles * */ if (cWords) { PLISTNODE pWordNode = lstQueryFirstNode(&pxfd->llWords); LONG lCurrentYTop = prclView->yTop, lOrigYTop = lCurrentYTop; BOOL fRects2Go = TRUE; // space before paragraph; this is reset // to 0 if we start a new rectangle for // the same paragraph ULONG ulYPre = flbuf.fmtp.lSpaceBefore; // rectangles loop while (fRects2Go) { BOOL fWords2Go = TRUE; ULONG ulWordsInThisRect = 0; // maximum height of words in this rect ULONG lWordsMaxCY = 0; // start a new rectangle: PTXVRECTANGLE pRect = (PTXVRECTANGLE)malloc(sizeof(TXVRECTANGLE)); lstInit(&pRect->llWords, FALSE); // no auto-free; the words are stored in the main // list also, which is freed // rectangle's xLeft; // xRight will be set when we're done with this rectangle pRect->rcl.xLeft = prclView->xLeft + flbuf.fmtp.lLeftMargin; if (ulYPre) // starting new paragraph: // add first-line offset also pRect->rcl.xLeft += flbuf.fmtp.lFirstLineMargin; // current X pos: start with left of rectangle flbuf.lXCurrent = pRect->rcl.xLeft; // max baseline ofs: set to 0, this will be raised pRect->ulMaxBaseLineOfs = 0; // words-per-rectangle loop; // we keep adding words to the rectangle until // a) words no longer fit and word-wrapping is on; // b) a newline or line feed is found; // c) the last word has been reached; while (fWords2Go) { PTXVWORD pWordThis = (PTXVWORD)pWordNode->pItemData; /* #define TXVWORDF_GLUEWITHNEXT 1 // escape #define TXVWORDF_LINEBREAK 2 // \n #define TXVWORDF_LINEFEED 4 // \r */ BOOL fNextWord = FALSE; if (pWordThis->cEscapeCode) { // pseudo-word for escape sequence: // process... ProcessEscapes((PSZ*)&pWordThis->pStart, pxfd, &flbuf, TRUE); // append this sequence only if it's needed // for painting (list markers etc.) if (pWordThis->fPaintEscapeWord) { pWordThis->lX = flbuf.lXCurrent; pWordThis->pRectangle = pRect; lstAppendItem(&pRect->llWords, pWordThis); ulWordsInThisRect++; } fNextWord = TRUE; } else { BOOL fWordWrapped = FALSE; // not escape sequence, but real word: format... // is word-wrapping on? if (flbuf.fmtp.fWordWrap) { // yes: check if the word still fits // WarpIN V1.0.18 @@todo add fudge factor of 2 - makes things work if ( (flbuf.lXCurrent + pWordThis->ulCXWithSpaces + 2 > prclView->xRight) // > ulWinCX) // but always add the first word in the rectangle, // because otherwise we get infinite loops && (ulWordsInThisRect > 0) ) // no: fWordWrapped = TRUE; } if (fWordWrapped) // start a new rectangle with the current word: fWords2Go = FALSE; // and do _not_ advance to the next word, // but start with this word for the next // rectangle... else { // add this word to the rectangle: // store current X pos in word pWordThis->lX = flbuf.lXCurrent; // increase current X pos by word width flbuf.lXCurrent += pWordThis->ulCXWithSpaces; // store word in rectangle pWordThis->pRectangle = pRect; lstAppendItem(&pRect->llWords, pWordThis); // @@todo memory leak right here!!! ulWordsInThisRect++; // store highest word width found for this rect if (pWordThis->ulCY > lWordsMaxCY) lWordsMaxCY = pWordThis->ulCY; // store highest base line ofs found for this rect if (pWordThis->ulBaseLineOfs > pRect->ulMaxBaseLineOfs) pRect->ulMaxBaseLineOfs = pWordThis->ulBaseLineOfs; // go for next word in any case fNextWord = TRUE; } // end if (!fBreakThisWord) // now check: add more words to this rectangle? if ( (pWordThis->ulFlags == TXVWORDF_LINEBREAK) // no if linebreak found || (pWordThis->ulFlags == TXVWORDF_LINEFEED) // no if linefeed found || (!fWords2Go) // no if we're out of words or // word-break was forced ) { // no: finish up this rectangle... // xLeft has been set on top pRect->rcl.xRight = flbuf.lXCurrent; pRect->rcl.yTop = lCurrentYTop - ulYPre; pRect->rcl.yBottom = pRect->rcl.yTop - lWordsMaxCY; // decrease current y top for next line lCurrentYTop = pRect->rcl.yBottom; if (!fRects2Go) // we're done completely: // add another one lCurrentYTop -= lWordsMaxCY; if (fWordWrapped) // starting with wrapped word in next line: ulYPre = 0; else if (pWordThis->ulFlags == TXVWORDF_LINEFEED) ulYPre = 0; else if (pWordThis->ulFlags == TXVWORDF_LINEBREAK) { // line break: // set y-pre for next loop ulYPre = flbuf.fmtp.lSpaceBefore; // and add paragraph post-y lCurrentYTop -= flbuf.fmtp.lSpaceAfter; } // update x extents if (pRect->rcl.xRight > pxfd->szlWorkspace.cx) pxfd->szlWorkspace.cx = pRect->rcl.xRight; // and quit the inner loop fWords2Go = FALSE; } // end finish up rectangle } // end else if (pWordThis->fIsEscapeSequence) if (fNextWord) { pWordNode = pWordNode->pNext; if (!pWordNode) { // no more to go: // quit fWords2Go = FALSE; fRects2Go = FALSE; } } } // end while (fWords2Go) // store rectangle lstAppendItem(&pxfd->llRectangles, pRect); } // lCurrentYTop now has the bottommost point we've used; // store this as workspace (this might be negative) pxfd->szlWorkspace.cy = lOrigYTop - lCurrentYTop; } } } } /* ****************************************************************** * * Device-independent text painting * ********************************************************************/ /* *@@ DrawListMarker: * *@@added V0.9.3 (2000-05-17) [umoeller] */ STATIC VOID DrawListMarker(HPS hps, PRECTL prclLine, // current line rectangle PTXVWORD pWordThis, // current word LONG lViewXOfs) // in: x offset to paint; 0 means rightmost { POINTL ptl; ULONG ulBulletSize = pWordThis->lPointSize * 2 / 3; // 2/3 of point size ARCPARAMS arcp = {1, 1, 0, 0}; // pWordThis->pStart points to the \xFF character; // next is the "marker" escape (\x23), // next is the marker type CHAR cBulletType = *((pWordThis->pStart) + 2) ; switch (cBulletType) { case 2: // square (filled box) ptl.x = pWordThis->lX - lViewXOfs; // center bullet vertically ptl.y = prclLine->yBottom + ( (prclLine->yTop - prclLine->yBottom) // height - ulBulletSize ) / 2; GpiMove(hps, &ptl); ptl.x += ulBulletSize; ptl.y += ulBulletSize; GpiBox(hps, DRO_FILL, &ptl, 0, 0); break; default: // case 1: // disc (filled circle) ptl.x = pWordThis->lX - lViewXOfs; // center bullet vertically; // the arc is drawn with the current position in its center ptl.y = prclLine->yBottom + ( (prclLine->yTop - prclLine->yBottom) // height / 2 ); GpiSetArcParams(hps, &arcp); GpiMove(hps, &ptl); GpiFullArc(hps, (cBulletType == 3) ? DRO_OUTLINE : DRO_FILL, MAKEFIXED(ulBulletSize / 2, // radius! 0)); break; } } /* *@@ txvPaintText: * device-independent function for painting. * This can only be called after the text has * been formatted (using txvFormatText). * * This only paints rectangles which are within * prcl2Paint. * * -- For WM_PAINT, set this to the * update rectangle, and set fPaintHalfLines * to TRUE. * * -- For printing, set this to the page rectangle, * and set fPaintHalfLines to FALSE. * * All coordinates are in world space (PU_PELS). * *@@changed V0.9.3 (2000-05-05) [umoeller]: fixed wrong visible lines calculations; great speedup painting! *@@changed V0.9.3 (2000-05-06) [umoeller]: now using gpihCharStringPosAt */ BOOL txvPaintText(HAB hab, HPS hps, // in: window or printer PS PXFORMATDATA pxfd, PRECTL prcl2Paint, // in: invalid rectangle to be drawn, // can be NULL to paint all LONG lViewXOfs, // in: x offset to paint; 0 means rightmost PULONG pulViewYOfs, // in: y offset to paint; 0 means _top_most; // out: y offset which should be passed to next call // (if TRUE is returned and fPaintHalfLines == FALSE) BOOL fPaintHalfLines, // in: if FALSE, lines which do not fully fit on // the page are dropped (useful for printing) PULONG pulLineIndex) // in: line to start painting with; // out: next line to paint, if any // (if TRUE is returned and fPaintHalfLines == FALSE) { BOOL brc = FALSE, fAnyLinesPainted = FALSE; ULONG ulCurrentLineIndex = *pulLineIndex; // LONG lViewYOfsSaved = *pulViewYOfs; PLISTNODE pRectNode = lstNodeFromIndex(&pxfd->llRectangles, ulCurrentLineIndex); LONG lcidLast = -99; LONG lPointSizeLast = -99; while (pRectNode) { PTXVRECTANGLE pLineRcl = (PTXVRECTANGLE)pRectNode->pItemData; BOOL fPaintThis = FALSE; // compose rectangle to draw for this line RECTL rclLine; rclLine.xLeft = pLineRcl->rcl.xLeft - lViewXOfs; rclLine.xRight = pLineRcl->rcl.xRight - lViewXOfs; rclLine.yBottom = pLineRcl->rcl.yBottom + *pulViewYOfs; rclLine.yTop = pLineRcl->rcl.yTop + *pulViewYOfs; /* if (pmpf) { CHAR szTemp[100]; ULONG cb = min(pLineRcl->cLineChars, 99); strhncpy0(szTemp, pLineRcl->pStartOfLine, cb); _Pmpf(("Checking line %d: '%s'", ulCurrentLineIndex, szTemp)); _Pmpf((" (yB stored %d -> in HPS %d against win yB %d)", pLineRcl->rcl.yBottom, rclLine.yBottom, prcl2Paint->yBottom)); } */ if (prcl2Paint == NULL) // draw all: fPaintThis = TRUE; else { BOOL fBottomInPaint = ( (rclLine.yBottom >= prcl2Paint->yBottom) && (rclLine.yBottom <= prcl2Paint->yTop) ); BOOL fTopInPaint = ( (rclLine.yTop >= prcl2Paint->yBottom) && (rclLine.yTop <= prcl2Paint->yTop) ); if ((fBottomInPaint) && (fTopInPaint)) // both in update rect: fPaintThis = TRUE; else if (fPaintHalfLines) { if ((fBottomInPaint) || (fTopInPaint)) // only one in update rect: fPaintThis = TRUE; else // now, for very small update rectangles, // especially with slow scrolling, // we can have the case that the paint rectangle // is only a few pixels high so that the top of // the line is above the repaint, and the bottom // of the line is below it! if ( (rclLine.yTop >= prcl2Paint->yTop) && (rclLine.yBottom <= prcl2Paint->yBottom) ) fPaintThis = TRUE; } } if (fPaintThis) { // rectangle invalid: paint this rectangle // by going thru the member words PLISTNODE pWordNode = lstQueryFirstNode(&pLineRcl->llWords); POINTL ptlStart; while (pWordNode) { PTXVWORD pWordThis = (PTXVWORD)pWordNode->pItemData; ULONG flChar = pWordThis->flChar; if (pWordThis->pcszLinkTarget) // V0.9.20 (2002-08-10) [umoeller] flChar |= CHS_UNDERSCORE; // x start: this word's X coordinate ptlStart.x = pWordThis->lX - lViewXOfs; // y start: bottom line of rectangle plus highest // base line offset found in all words (format step 2) ptlStart.y = rclLine.yBottom + pLineRcl->ulMaxBaseLineOfs; // pWordThis->ulBaseLineOfs; // set font for subsequent calculations, // if changed (this includes the first call) if (lcidLast != pWordThis->lcid) { GpiSetCharSet(hps, pWordThis->lcid); lcidLast = pWordThis->lcid; // force recalc of point size lPointSizeLast = -99; } if (lPointSizeLast != pWordThis->lPointSize) { if (pWordThis->lPointSize) // is outline font: gpihSetPointSize(hps, pWordThis->lPointSize); lPointSizeLast = pWordThis->lPointSize; } if (!pWordThis->cEscapeCode) // regular word: gpihCharStringPosAt(hps, &ptlStart, &rclLine, flChar, pWordThis->cChars, (PSZ)pWordThis->pStart); else { // check escape code switch (pWordThis->cEscapeCode) { case 0x23: // escape to be painted: DrawListMarker(hps, &rclLine, pWordThis, lViewXOfs); break; } } // ptlStart.x += pWordThis->ulCXWithSpaces; fAnyLinesPainted = TRUE; pWordNode = pWordNode->pNext; } /* { LONG lColor = GpiQueryColor(hps); POINTL ptl2; GpiSetColor(hps, RGBCOL_RED); ptl2.x = rclLine.xLeft; ptl2.y = rclLine.yBottom; GpiMove(hps, &ptl2); ptl2.x = rclLine.xRight; ptl2.y = rclLine.yTop; GpiBox(hps, DRO_OUTLINE, &ptl2, 0, 0); GpiSetColor(hps, lColor); } */ } else { // this line is no longer fully visible: if (fAnyLinesPainted) { // we had painted lines already: // this means that all the following lines are // too far below the window, so quit /* if (pmpf) _Pmpf(("Quitting with line %d (xL = %d yB = %d)", ulCurrentLineIndex, rclLine.xLeft, rclLine.yBottom)); */ *pulLineIndex = ulCurrentLineIndex; if (pRectNode->pNext) { // another line to paint: PTXVRECTANGLE pLineRcl2 = (PTXVRECTANGLE)pRectNode->pNext->pItemData; // return TRUE brc = TRUE; // and set *pulViewYOfs to the top of // the next line, which wasn't visible // on the page any more *pulViewYOfs = pLineRcl2->rcl.yTop + *pulViewYOfs; } break; } // else no lines painted yet: // go for next node, because we're still above the visible window } // next line pRectNode = pRectNode->pNext; // raise index to return ulCurrentLineIndex++; } if (!fAnyLinesPainted) brc = FALSE; return brc; } /* *@@ txvFindWordFromPoint: * returns the list node of the word under the * given point. The list node is from the global * words list in pxfd. * *@@added V0.9.3 (2000-05-18) [umoeller] */ PLISTNODE txvFindWordFromPoint(PXFORMATDATA pxfd, PPOINTL pptl) { PLISTNODE pWordNodeFound = NULL; PLISTNODE pRectangleNode = lstQueryFirstNode(&pxfd->llRectangles); while ((pRectangleNode) && (!pWordNodeFound)) { PTXVRECTANGLE prclThis = (PTXVRECTANGLE)pRectangleNode->pItemData; if ( (pptl->x >= prclThis->rcl.xLeft) && (pptl->x <= prclThis->rcl.xRight) && (pptl->y >= prclThis->rcl.yBottom) && (pptl->y <= prclThis->rcl.yTop) ) { // cool, we found the rectangle: // now go thru the words in this rectangle PLISTNODE pWordNode = lstQueryFirstNode(&prclThis->llWords); while (pWordNode) { PTXVWORD pWordThis = (PTXVWORD)pWordNode->pItemData; if ( (pptl->x >= pWordThis->lX) && (pptl->x <= pWordThis->lX + pWordThis->ulCXWithSpaces) ) { pWordNodeFound = pWordNode; break; } pWordNode = pWordNode->pNext; } } pRectangleNode = pRectangleNode->pNext; } return pWordNodeFound; } /* *@@ txvFindWordFromAnchor: * returns the list node from the global words list * BEFORE the word which represents the escape sequence * containing the specified anchor name. * *@@added V0.9.4 (2000-06-12) [umoeller] */ PLISTNODE txvFindWordFromAnchor(PXFORMATDATA pxfd, const char *pszAnchorName) { PLISTNODE pNodeFound = NULL; ULONG cbAnchorName = strlen(pszAnchorName); PLISTNODE pWordNode = lstQueryFirstNode(&pxfd->llWords); while ((pWordNode) && (!pNodeFound)) { PTXVWORD pWordThis = (PTXVWORD)pWordNode->pItemData; if (pWordThis->cEscapeCode == 7) { // this word is an anchor escape sequence: if (strnicmp(pszAnchorName, (pWordThis->pStart + 2), cbAnchorName) == 0) { // matches: check length if (*(pWordThis->pStart + 2 + cbAnchorName) == (char)0xFF) // OK: pNodeFound = pWordNode; } } pWordNode = pWordNode ->pNext; } if (pNodeFound) { // anchor found: // go backwords in word list until we find a "real" word // which is no escape sequence while (pNodeFound) { PTXVWORD pWordThis = (PTXVWORD)pNodeFound->pItemData; if (pWordThis->cEscapeCode) pNodeFound = pNodeFound->pPrevious; else break; } } return pNodeFound; } /* ****************************************************************** * * Window-dependent functions * ********************************************************************/ #define QWL_PRIVATE 4 // V0.9.20 (2002-08-10) [umoeller] /* *@@ TEXTVIEWWINDATA: * view control-internal structure, stored in * QWL_PRIVATE at fnwpTextView. * This is device-dependent on the text view * window. */ typedef struct _TEXTVIEWWINDATA { HAB hab; // anchor block (for speed) HDC hdc; HPS hps; ULONG flStyle; // window style flags copied on WM_CREATE // V0.9.20 (2002-08-10) [umoeller] LONG lBackColor, lForeColor; XTEXTVIEWCDATA cdata; // control data, as passed to WM_CREATE XFORMATDATA xfd; HWND hwndVScroll, // vertical scroll bar hwndHScroll; // horizontal scroll bar BOOL fVScrollVisible, // TRUE if vscroll is currently used fHScrollVisible; // TRUE if hscroll is currently used RECTL rclViewReal, // window rect as returned by WinQueryWindowRect // (top right point is inclusive!) rclViewPaint, // same as rclViewReal, but excluding scroll bars rclViewText; // same as rclViewPaint, but excluding cdata borders ULONG ulViewXOfs, // pixels that we have scrolled to the RIGHT; 0 means very left ulViewYOfs; // pixels that we have scrolled to the BOTTOM; 0 means very top BOOL fAcceptsPresParamsNow; // TRUE after first WM_PAINT // anchor clicking PLISTNODE pWordNodeFirstInAnchor; // points to first word which belongs to anchor // USHORT usLastAnchorClicked; // last anchor which was clicked (1-0xFFFF) PCSZ pcszLastLinkClicked; // last link that was clicked (points into llLinks) // V0.9.20 (2002-08-10) [umoeller] } TEXTVIEWWINDATA, *PTEXTVIEWWINDATA; #define ID_VSCROLL 100 #define ID_HSCROLL 101 /* *@@ UpdateTextViewPresData: * called from WM_CREATE and WM_PRESPARAMCHANGED * in fnwpTextView to update the TEXTVIEWWINDATA * from the window's presparams. This calls * txvSetDefaultFormat in turn. */ STATIC VOID UpdateTextViewPresData(HWND hwndTextView, PTEXTVIEWWINDATA ptxvd) { PSZ pszFont; ptxvd->lBackColor = winhQueryPresColor(hwndTextView, PP_BACKGROUNDCOLOR, FALSE, // no inherit SYSCLR_DIALOGBACKGROUND); ptxvd->lForeColor = winhQueryPresColor(hwndTextView, PP_FOREGROUNDCOLOR, FALSE, // no inherit SYSCLR_WINDOWSTATICTEXT); if ((pszFont = winhQueryWindowFont(hwndTextView))) { ULONG ulSize; PSZ pszFaceName; // _Pmpf(("font: %s", pszFont)); if (gpihSplitPresFont(pszFont, &ulSize, &pszFaceName)) { SetFormatFont(ptxvd->hps, &ptxvd->xfd.fmtcStandard, ulSize, pszFaceName); } free(pszFont); } } /* *@@ AdjustViewRects: * updates the internal size-dependent structures * and positions the scroll bars. * * This is device-dependent for the text view * control and must be called before FormatText2Screen * so that the view rectangles get calculated right. * * Required input in TEXTVIEWWINDATA: * * -- rclViewReal: the actual window dimensions. * * -- cdata: control data. * * Output from this function in TEXTVIEWWINDATA: * * -- rclViewPaint: the paint subrectangle (which * is rclViewReal minus scrollbars, if any). * * -- rclViewText: the text subrectangle (which * is rclViewPaint minus borders). * *@@changed WarpIN V1.0.18 (2008-11-16) [pr]: fix cut/paste/typo. errors @@fixes 1086 */ STATIC VOID AdjustViewRects(HWND hwndTextView, PTEXTVIEWWINDATA ptxvd) { ULONG ulScrollCX = WinQuerySysValue(HWND_DESKTOP, SV_CXVSCROLL), ulScrollCY = WinQuerySysValue(HWND_DESKTOP, SV_CYHSCROLL), ulOfs; // calculate rclViewPaint: // 1) left ptxvd->rclViewPaint.xLeft = ptxvd->rclViewReal.xLeft; // 2) bottom ptxvd->rclViewPaint.yBottom = ptxvd->rclViewReal.yBottom; if (ptxvd->fHScrollVisible) // if we have a horizontal scroll bar at the bottom, // raise bottom by its height ptxvd->rclViewPaint.yBottom += ulScrollCY; // 3) right ptxvd->rclViewPaint.xRight = ptxvd->rclViewReal.xRight; if (ptxvd->fVScrollVisible) // if we have a vertical scroll bar at the right, // subtract its width from the right ptxvd->rclViewPaint.xRight -= ulScrollCX; ptxvd->rclViewPaint.yTop = ptxvd->rclViewReal.yTop; // calculate rclViewText from that ptxvd->rclViewText.xLeft = ptxvd->rclViewPaint.xLeft + ptxvd->cdata.ulXBorder; ptxvd->rclViewText.yBottom = ptxvd->rclViewPaint.yBottom + ptxvd->cdata.ulYBorder; ptxvd->rclViewText.xRight = ptxvd->rclViewPaint.xRight - ptxvd->cdata.ulXBorder; ptxvd->rclViewText.yTop = ptxvd->rclViewPaint.yTop - ptxvd->cdata.ulYBorder; // WarpIN V1.0.18 // now reposition scroll bars; their sizes may change // if either the vertical or horizontal scroll bar has // popped up or been hidden if (ptxvd->flStyle & XS_VSCROLL) { // vertical scroll bar enabled: ulOfs = 0; if (ptxvd->fHScrollVisible) ulOfs = ulScrollCY; // WarpIN V1.0.18 WinSetWindowPos(ptxvd->hwndVScroll, HWND_TOP, ptxvd->rclViewReal.xRight - ulScrollCX, ulOfs, // y ulScrollCX, // cx ptxvd->rclViewReal.yTop - ulOfs, // cy SWP_MOVE | SWP_SIZE); } if (ptxvd->flStyle & XS_HSCROLL) { ulOfs = 0; if (ptxvd->fVScrollVisible) ulOfs = ulScrollCX; WinSetWindowPos(ptxvd->hwndHScroll, HWND_TOP, 0, 0, ptxvd->rclViewReal.xRight - ulOfs, // cx ulScrollCY, // cy SWP_MOVE | SWP_SIZE); } } /* *@@ FormatText2Screen: * device-dependent version of text formatting * for the text view window. This calls txvFormatText * in turn and updates the view's scroll bars. * *@@changed V0.9.3 (2000-05-05) [umoeller]: fixed buggy vertical scroll bars *@@changed WarpIN V1.0.18 (2008-11-16) [pr]: fix buggy horiz. scroll bars @@fixes 1086 */ STATIC VOID FormatText2Screen(HWND hwndTextView, PTEXTVIEWWINDATA ptxvd, BOOL fAlreadyRecursing, // in: set this to FALSE when calling BOOL fFullRecalc) { ULONG ulWinCX, ulWinCY; // call device-independent formatter with the // window presentation space txvFormatText(ptxvd->hps, &ptxvd->xfd, &ptxvd->rclViewText, fFullRecalc); ulWinCY = (ptxvd->rclViewText.yTop - ptxvd->rclViewText.yBottom); if (ptxvd->ulViewYOfs < 0) ptxvd->ulViewYOfs = 0; if (ptxvd->ulViewYOfs > ((LONG)ptxvd->xfd.szlWorkspace.cy - ulWinCY)) ptxvd->ulViewYOfs = (LONG)ptxvd->xfd.szlWorkspace.cy - ulWinCY; // vertical scroll bar enabled at all? if (ptxvd->flStyle & XS_VSCROLL) { BOOL fEnabled = winhUpdateScrollBar(ptxvd->hwndVScroll, ulWinCY, ptxvd->xfd.szlWorkspace.cy, ptxvd->ulViewYOfs, (ptxvd->flStyle & XS_AUTOVHIDE)); // is auto-hide on? if (ptxvd->flStyle & XS_AUTOVHIDE) { // yes, auto-hide on: did visibility change? if (fEnabled != ptxvd->fVScrollVisible) // visibility changed: // if we're not already recursing, // force calling ourselves again if (!fAlreadyRecursing) { ptxvd->fVScrollVisible = fEnabled; AdjustViewRects(hwndTextView, ptxvd); FormatText2Screen(hwndTextView, ptxvd, TRUE, // fAlreadyRecursing FALSE); // quick format } } } ulWinCX = (ptxvd->rclViewText.xRight - ptxvd->rclViewText.xLeft); // horizontal scroll bar enabled at all? if (ptxvd->flStyle & XS_HSCROLL) { BOOL fEnabled = winhUpdateScrollBar(ptxvd->hwndHScroll, ulWinCX, ptxvd->xfd.szlWorkspace.cx, ptxvd->ulViewXOfs, (ptxvd->flStyle & XS_AUTOHHIDE)); // is auto-hide on? if (ptxvd->flStyle & XS_AUTOHHIDE) { // yes, auto-hide on: did visibility change? if (fEnabled != ptxvd->fHScrollVisible) // visibility changed: // if we're not already recursing, // force calling ourselves again (at the bottom) if (!fAlreadyRecursing) { ptxvd->fHScrollVisible = fEnabled; AdjustViewRects(hwndTextView, ptxvd); FormatText2Screen(hwndTextView, // WarpIN V1.0.18 ptxvd, TRUE, // fAlreadyRecursing FALSE); // quick format } } } WinInvalidateRect(hwndTextView, NULL, FALSE); } /* *@@ SetWindowText: * implementation for WM_SETWINDOWPARAMS and * also WM_CREATE to set the window text. * *@@added V0.9.20 (2002-08-10) [umoeller] *@@changed WarpIN V1.0.18 (2008-11-29) [pr] */ VOID SetWindowText(HWND hwndTextView, PTEXTVIEWWINDATA ptxvd, PCSZ pcszText) { if (pcszText && *pcszText) { PXSTRING pstr = &ptxvd->xfd.strViewText; PSZ p; // WarpIN V1.0.18 xstrcpy(&ptxvd->xfd.strOrigText, pcszText, 0); switch (ptxvd->flStyle & XS_FORMAT_MASK) { case XS_PLAINTEXT: // 0x0100 // WarpIN V1.0.18 if (p = strdup(pcszText)) { PSZ p2; for (p2 = p; p2 = strchr(p2, TXVESC_CHAR); *p2 = ' '); txvStripLinefeeds(&p, 4); xstrset(pstr, p); } break; case XS_HTML: // 0x0200 // WarpIN V1.0.18 if (p = strdup(pcszText)) { PSZ p2; for (p2 = p; p2 = strchr(p2, TXVESC_CHAR); *p2 = ' '); txvConvertFromHTML(&p, NULL, NULL, NULL); xstrset(pstr, p); xstrConvertLineFormat(pstr, CRLF2LF); } break; default: // case XS_PREFORMATTED: // 0x0000 // no conversion (default) xstrcpy(pstr, pcszText, 0); break; } // if the last character of the window text is not "\n", // add it explicitly here, or our lines processing // is being funny // V0.9.20 (2002-08-10) [umoeller] if (pstr->psz[pstr->ulLength - 1] != '\n') xstrcatc(pstr, '\n'); ptxvd->ulViewXOfs = 0; ptxvd->ulViewYOfs = 0; AdjustViewRects(hwndTextView, ptxvd); FormatText2Screen(hwndTextView, ptxvd, FALSE, TRUE); // full format } } /* *@@ PaintViewText2Screen: * device-dependent version of text painting * for the text view window. This calls txvPaintText * in turn and updates the view's scroll bars. */ STATIC VOID PaintViewText2Screen(PTEXTVIEWWINDATA ptxvd, PRECTL prcl2Paint) // in: invalid rectangle, can be NULL == paint all { ULONG ulLineIndex = 0; ULONG ulYOfs = ptxvd->ulViewYOfs; txvPaintText(ptxvd->hab, ptxvd->hps, // paint PS: screen &ptxvd->xfd, // formatting data prcl2Paint, // update rectangle given to us ptxvd->ulViewXOfs, // current X scrolling offset &ulYOfs, // current Y scrolling offset TRUE, // draw even partly visible lines &ulLineIndex); } /* *@@ PaintViewFocus: * paint a focus rectangle. */ STATIC VOID PaintViewFocus(HPS hps, PTEXTVIEWWINDATA ptxvd, BOOL fFocus) { POINTL ptl; HRGN hrgn; GpiSetClipRegion(hps, NULLHANDLE, &hrgn); GpiSetColor(hps, (fFocus) ? WinQuerySysColor(HWND_DESKTOP, SYSCLR_HILITEBACKGROUND, 0) : ptxvd->lBackColor); GpiSetLineType(hps, LINETYPE_DOT); ptl.x = ptxvd->rclViewPaint.xLeft; ptl.y = ptxvd->rclViewPaint.yBottom; GpiMove(hps, &ptl); ptl.x = ptxvd->rclViewPaint.xRight - 1; ptl.y = ptxvd->rclViewPaint.yTop - 1; GpiBox(hps, DRO_OUTLINE, &ptl, 0, 0); } /* *@@ RepaintWord: * *@@added V0.9.3 (2000-05-18) [umoeller] */ STATIC VOID RepaintWord(PTEXTVIEWWINDATA ptxvd, PTXVWORD pWordThis, LONG lColor) { POINTL ptlStart; ULONG flChar = pWordThis->flChar; PTXVRECTANGLE pLineRcl = pWordThis->pRectangle; RECTL rclLine; rclLine.xLeft = pLineRcl->rcl.xLeft - ptxvd->ulViewXOfs; rclLine.xRight = pLineRcl->rcl.xRight - ptxvd->ulViewXOfs; rclLine.yBottom = pLineRcl->rcl.yBottom + ptxvd->ulViewYOfs; rclLine.yTop = pLineRcl->rcl.yTop + ptxvd->ulViewYOfs; if (pWordThis->pcszLinkTarget) flChar |= CHS_UNDERSCORE; // x start: this word's X coordinate ptlStart.x = pWordThis->lX - ptxvd->ulViewXOfs; // y start: bottom line of rectangle plus highest // base line offset found in all words (format step 2) ptlStart.y = rclLine.yBottom + pLineRcl->ulMaxBaseLineOfs; // pWordThis->ulBaseLineOfs; GpiSetCharSet(ptxvd->hps, pWordThis->lcid); if (pWordThis->lPointSize) // is outline font: gpihSetPointSize(ptxvd->hps, pWordThis->lPointSize); GpiSetColor(ptxvd->hps, lColor); if (!pWordThis->cEscapeCode) { gpihCharStringPosAt(ptxvd->hps, &ptlStart, &rclLine, flChar, pWordThis->cChars, (PSZ)pWordThis->pStart); } else // escape to be painted: DrawListMarker(ptxvd->hps, &rclLine, pWordThis, ptxvd->ulViewXOfs); } /* *@@ RepaintAnchor: * *@@added V0.9.3 (2000-05-18) [umoeller] */ STATIC VOID RepaintAnchor(PTEXTVIEWWINDATA ptxvd, LONG lColor) { PLISTNODE pNode = ptxvd->pWordNodeFirstInAnchor; PCSZ pcszLinkTarget = NULL; while (pNode) { PTXVWORD pWordThis = (PTXVWORD)pNode->pItemData; if (!pcszLinkTarget) // first loop: pcszLinkTarget = pWordThis->pcszLinkTarget; else if (pWordThis->pcszLinkTarget != pcszLinkTarget) // first word with different anchor: break; RepaintWord(ptxvd, pWordThis, lColor); pNode = pNode->pNext; } } /* *@@ ProcessCreate: * implementation for WM_CREATE in fnwpTextView. * *@@added V1.0.0 (2002-08-12) [umoeller] */ STATIC MRESULT ProcessCreate(HWND hwndTextView, MPARAM mp1, MPARAM mp2) { PXTEXTVIEWCDATA pcd = (PXTEXTVIEWCDATA)mp1; // can be NULL PCREATESTRUCT pcs = (PCREATESTRUCT)mp2; SBCDATA sbcd; MRESULT mrc = (MRESULT)TRUE; // error PTEXTVIEWWINDATA ptxvd; // allocate TEXTVIEWWINDATA for QWL_PRIVATE if (ptxvd = (PTEXTVIEWWINDATA)malloc(sizeof(TEXTVIEWWINDATA))) { SIZEL szlPage = {0, 0}; BOOL fShow = FALSE; // query message queue HMQ hmq = WinQueryWindowULong(hwndTextView, QWL_HMQ); // get codepage of message queue ULONG ulCodepage = WinQueryCp(hmq); memset(ptxvd, 0, sizeof(TEXTVIEWWINDATA)); WinSetWindowPtr(hwndTextView, QWL_PRIVATE, ptxvd); ptxvd->hab = WinQueryAnchorBlock(hwndTextView); ptxvd->hdc = WinOpenWindowDC(hwndTextView); ptxvd->hps = GpiCreatePS(ptxvd->hab, ptxvd->hdc, &szlPage, // use same page size as device PU_PELS | GPIT_MICRO | GPIA_ASSOC); // copy window style flags V0.9.20 (2002-08-10) [umoeller] ptxvd->flStyle = pcs->flStyle; gpihSwitchToRGB(ptxvd->hps); // set codepage; GPI defaults this to // the process codepage GpiSetCp(ptxvd->hps, ulCodepage); txvInitFormat(&ptxvd->xfd); // copy control data, if present if (pcd) memcpy(&ptxvd->cdata, pcd, pcd->cbData); // check values which might cause null divisions if (ptxvd->cdata.ulVScrollLineUnit == 0) ptxvd->cdata.ulVScrollLineUnit = 15; if (ptxvd->cdata.ulHScrollLineUnit == 0) ptxvd->cdata.ulHScrollLineUnit = 15; ptxvd->fAcceptsPresParamsNow = FALSE; // copy window dimensions from CREATESTRUCT ptxvd->rclViewReal.xLeft = 0; ptxvd->rclViewReal.yBottom = 0; ptxvd->rclViewReal.xRight = pcs->cx; ptxvd->rclViewReal.yTop = pcs->cy; sbcd.cb = sizeof(SBCDATA); sbcd.sHilite = 0; sbcd.posFirst = 0; sbcd.posLast = 100; sbcd.posThumb = 30; sbcd.cVisible = 50; sbcd.cTotal = 50; ptxvd->hwndVScroll = WinCreateWindow(hwndTextView, WC_SCROLLBAR, "", SBS_VERT | SBS_THUMBSIZE | WS_VISIBLE, 10, 10, 20, 100, hwndTextView, // owner HWND_TOP, ID_VSCROLL, &sbcd, 0); fShow = ((ptxvd->flStyle & XS_VSCROLL) != 0); WinShowWindow(ptxvd->hwndVScroll, fShow); ptxvd->fVScrollVisible = fShow; ptxvd->hwndHScroll = WinCreateWindow(hwndTextView, WC_SCROLLBAR, "", SBS_THUMBSIZE | WS_VISIBLE, 10, 10, 20, 100, hwndTextView, // owner HWND_TOP, ID_HSCROLL, &sbcd, 0); fShow = ((ptxvd->flStyle & XS_HSCROLL) != 0); WinShowWindow(ptxvd->hwndHScroll, fShow); ptxvd->fHScrollVisible = fShow; if (ptxvd->flStyle & XS_WORDWRAP) // word-wrapping should be enabled from the start: // V0.9.20 (2002-08-10) [umoeller] ptxvd->xfd.fmtpStandard.fWordWrap = TRUE; // set "code" format SetFormatFont(ptxvd->hps, &ptxvd->xfd.fmtcCode, 6, "System VIO"); // get colors from presparams/syscolors UpdateTextViewPresData(hwndTextView, ptxvd); AdjustViewRects(hwndTextView, ptxvd); if (ptxvd->flStyle & XS_HTML) { // if we're operating in HTML mode, set a // different default paragraph format to // make things prettier // V0.9.20 (2002-08-10) [umoeller] ptxvd->xfd.fmtpStandard.lSpaceBefore = 5; ptxvd->xfd.fmtpStandard.lSpaceAfter = 5; } // setting the window text on window creation never // worked V0.9.20 (2002-08-10) [umoeller] if (pcs->pszText) SetWindowText(hwndTextView, ptxvd, pcs->pszText); mrc = (MRESULT)FALSE; // OK } return mrc; } /* *@@ ProcessPaint: * implementation for WM_PAINT in fnwpTextView. * *@@added V1.0.0 (2002-08-12) [umoeller] */ STATIC VOID ProcessPaint(HWND hwndTextView) { PTEXTVIEWWINDATA ptxvd; if (ptxvd = (PTEXTVIEWWINDATA)WinQueryWindowPtr(hwndTextView, QWL_PRIVATE)) { HRGN hrgnOldClip; RECTL rclClip; RECTL rcl2Update; // get update rectangle WinQueryUpdateRect(hwndTextView, &rcl2Update); // since we're not using WinBeginPaint, // we must validate the update region, // or we'll get bombed with WM_PAINT msgs WinValidateRect(hwndTextView, NULL, FALSE); // reset clip region to "all" GpiSetClipRegion(ptxvd->hps, NULLHANDLE, &hrgnOldClip); // out: old clip region // reduce clip region to update rectangle GpiIntersectClipRectangle(ptxvd->hps, &rcl2Update); // exclusive // draw little box at the bottom right // (in between scroll bars) if we have // both vertical and horizontal scroll bars if ( (ptxvd->flStyle & (XS_VSCROLL | XS_HSCROLL)) == (XS_VSCROLL | XS_HSCROLL) && (ptxvd->fVScrollVisible) && (ptxvd->fHScrollVisible) ) { RECTL rclBox; rclBox.xLeft = ptxvd->rclViewPaint.xRight; rclBox.yBottom = 0; rclBox.xRight = rclBox.xLeft + WinQuerySysValue(HWND_DESKTOP, SV_CXVSCROLL); rclBox.yTop = WinQuerySysValue(HWND_DESKTOP, SV_CYHSCROLL); WinFillRect(ptxvd->hps, &rclBox, WinQuerySysColor(HWND_DESKTOP, SYSCLR_DIALOGBACKGROUND, 0)); } // paint "view paint" rectangle white; // this can be larger than "view text" WinFillRect(ptxvd->hps, &ptxvd->rclViewPaint, // exclusive ptxvd->lBackColor); // now reduce clipping rectangle to "view text" rectangle rclClip.xLeft = ptxvd->rclViewText.xLeft; rclClip.xRight = ptxvd->rclViewText.xRight - 1; rclClip.yBottom = ptxvd->rclViewText.yBottom; rclClip.yTop = ptxvd->rclViewText.yTop - 1; GpiIntersectClipRectangle(ptxvd->hps, &rclClip); // exclusive // finally, draw text lines in invalid rectangle; // this subfunction is smart enough to redraw only // the lines which intersect with rcl2Update GpiSetColor(ptxvd->hps, ptxvd->lForeColor); PaintViewText2Screen(ptxvd, &rcl2Update); if ( (!(ptxvd->flStyle & XS_STATIC)) // V0.9.20 (2002-08-10) [umoeller] && (WinQueryFocus(HWND_DESKTOP) == hwndTextView) ) { // we have the focus: // reset clip region to "all" GpiSetClipRegion(ptxvd->hps, NULLHANDLE, &hrgnOldClip); // out: old clip region PaintViewFocus(ptxvd->hps, ptxvd, TRUE); } ptxvd->fAcceptsPresParamsNow = TRUE; } } /* *@@ ProcessPresParamChanged: * implementation for WM_PRESPARAMCHANGED in fnwpTextView. * *@@added V1.0.0 (2002-08-12) [umoeller] */ STATIC VOID ProcessPresParamChanged(HWND hwndTextView, MPARAM mp1) { PTEXTVIEWWINDATA ptxvd; if (ptxvd = (PTEXTVIEWWINDATA)WinQueryWindowPtr(hwndTextView, QWL_PRIVATE)) { switch ((LONG)mp1) { case 0: // layout palette thing dropped case PP_BACKGROUNDCOLOR: case PP_FOREGROUNDCOLOR: case PP_FONTNAMESIZE: // re-query our presparams UpdateTextViewPresData(hwndTextView, ptxvd); } if (ptxvd->fAcceptsPresParamsNow) FormatText2Screen(hwndTextView, ptxvd, FALSE, TRUE); // full reformat } } /* *@@ ProcessSetFocus: * implementation for WM_SETFOCUS in fnwpTextView. * *@@added V1.0.0 (2002-08-12) [umoeller] */ STATIC VOID ProcessSetFocus(HWND hwndTextView, MPARAM mp2) { PTEXTVIEWWINDATA ptxvd; if (ptxvd = (PTEXTVIEWWINDATA)WinQueryWindowPtr(hwndTextView, QWL_PRIVATE)) { if (ptxvd->flStyle & XS_STATIC) { if (mp2) { // we're receiving the focus, but shouldn't have it: // then behave like the static control does, that is, // give focus to the next window in the dialog HWND hwnd = hwndTextView, hwndStart = hwnd; while (TRUE) { ULONG flStyle; if (!(hwnd = WinQueryWindow(hwnd, QW_NEXT))) hwnd = WinQueryWindow(WinQueryWindow(hwndStart, QW_PARENT), QW_TOP); // avoid endless looping if (hwnd == hwndStart) { if ( (hwnd = WinQueryWindow(hwnd, QW_OWNER)) && (hwnd == hwndStart) ) hwnd = NULLHANDLE; break; } if ( (flStyle = WinQueryWindowULong(hwnd, QWL_STYLE)) && (flStyle & (WS_DISABLED | WS_TABSTOP | WS_VISIBLE) == (WS_TABSTOP | WS_VISIBLE)) ) { WinSetFocus(HWND_DESKTOP, hwnd); break; } }; } } else { HPS hps = WinGetPS(hwndTextView); gpihSwitchToRGB(hps); PaintViewFocus(hps, ptxvd, (mp2 != 0)); WinReleasePS(hps); } } } /* *@@ ProcessButton1Down: * implementation for WM_BUTTON1DOWN in fnwpTextView. * *@@added V1.0.0 (2002-08-12) [umoeller] */ STATIC MRESULT ProcessButton1Down(HWND hwndTextView, MPARAM mp1) { MRESULT mrc = 0; PTEXTVIEWWINDATA ptxvd; if (ptxvd = (PTEXTVIEWWINDATA)WinQueryWindowPtr(hwndTextView, QWL_PRIVATE)) { POINTL ptlPos; PLISTNODE pWordNodeClicked; ptlPos.x = SHORT1FROMMP(mp1) + ptxvd->ulViewXOfs; ptlPos.y = SHORT2FROMMP(mp1) - ptxvd->ulViewYOfs; if ( (!(ptxvd->flStyle & XS_STATIC)) // V0.9.20 (2002-08-10) [umoeller] && (hwndTextView != WinQueryFocus(HWND_DESKTOP)) ) WinSetFocus(HWND_DESKTOP, hwndTextView); ptxvd->pcszLastLinkClicked = NULL; if (pWordNodeClicked = txvFindWordFromPoint(&ptxvd->xfd, &ptlPos)) { PTXVWORD pWordClicked = (PTXVWORD)pWordNodeClicked->pItemData; // store link target (can be NULL) if (ptxvd->pcszLastLinkClicked = pWordClicked->pcszLinkTarget) { // word has a link target: PLISTNODE pNode = pWordNodeClicked; // reset first word of anchor ptxvd->pWordNodeFirstInAnchor = NULL; // go back to find the first word which has this anchor, // because we need to repaint them all while (pNode) { PTXVWORD pWordThis = (PTXVWORD)pNode->pItemData; if (pWordThis->pcszLinkTarget == pWordClicked->pcszLinkTarget) { // still has same anchor: // go for previous ptxvd->pWordNodeFirstInAnchor = pNode; pNode = pNode->pPrevious; } else // different anchor: // pNodeFirst points to first node with same anchor now break; } RepaintAnchor(ptxvd, RGBCOL_RED); } } WinSetCapture(HWND_DESKTOP, hwndTextView); mrc = (MRESULT)TRUE; } return mrc; } /* *@@ ProcessButton1Up: * implementation for WM_BUTTON1UP in fnwpTextView. * *@@added V1.0.0 (2002-08-12) [umoeller] */ STATIC MRESULT ProcessButton1Up(HWND hwndTextView, MPARAM mp1) { MRESULT mrc = 0; PTEXTVIEWWINDATA ptxvd; if (ptxvd = (PTEXTVIEWWINDATA)WinQueryWindowPtr(hwndTextView, QWL_PRIVATE)) { POINTL ptlPos; HWND hwndOwner = NULLHANDLE; ptlPos.x = SHORT1FROMMP(mp1) + ptxvd->ulViewXOfs; ptlPos.y = SHORT2FROMMP(mp1) - ptxvd->ulViewYOfs; WinSetCapture(HWND_DESKTOP, NULLHANDLE); if (ptxvd->pcszLastLinkClicked) { RepaintAnchor(ptxvd, ptxvd->lForeColor); // nofify owner if (hwndOwner = WinQueryWindow(hwndTextView, QW_OWNER)) WinPostMsg(hwndOwner, WM_CONTROL, MPFROM2SHORT(WinQueryWindowUShort(hwndTextView, QWS_ID), TXVN_LINK), (MPARAM)(ULONG)(ptxvd->pcszLastLinkClicked)); } mrc = (MRESULT)TRUE; } return mrc; } /* *@@ ProcessChar: * implementation for WM_CHAR in fnwpTextView. * *@@added V1.0.0 (2002-08-12) [umoeller] *@@changed WarpIN V1.0.18 (2008-11-16) [pr]: added correct ID for horiz. scroll @@fixes 1086 *@@changed WarpIN V1.0.18 (2008-11-16) [pr]: added Ctrl-Ins copy capability @@fixes 1116 */ STATIC MRESULT ProcessChar(HWND hwndTextView, MPARAM mp1, MPARAM mp2) { MRESULT mrc = 0; PTEXTVIEWWINDATA ptxvd; if (ptxvd = (PTEXTVIEWWINDATA)WinQueryWindowPtr(hwndTextView, QWL_PRIVATE)) { BOOL fDefProc = TRUE; USHORT usFlags = SHORT1FROMMP(mp1); // USHORT usch = SHORT1FROMMP(mp2); USHORT usvk = SHORT2FROMMP(mp2); if (usFlags & KC_VIRTUALKEY) { ULONG ulMsg = 0; USHORT usID = ID_VSCROLL; SHORT sPos = 0; SHORT usCmd = 0; fDefProc = FALSE; switch (usvk) { case VK_UP: ulMsg = WM_VSCROLL; usCmd = SB_LINEUP; break; case VK_DOWN: ulMsg = WM_VSCROLL; usCmd = SB_LINEDOWN; break; case VK_RIGHT: ulMsg = WM_HSCROLL; usCmd = SB_LINERIGHT; usID = ID_HSCROLL; // WarpIN V1.0.18 break; case VK_LEFT: ulMsg = WM_HSCROLL; usCmd = SB_LINELEFT; usID = ID_HSCROLL; // WarpIN V1.0.18 break; case VK_PAGEUP: ulMsg = WM_VSCROLL; if (usFlags & KC_CTRL) { sPos = 0; usCmd = SB_SLIDERPOSITION; } else usCmd = SB_PAGEUP; break; case VK_PAGEDOWN: ulMsg = WM_VSCROLL; if (usFlags & KC_CTRL) { sPos = ptxvd->xfd.szlWorkspace.cy; usCmd = SB_SLIDERPOSITION; } else usCmd = SB_PAGEDOWN; break; case VK_HOME: if (usFlags & KC_CTRL) // vertical: ulMsg = WM_VSCROLL; else { ulMsg = WM_HSCROLL; usID = ID_HSCROLL; // WarpIN V1.0.18 } sPos = 0; usCmd = SB_SLIDERPOSITION; break; case VK_END: if (usFlags & KC_CTRL) { // vertical: ulMsg = WM_VSCROLL; sPos = ptxvd->xfd.szlWorkspace.cy; } else { ulMsg = WM_HSCROLL; sPos = ptxvd->xfd.szlWorkspace.cx; usID = ID_HSCROLL; // WarpIN V1.0.18 } usCmd = SB_SLIDERPOSITION; break; // WarpIN V1.0.18 case VK_INSERT: if (usFlags & KC_CTRL) { ulMsg = TXM_COPY; usID = 0; } break; default: // other: fDefProc = TRUE; } if ( ((usFlags & KC_KEYUP) == 0) && (ulMsg) ) WinSendMsg(hwndTextView, ulMsg, MPFROMSHORT(usID), MPFROM2SHORT(sPos, usCmd)); } if (fDefProc) mrc = WinDefWindowProc(hwndTextView, WM_CHAR, mp1, mp2); // sends to owner else mrc = (MPARAM)TRUE; } return mrc; } /* *@@ ProcessJumpToAnchorName: * implementation for TXM_JUMPTOANCHORNAME in fnwpTextView. * *@@added V1.0.0 (2002-08-12) [umoeller] */ STATIC MRESULT ProcessJumpToAnchorName(HWND hwndTextView, MPARAM mp1) { MRESULT mrc = 0; PTEXTVIEWWINDATA ptxvd; if ( (ptxvd = (PTEXTVIEWWINDATA)WinQueryWindowPtr(hwndTextView, QWL_PRIVATE)) && (mp1) ) { PLISTNODE pWordNode; PTXVWORD pWord; if ( (pWordNode = txvFindWordFromAnchor(&ptxvd->xfd, (const char*)mp1)) && (pWord = (PTXVWORD)pWordNode->pItemData) ) { // found: PTXVRECTANGLE pRect = pWord->pRectangle; ULONG ulWinCY = (ptxvd->rclViewText.yTop - ptxvd->rclViewText.yBottom); // now we need to scroll the window so that this rectangle is on top. // Since rectangles start out with the height of the window (e.g. +768) // and then have lower y coordinates down to way in the negatives, // to get the y offset, we must... ptxvd->ulViewYOfs = (-pRect->rcl.yTop) - ulWinCY; if (ptxvd->ulViewYOfs < 0) ptxvd->ulViewYOfs = 0; if (ptxvd->ulViewYOfs > ((LONG)ptxvd->xfd.szlWorkspace.cy - ulWinCY)) ptxvd->ulViewYOfs = (LONG)ptxvd->xfd.szlWorkspace.cy - ulWinCY; // vertical scroll bar enabled at all? if (ptxvd->flStyle & XS_VSCROLL) { /* BOOL fEnabled = */ winhUpdateScrollBar(ptxvd->hwndVScroll, ulWinCY, ptxvd->xfd.szlWorkspace.cy, ptxvd->ulViewYOfs, (ptxvd->flStyle & XS_AUTOVHIDE)); WinInvalidateRect(hwndTextView, NULL, FALSE); } mrc = (MRESULT)TRUE; } } return mrc; } /* *@@ ProcessDestroy: * implementation for WM_DESTROY in fnwpTextView. * *@@added V1.0.0 (2002-08-12) [umoeller] */ STATIC MRESULT ProcessDestroy(HWND hwndTextView, MPARAM mp1, MPARAM mp2) { PTEXTVIEWWINDATA ptxvd; if (ptxvd = (PTEXTVIEWWINDATA)WinQueryWindowPtr(hwndTextView, QWL_PRIVATE)) { xstrClear(&ptxvd->xfd.strViewText); xstrClear(&ptxvd->xfd.strOrigText); // WarpIN V1.0.18 lstClear(&ptxvd->xfd.llRectangles); lstClear(&ptxvd->xfd.llWords); GpiDestroyPS(ptxvd->hps); free(ptxvd); WinSetWindowPtr(hwndTextView, QWL_PRIVATE, NULL); } return WinDefWindowProc(hwndTextView, WM_DESTROY, mp1, mp2); } /* *@@ fnwpTextView: * window procedure for the text view control. This is * registered with the WC_XTEXTVIEW class in txvRegisterTextView. * * The text view control is not a subclassed whatever control, * but a control implemented from scratch. As a result, we * had to implement all messages which are usually recognized * by a control. In detail, we have: * * -- WM_WINDOWPOSCHANGED: if the control is resized, the * text is reformatted and the scroll bars are readjusted. * See AdjustViewRects and txvFormatText. * * -- WM_PRESPARAMCHANGED: if fonts or colors are dropped * on the control, we reformat the text also. * * -- WM_HSCROLL and WM_VSCROLL: this calls winhHandleScrollMsg * to scroll the window contents. * * -- WM_BUTTON1DOWN: this sets the focus to the control. * * -- WM_SETFOCUS: if we receive the focus, we draw a fine * dotted line in the "selection" color around the text * window. * * -- WM_CHAR: if we have the focus, the user can move the * visible part within the workspace using the usual * cursor and HOME/END keys. * * -- WM_MOUSEMOVE: this sends WM_CONTROLPOINTER to the * owner so the owner can change the mouse pointer. * * Painting * * The text view control creates a micro presentation space * from the window's device context upon WM_CREATE, which is * stored in TEXTVIEWWINDATA. We do not use WinBeginPaint in * WM_PAINT, but only the PS we created ourselves. This saves * us from resetting and researching all the fonts etc., which * should be speedier. * * The text view control uses a private window word for storing * its own data. The client is free to use QWL_USER of the * text view control. * *@@changed V0.9.3 (2000-05-05) [umoeller]: removed TXM_NEWTEXT; now supporting WinSetWindowText *@@changed V0.9.3 (2000-05-07) [umoeller]: crashed if create param was NULL; fixed *@@changed V0.9.20 (2002-08-10) [umoeller]: no longer using QWL_USER, which is free now *@@changed V0.9.20 (2002-08-10) [umoeller]: setting text on window creation never worked, fixed *@@changed V0.9.20 (2002-08-10) [umoeller]: added TXN_ANCHORCLICKED owner notify for anchors *@@changed V0.9.20 (2002-08-10) [umoeller]: converted private style flags to XS_* window style flags *@@changed V0.9.20 (2002-08-10) [umoeller]: added support for XS_STATIC *@@changed V0.9.20 (2002-08-10) [umoeller]: added support for formatting HTML and plain text automatically *@@changed V1.0.0 (2002-08-12) [umoeller]: optimized locality by moving big chunks into subfuncs *@@changed WarpIN V1.0.18 (2008-11-16) [pr]: added Ctrl-Ins copy capability @@fixes 1116 *@@changed WarpIN V1.0.18 (2008-11-29) [pr]: added style set/query functions @@fixes 1116 */ STATIC MRESULT EXPENTRY fnwpTextView(HWND hwndTextView, ULONG msg, MPARAM mp1, MPARAM mp2) { MRESULT mrc = 0; PTEXTVIEWWINDATA ptxvd; switch (msg) { /* * WM_CREATE: * */ case WM_CREATE: mrc = ProcessCreate(hwndTextView, mp1, mp2); // extracted V1.0.0 (2002-08-12) [umoeller] break; /* * WM_SETWINDOWPARAMS: * this message sets the window parameters, * most importantly, the window text. * * This updates the control. */ case WM_SETWINDOWPARAMS: if ( (mp1) && (((PWNDPARAMS)mp1)->fsStatus & WPM_TEXT) && (ptxvd = (PTEXTVIEWWINDATA)WinQueryWindowPtr(hwndTextView, QWL_PRIVATE)) ) { SetWindowText(hwndTextView, ptxvd, ((PWNDPARAMS)mp1)->pszText); mrc = (MRESULT)TRUE; // was missing V0.9.20 (2002-08-10) [umoeller] } break; /* * WM_WINDOWPOSCHANGED: * */ case WM_WINDOWPOSCHANGED: // resizing? if ( (mp1) && (((PSWP)mp1)->fl & SWP_SIZE) && (ptxvd = (PTEXTVIEWWINDATA)WinQueryWindowPtr(hwndTextView, QWL_PRIVATE)) ) { WinQueryWindowRect(hwndTextView, &ptxvd->rclViewReal); AdjustViewRects(hwndTextView, ptxvd); FormatText2Screen(hwndTextView, ptxvd, FALSE, FALSE); // quick format } break; /* * WM_PAINT: * */ case WM_PAINT: ProcessPaint(hwndTextView); // extracted V1.0.0 (2002-08-12) [umoeller] break; /* * WM_PRESPARAMCHANGED: * * Changing the color or font settings * is equivalent to changing the default * paragraph format. See TXM_SETFORMAT. */ case WM_PRESPARAMCHANGED: ProcessPresParamChanged(hwndTextView, mp1); break; /* * WM_VSCROLL: * */ case WM_VSCROLL: if ( (ptxvd = (PTEXTVIEWWINDATA)WinQueryWindowPtr(hwndTextView, QWL_PRIVATE)) && (ptxvd->fVScrollVisible) ) { winhHandleScrollMsg(hwndTextView, ptxvd->hwndVScroll, &ptxvd->ulViewYOfs, &ptxvd->rclViewText, ptxvd->xfd.szlWorkspace.cy, ptxvd->cdata.ulVScrollLineUnit, msg, mp2); } break; /* * WM_HSCROLL: * */ case WM_HSCROLL: if ( (ptxvd = (PTEXTVIEWWINDATA)WinQueryWindowPtr(hwndTextView, QWL_PRIVATE)) && (ptxvd->fHScrollVisible) ) { winhHandleScrollMsg(hwndTextView, ptxvd->hwndHScroll, &ptxvd->ulViewXOfs, &ptxvd->rclViewText, ptxvd->xfd.szlWorkspace.cx, ptxvd->cdata.ulHScrollLineUnit, msg, mp2); } break; /* * WM_SETFOCUS: * */ case WM_SETFOCUS: ProcessSetFocus(hwndTextView, mp2); break; /* * WM_MOUSEMOVE: * send WM_CONTROLPOINTER to owner. */ case WM_MOUSEMOVE: { HWND hwndOwner; if (hwndOwner = WinQueryWindow(hwndTextView, QW_OWNER)) { HPOINTER hptrSet = (HPOINTER)WinSendMsg(hwndOwner, WM_CONTROLPOINTER, (MPARAM)(LONG)WinQueryWindowUShort(hwndTextView, QWS_ID), (MPARAM)WinQuerySysPointer(HWND_DESKTOP, SPTR_ARROW, FALSE)); WinSetPointer(HWND_DESKTOP, hptrSet); } } break; /* * WM_BUTTON1DOWN: * */ case WM_BUTTON1DOWN: mrc = ProcessButton1Down(hwndTextView, mp1); break; /* * WM_BUTTON1UP: * */ case WM_BUTTON1UP: mrc = ProcessButton1Up(hwndTextView, mp1); break; /* * WM_CHAR: * */ case WM_CHAR: mrc = ProcessChar(hwndTextView, mp1, mp2); break; /* *@@ TXM_QUERYPARFORMAT: * this msg can be sent to the text view control * to retrieve the paragraph format with the * index specified in mp1. * * This must be sent, not posted, to the control. * * Parameters: * * -- ULONG mp1: index of format to query. * Must be 0 currently for the standard * paragraph format. * * -- PXFMTPARAGRAPH mp2: pointer to buffer * which is to receive the formatting * data. * * Returns TRUE if copying was successful. * *@@added V0.9.3 (2000-05-06) [umoeller] */ case TXM_QUERYPARFORMAT: if ( (ptxvd = (PTEXTVIEWWINDATA)WinQueryWindowPtr(hwndTextView, QWL_PRIVATE)) && (!mp1) && (mp2) ) { memcpy(mp2, &ptxvd->xfd.fmtpStandard, sizeof(XFMTPARAGRAPH)); mrc = (MPARAM)TRUE; } break; /* *@@ TXM_SETPARFORMAT: * reverse to TXM_QUERYPARFORMAT, this sets a * paragraph format (line spacings, margins * and such). * * This must be sent, not posted, to the control. * * Parameters: * * -- ULONG mp1: index of format to set. * Must be 0 currently for the standard * paragraph format. * * -- PXFMTPARAGRAPH mp2: pointer to buffer * from which to copy formatting data. * If this pointer is NULL, the format * is reset to the default. * * This reformats the control. * *@@added V0.9.3 (2000-05-06) [umoeller] */ case TXM_SETPARFORMAT: if ( (ptxvd = (PTEXTVIEWWINDATA)WinQueryWindowPtr(hwndTextView, QWL_PRIVATE)) && (!mp1) ) { if (mp2) // copy: memcpy(&ptxvd->xfd.fmtpStandard, mp2, sizeof(XFMTPARAGRAPH)); else // default: memset(&ptxvd->xfd.fmtpStandard, 0, sizeof(XFMTPARAGRAPH)); FormatText2Screen(hwndTextView, ptxvd, FALSE, TRUE); // full reformat mrc = (MPARAM)TRUE; } break; /* *@@ TXM_SETWORDWRAP: * this text view control msg quickly changes * the word-wrapping style of the default * paragraph formatting. * * This may be sent or posted. * * (BOOL)mp1 determines whether word wrapping * should be turned on or off. */ case TXM_SETWORDWRAP: if (ptxvd = (PTEXTVIEWWINDATA)WinQueryWindowPtr(hwndTextView, QWL_PRIVATE)) { BOOL ulOldFlFormat = ptxvd->xfd.fmtpStandard.fWordWrap; ptxvd->xfd.fmtpStandard.fWordWrap = (BOOL)mp1; if (ptxvd->xfd.fmtpStandard.fWordWrap != ulOldFlFormat) FormatText2Screen(hwndTextView, ptxvd, FALSE, FALSE); // quick format } break; /* *@@ TXM_QUERYCDATA: * copies the current XTEXTVIEWCDATA * into the specified buffer. * * This must be sent, not posted, to the control. * * Parameters: * * -- PXTEXTVIEWCDATA mp1: target buffer. * Before calling this, you MUST specify * XTEXTVIEWCDATA.cbData. * * Returns the bytes that were copied as * a ULONG. */ case TXM_QUERYCDATA: if ( (ptxvd = (PTEXTVIEWWINDATA)WinQueryWindowPtr(hwndTextView, QWL_PRIVATE)) && (mp1) ) { PXTEXTVIEWCDATA pTarget = (PXTEXTVIEWCDATA)mp1; mrc = (MRESULT)min(pTarget->cbData, sizeof(XTEXTVIEWCDATA)); memcpy(pTarget, &ptxvd->cdata, (ULONG)mrc); } break; /* *@@ TXM_SETCDATA: * updates the current XTEXTVIEWCDATA * with the data from the specified buffer. * * This must be sent, not posted, to the control. * * Parameters: * * -- PXTEXTVIEWCDATA mp1: source buffer. * Before calling this, you MUST specify * XTEXTVIEWCDATA.cbData. * * Returns the bytes that were copied as * a ULONG. * *@@changed V1.0.0 (2002-08-12) [umoeller]: now returning bytes */ case TXM_SETCDATA: if ( (ptxvd = (PTEXTVIEWWINDATA)WinQueryWindowPtr(hwndTextView, QWL_PRIVATE)) && (mp1) ) { PXTEXTVIEWCDATA pSource = (PXTEXTVIEWCDATA)mp1; mrc = (MRESULT)min(pSource->cbData, sizeof(XTEXTVIEWCDATA)); memcpy(&ptxvd->cdata, pSource, (ULONG)mrc); } break; /* *@@ TXM_JUMPTOANCHORNAME: * scrolls the XTextView control contents so that * the text marked with the specified anchor name * (TXVESC_ANCHORNAME escape) appears at the top * of the control. * * This must be sent, not posted, to the control. * * Parameters: * -- PSZ mp1: anchor name (e.g. "anchor1"). * * Returns TRUE if the jump was successful. * *@@added V0.9.4 (2000-06-12) [umoeller] */ case TXM_JUMPTOANCHORNAME: mrc = ProcessJumpToAnchorName(hwndTextView, mp1); break; /* *@@ TXM_QUERYTEXTEXTENT: * returns the extents of the currently set text, * that is, the width and height of the internal * work area, of which the current view rectangle * displays a subrectangle. * * This must be sent, not posted, to the control. * * Parameters: * * -- PSIZEL mp1: pointer to a SIZEL buffer, * which receives the extent in the cx and * cy members. These will be set to null * values if the control currently has no * text. * * Returns TRUE on success. * *@@added V0.9.20 (2002-08-10) [umoeller] */ case TXM_QUERYTEXTEXTENT: if ( (mp1) && (ptxvd = (PTEXTVIEWWINDATA)WinQueryWindowPtr(hwndTextView, QWL_PRIVATE)) ) { memcpy((PSIZEL)mp1, &ptxvd->xfd.szlWorkspace, sizeof(SIZEL)); mrc = (MRESULT)TRUE; } break; /* *@@ TXM_QUERYSTYLE: * returns the current style flags. * * This must be sent, not posted, to the control. * * Parameters: * * -- PULONG mp1: pointer to a ULONG buffer. * * Returns TRUE on success. * *@@added WarpIN V1.0.18 (2008-11-29) [pr] */ case TXM_QUERYSTYLE: if ( (mp1) && (ptxvd = (PTEXTVIEWWINDATA)WinQueryWindowPtr(hwndTextView, QWL_PRIVATE)) ) { *((ULONG *) mp1) = ptxvd->flStyle; mrc = (MRESULT)TRUE; } break; /* *@@ TXM_SETSTYLE: * sets the current style flags. * * This must be sent, not posted, to the control. * * Parameters: * * -- PULONG mp1: pointer to a ULONG buffer. * * Returns TRUE on success. * *@@added WarpIN V1.0.18 (2008-11-29) [pr] */ case TXM_SETSTYLE: if ( (mp1) && (ptxvd = (PTEXTVIEWWINDATA)WinQueryWindowPtr(hwndTextView, QWL_PRIVATE)) ) { ptxvd->flStyle = *((ULONG *) mp1); mrc = (MRESULT)TRUE; } break; /* *@@ TXM_COPY: * copies the unprocessed window text to the clipboard. * * Returns TRUE on success. * *@@added WarpIN V1.0.18 (2008-11-16) [pr] */ case TXM_COPY: if (ptxvd = (PTEXTVIEWWINDATA)WinQueryWindowPtr(hwndTextView, QWL_PRIVATE)) { mrc = (MRESULT) winhSetClipboardText(ptxvd->hab, ptxvd->xfd.strOrigText.psz, strlen(ptxvd->xfd.strOrigText.psz)); } break; /* * WM_DESTROY: * clean up. */ case WM_DESTROY: mrc = ProcessDestroy(hwndTextView, mp1, mp2); break; default: mrc = WinDefWindowProc(hwndTextView, msg, mp1, mp2); } return mrc; } /* *@@ txvRegisterTextView: * registers the Text View class with PM. Required * before the text view control can be used. */ BOOL txvRegisterTextView(HAB hab) { return WinRegisterClass(hab, WC_XTEXTVIEW, fnwpTextView, 0, 2 * sizeof(PVOID)); // QWL_USER and QWL_PRIVATE } /* *@@ txvReplaceWithTextView: * replaces any window with a text view control. * You must call txvRegisterTextView beforehand. * *@@added V0.9.1 (2000-02-13) [umoeller] */ HWND txvReplaceWithTextView(HWND hwndParentAndOwner, USHORT usID, ULONG flWinStyle, USHORT usBorder) { HWND hwndMLE = WinWindowFromID(hwndParentAndOwner, usID), hwndTextView = NULLHANDLE; if (hwndMLE) { ULONG ul, // attrFound, abValue[32]; SWP swpMLE; XTEXTVIEWCDATA xtxCData; PSZ pszFont = winhQueryWindowFont(hwndMLE); LONG lBackClr = -1, lForeClr = -1; if ((ul = WinQueryPresParam(hwndMLE, PP_BACKGROUNDCOLOR, 0, NULL, (ULONG)sizeof(abValue), (PVOID)&abValue, QPF_NOINHERIT))) lBackClr = abValue[0]; if ((ul = WinQueryPresParam(hwndMLE, PP_FOREGROUNDCOLOR, 0, NULL, (ULONG)sizeof(abValue), (PVOID)&abValue, QPF_NOINHERIT))) lForeClr = abValue[0]; WinQueryWindowPos(hwndMLE, &swpMLE); WinDestroyWindow(hwndMLE); memset(&xtxCData, 0, sizeof(xtxCData)); xtxCData.cbData = sizeof(xtxCData); xtxCData.ulXBorder = usBorder; xtxCData.ulYBorder = usBorder; hwndTextView = WinCreateWindow(hwndParentAndOwner, WC_XTEXTVIEW, "", flWinStyle, swpMLE.x, swpMLE.y, swpMLE.cx, swpMLE.cy, hwndParentAndOwner, HWND_TOP, usID, &xtxCData, 0); if (pszFont) { winhSetWindowFont(hwndTextView, pszFont); free(pszFont); } if (lBackClr != -1) WinSetPresParam(hwndTextView, PP_BACKGROUNDCOLOR, sizeof(ULONG), &lBackClr); if (lForeClr != -1) WinSetPresParam(hwndTextView, PP_FOREGROUNDCOLOR, sizeof(ULONG), &lForeClr); } return hwndTextView; } /* ****************************************************************** * * Printer-dependent functions * ********************************************************************/ /* *@@ prthQueryQueues: * returns a buffer containing all print queues * on the system. * * This is usually the first step before printing. * After calling this function, show a dlg to the * user, allow him to select the printer queue * to be used. This can then be passed to * prthCreatePrinterDC. * * Use prthFreeBuf to free the returned buffer. */ STATIC PRQINFO3* prthEnumQueues(PULONG pulReturned) // out: no. of queues found { SPLERR rc; ULONG cTotal; ULONG cbNeeded = 0; PRQINFO3 *pprq3 = NULL; // count queues & get number of bytes needed for buffer rc = SplEnumQueue(NULL, // default computer 3, // detail level NULL, // pbuf 0L, // cbBuf pulReturned, // out: entries returned &cTotal, // out: total entries available &cbNeeded, NULL); // reserved if (!rc && cbNeeded) { pprq3 = (PRQINFO3*)malloc(cbNeeded); if (pprq3) { // enum the queues rc = SplEnumQueue(NULL, 3, pprq3, cbNeeded, pulReturned, &cTotal, &cbNeeded, NULL); } } return pprq3; } /* *@@ prthFreeBuf: * */ STATIC VOID prthFreeBuf(PVOID pprq3) { if (pprq3) free(pprq3); } /* *@@ prthCreatePrinterDC: * creates a device context for the printer * specified by the given printer queue. * * As a nifty feature, this returns printer * device resolution automatically in the * specified buffer. * * Returns NULLHANDLE (== DEV_ERROR) on errors. * * Use DevCloseDC to destroy the DC. * * Based on print sample by Peter Fitzsimmons, Fri 95-09-29 02:47:16am. */ STATIC HDC prthCreatePrinterDC(HAB hab, PRQINFO3 *pprq3, PLONG palRes) // out: 2 longs holding horizontal and vertical // printer resolution in pels per inch { HDC hdc = NULLHANDLE; DEVOPENSTRUC dos; PSZ p; memset(&dos, 0, sizeof(dos)); p = strrchr(pprq3->pszDriverName, '.'); if (p) *p = 0; // del everything after '.' dos.pszLogAddress = pprq3->pszName; dos.pszDriverName = pprq3->pszDriverName; dos.pdriv = pprq3->pDriverData; dos.pszDataType = "PM_Q_STD"; hdc = DevOpenDC(hab, OD_QUEUED, "*", 4L, // count of items in next param (PDEVOPENDATA)&dos, 0); // compatible DC if (hdc) DevQueryCaps(hdc, CAPS_HORIZONTAL_FONT_RES, 2, palRes); // buffer return hdc; } /* *@@ prthQueryForms: * returns a buffer containing all forms * supported by the specified printer DC. * * Use prthFreeBuf to free the returned * buffer. * * HCINFO uses different model spaces for * the returned info. See PMREF for details. */ STATIC HCINFO* prthQueryForms(HDC hdc, PULONG pulCount) { HCINFO *pahci = NULL; LONG cForms; // get form count cForms = DevQueryHardcopyCaps(hdc, 0L, 0L, NULL); // phci); if (cForms) { pahci = (HCINFO*)malloc(cForms * sizeof(HCINFO)); if (pahci) { *pulCount = DevQueryHardcopyCaps(hdc, 0, cForms, pahci); } } return pahci; } /* *@@ prthCreatePS: * creates a "normal" presentation space from the specified * printer device context (which can be opened thru * prthCreatePrinterDC). * * Returns NULLHANDLE on errors. * * Based on print sample by Peter Fitzsimmons, Fri 95-09-29 02:47:16am. */ STATIC HPS prthCreatePS(HAB hab, // in: anchor block HDC hdc, // in: printer device context ULONG ulUnits) // in: one of: // -- PU_PELS // -- PU_LOMETRIC // -- PU_HIMETRIC // -- PU_LOENGLISH // -- PU_HIENGLISH // -- PU_TWIPS { SIZEL sizel; sizel.cx = 0; sizel.cy = 0; return GpiCreatePS(hab, hdc, &sizel, ulUnits | GPIA_ASSOC | GPIT_NORMAL); } /* *@@ prthStartDoc: * calls DevEscape with DEVESC_STARTDOC. * This must be called before any painting * into the HDC's HPS. Any GPI calls made * before this are ignored. * * pszDocTitle appears in the spooler. */ STATIC VOID prthStartDoc(HDC hdc, PSZ pszDocTitle) { DevEscape(hdc, DEVESC_STARTDOC, strlen(pszDocTitle), pszDocTitle, 0L, 0L); } /* *@@ prthNextPage: * calls DevEscape with DEVESC_NEWFRAME. * Signals when an application has finished writing to a page and wants to * start a new page. It is similar to GpiErase processing for a screen device * context, and causes a reset of the attributes. This escape is used with a * printer device to advance to a new page. */ STATIC VOID prthNextPage(HDC hdc) { DevEscape(hdc, DEVESC_NEWFRAME, 0, 0, 0, 0); } /* *@@ prthEndDoc: * calls DevEscape with DEVESC_ENDDOC * and disassociates the HPS from the HDC. * Call this right before doing + GpiDestroyPS(hps); + DevCloseDC(hdc); */ STATIC VOID prthEndDoc(HDC hdc, HPS hps) { DevEscape(hdc, DEVESC_ENDDOC, 0L, 0L, 0, NULL); GpiAssociate(hps, NULLHANDLE); } /* *@@ txvPrint: * this does the actual printing. */ BOOL txvPrint(HAB hab, HDC hdc, // in: printer device context HPS hps, // in: printer presentation space (using PU_PELS) PSZ pszViewText, // in: text to print ULONG ulSize, // in: default font point size PSZ pszFaceName, // in: default font face name HCINFO *phci, // in: hardcopy form to use PSZ pszDocTitle, // in: document title (appears in spooler) FNPRINTCALLBACK *pfnCallback) { RECTL rclPageDevice, rclPageWorld; XFORMATDATA xfd; BOOL fAnotherPage = FALSE; ULONG ulCurrentLineIndex = 0, ulCurrentPage = 1; ULONG ulCurrentYOfs = 0; /* MATRIXLF matlf; POINTL ptlCenter; FIXED scalars[2]; */ // important: we must do a STARTDOC before we use the printer HPS. prthStartDoc(hdc, pszDocTitle); // the PS is in TWIPS, but our world coordinate // space is in pels, so we need to transform /* GpiQueryViewingTransformMatrix(hps, 1L, &matlf); ptlCenter.x = 0; ptlCenter.y = 0; scalars[0] = MAKEFIXED(2,0); scalars[1] = MAKEFIXED(3,0); GpiScale (hps, &matlf, TRANSFORM_REPLACE, scalars, &ptlCenter); */ // initialize format with font from window txvInitFormat(&xfd); /* SetFormatFont(hps, &xfd, ulSize, pszFaceName); */ // use text from window xstrcpy(&xfd.strViewText, pszViewText, 0); // setup page GpiQueryPageViewport(hps, &rclPageDevice); // this is in device units; convert this // to the world coordinate space of the printer PS memcpy(&rclPageWorld, &rclPageDevice, sizeof(RECTL)); GpiConvert(hps, CVTC_DEVICE, // source CVTC_WORLD, 2, // 2 points, it's a rectangle (PPOINTL)&rclPageWorld); // left and bottom margins are in millimeters... /* rclPage.xLeft = 100; // ### rclPage.yBottom = 100; rclPage.xRight = rclPage.xLeft + phci->xPels; rclPage.yTop = rclPage.yBottom + phci->yPels; */ txvFormatText(hps, &xfd, // in: ptxvd->rclViewText &rclPageWorld, TRUE); do { _Pmpf(("---- printing page %d", ulCurrentPage)); fAnotherPage = txvPaintText(hab, hps, &xfd, &rclPageWorld, 0, &ulCurrentYOfs, FALSE, // draw only fully visible lines &ulCurrentLineIndex); // in/out: line to start with if (fAnotherPage) { prthNextPage(hdc); if (pfnCallback(ulCurrentPage++, 0) == FALSE) fAnotherPage = FALSE; } } while (fAnotherPage); prthEndDoc(hdc, hps); return TRUE; } /* *@@ txvPrintWindow: * one-shot function which prints the contents * of the specified XTextView control to the * default printer, using the default form. * * Returns a nonzero value upon errors. * * Based on print sample by Peter Fitzsimmons, Fri 95-09-29 02:47:16am. */ int txvPrintWindow(HWND hwndTextView, PSZ pszDocTitle, // in: document title (appears in spooler) FNPRINTCALLBACK *pfnCallback) { int irc = 0; PTEXTVIEWWINDATA ptxvd = (PTEXTVIEWWINDATA)WinQueryWindowPtr(hwndTextView, QWL_PRIVATE); if (!ptxvd) irc = 1; else { ULONG cReturned = 0; PRQINFO3 *pprq3 = prthEnumQueues(&cReturned); HDC hdc = NULLHANDLE; LONG caps[2]; // find default queue if (pprq3) { ULONG i; // search for default queue; for (i = 0; i < cReturned; i++) if (pprq3[i].fsType & PRQ3_TYPE_APPDEFAULT) { hdc = prthCreatePrinterDC(ptxvd->hab, &pprq3[i], caps); break; } prthFreeBuf(pprq3); } if (!hdc) irc = 2; else { // OK, we got a printer DC: HPS hps; ULONG cForms = 0; HCINFO *pahci, *phciSelected = 0; // find default form pahci = prthQueryForms(hdc, &cForms); if (pahci) { HCINFO *phciThis = pahci; ULONG i; for (i = 0; i < cForms; i++, phciThis++) { if (phciThis->flAttributes & HCAPS_CURRENT) { phciSelected = phciThis; } } } if (!phciSelected) irc = 3; else { // create printer PS hps = prthCreatePS(ptxvd->hab, hdc, PU_PELS); if (hps == GPI_ERROR) irc = 4; else { PSZ pszFont; ULONG ulSize = 0; PSZ pszFaceName = 0; if ((pszFont = winhQueryWindowFont(hwndTextView))) gpihSplitPresFont(pszFont, &ulSize, &pszFaceName); txvPrint(ptxvd->hab, hdc, hps, ptxvd->xfd.strViewText.psz, ulSize, pszFaceName, phciSelected, pszDocTitle, pfnCallback); if (pszFont) free(pszFont); GpiDestroyPS(hps); } } DevCloseDC(hdc); } } return irc; }