Note that the files below are useless unless you have a Synex ViewPort version 2.2 developer package, so do not waste bandwidth in downloading the DLLs or binaries as they require files included only with aforementioned package.
Last updated on March 22, 2001.
Per request, below is a step by step description on how to do an MFC/VC++ integration.
BOOL CAppWApp::InitInstance() {
// Initialize Synex ViewPort
SvOpenAPI(m_hInstance,
"Synex ViewPort MFC App",
NULL, /* pszCatalogFile */
NULL, /* pszEntityRCFile */
NULL, /* pszSetupFile */
NULL, /* pszTempHome */
NULL); /* pszSDATAHome */
...
int CAppWApp::ExitInstance() {
// Close the ViewPort API
SvCloseAPI();
return CWinApp::ExitInstance();
}
LRESULT CAppWView::WindowProc(UINT message, WPARAM wParam,
LPARAM lParam)
{
LRESULT Result = 0L;
// Let ViewPort "peek" at all messages to this window
if (SvPeekWindowProc(m_hWnd, message, wParam, lParam,
(long *) &Result))
{
// If ViewPort handled the event, just pass the result
return Result;
}
return CView::WindowProc(message, wParam, lParam);
}
CAppWDoc::CAppWDoc()
{
m_hDoc = HNIL;
}
In the CAppWDoc destructor, call SvUnlock if the variable is non-NIL.
CAppWDoc::~CAppWDoc()
{
if (m_hDoc) SvUnlock(m_hDoc);
}
BOOL CAppWDoc::OnOpenDocument(LPCTSTR lpszPathName)
{
// In non-MDI apps, the CSGMLDoc object is reused...so
// unlock any *previous* handle
if (m_hDoc) { SvUnlock(m_hDoc); m_hDoc = HNIL; }
if (!CDocument::OnOpenDocument(lpszPathName))
return FALSE;
m_hDoc = SvOpenNameDoc(lpszPathName, NULL, HNIL, NULL, 0);
return (BOOL) m_hDoc;
}
void CAppWView::OnInitialUpdate()
{
CView::OnInitialUpdate();
HDOC hDoc = GetDocument()->m_hDoc;
SvDisplayPage(m_hWnd, hDoc, HNIL, HNIL, HNIL, HNIL, HNIL,
OPT_OCCDENS | OPT_AUTOSHRINK | OPT_SELECTTEXT
| OPT_BACKTRACK);
}
Now when you run the application, everything should work finewhen you open an SGML
document, it should be displayed in your window!
void CAppWView::OnEditCopy() {
// Copy the selected SGML text to the Windows clipboard
int nSelSize = SvGetSelSize(m_hWnd);
if (nSelSize) {
HANDLE hMem = GlobalAlloc(GMEM_MOVEABLE, nSelSize + 4L);
LPSTR pszCopy = (LPSTR) GlobalLock(hMem);
SvCopySel(m_hWnd, pszCopy, nSelSize);
GlobalUnlock(hMem);
if (::OpenClipboard(m_hWnd)) {
::EmptyClipboard();
::SetClipboardData(CF_TEXT, hMem);
::CloseClipboard();
}
}
}
void CAppWView::OnUpdateEditCopy(CCmdUI* pCmdUI) {
BOOL fHasUniqueSel = (BOOL) SvGetSelSize(m_hWnd);
pCmdUI->Enable(fHasUniqueSel);
}
With these commands, the "Copy" button and the Copy menu item should be
enabled/disabled depending on if the user has selected any text in the document window.
Clicking the copy button should copy the
selected text to the clipboard.
Q: How do I programmatically select all the text in a document for copying?
A: You can use SvNewSel() to set the text selection to a specific span and offset, and you can get the span and offset of the current document with SvGetDocSpan(). Use SvCopySel() to copy the selection's textual content.
Q: How can we get the title of the current selected element in the navigator window (DTD independent) including the generated enumeration?
A: SvGetNavCurrTag() will retrieve the "current" navigator tag, SvGetTagText() will get the text. The autonumbering is not included, as that is not considered "part of the document." If the information that a certain title is section "2.1.3" is considered vital, then that information should be included in the document instance and not generated dynamically. The autonumbering is just made by ViewPort on-the-fly, if the document or style sheet are edited the auto-numbering would probably be affected. Therefore, it is a design decision not to include generated text (neither from the navigator nor from the style sheets) when copying or creating .
Q: The SV demo browser is very rigid in its implementation of the Print Preview functionality; for instance the paper size and orientation is hardcoded. Can you give some pointers on how to support this?
A: ViewPort doesn't really "know" that the orientation is landscape instead of portrait, all rotation is done by the printer driver. The width of the page will just be larger than the height. You can download a small MFC sample app that shows a little better how the preview can be done, see the MFC source. The MFC implementation of the Print Preview is more "complete" than the one in the SV sample application, it uses the specified settings for orientation (portrait/landscape) and paper size.
Q: How can we get all hits of a query in a list? The intention is to show a dialog box containing all hits together with the corresponding navigator title.
A: If you have made a document search, you can enumerate all search hits with SvEnumSels(), this will give you all the offsets in the document where you have search hits. From the offsets, you can get the spanning tags with SvGetOffsTag(), and from there once you have the HTAG handle, you can get pretty much anything.
If you are integrating ViewPort with a full-text database, you normally use the opposite approach: let the database handle all searching and just tell ViewPort what parts of the document to highlight as search results.
Q: How can we fold all entries in the navigator window?
A: Calling SvDisplayPage() again on the navigator page with the same HDOC/HNAV has that effect, it "resets" the page.
Q: Is there a way that I can tell if a branch in the navigator is open, so that if it's clicked on I can close it. All other branches in the navigator should remain open.
A: SvGetNavCurrTag retrieves the element corresponding to the current navigator entry. Once you have that, you can get some information about the tag (such as its open/closed mode) by using the SvMoveTagToNavTag() function:
BOOL SvMoveTagToNavTag( HPAGE hNavPage, HTAG hBodyTag, PBITS pbNavMode, PUINT puDepth, PUINT puNavHits);
Pass it a correct navigator page/tag and the addresses of some variables:
BITS bMode; UINT uDepth; UINT uNavHits; bSuccess = SvMoveTagToNavTag(hNavPage, hTag, &bMode, &uDepth, &uNavHits);
and it will fill in the values of some interesting data for the tag (such as the Open/Closed state, it is open if the NAV_OPEN bit in uDepth is set (uDepth & NAV_OPEN)). You can change the open/closed state of a navigator tag with the SvSetNavTagMode() function:
BOOL SvSetNavTagMode( HPAGE hNavPage HTAG hTag BOOL fOpen BOOL fRebuild)
If you have problems getting this to work, remember that the Navigator items normally have two parts, a block HTAG and a title HTAG (the one you click), and that it is the "block" you should open/close.
Q: We want to enable an user to remove links as fast as possible. Our idea is to automatically remove link ends just by removing a link starting anchor. However, when a link end is removed, its beginning is turned into an annotation. Is there a way, starting from a link anchor, to retrieve the "whole" link in order to delete it?
A: All anchors connected to a specific anchor can be retrieved by calling SvEnumAnchorLinks().
Q: I would like to examine exactly what ViewPort stores with respect to attributes as declared in the DTD.
A: Run the following sample code on a DTD of your choice; just call the EnumDTDAttributeDecls() function with the file name of a DTD and watch the results.
PSZ apszAtToS[] = {
"NONE",
"(GROUP)",
"CDATA",
"ENTITY",
"ENTITIES",
"ID",
"IDREF",
"IDREFS",
"NAME",
"NAMES",
"NMTOKEN",
"NMTOKENS",
"NOTATION",
"NUMBER",
"NUMBERS",
"NUTOKEN",
"NUTOKENS"
};
PSZ AttValToStr(PSZ pszAttVal) {
if (pszAttVal == ATTV_IMPLIED) return "#IMPLIED";
if (pszAttVal == ATTV_REQUIRED) return "#REQUIRED";
if (pszAttVal == ATTV_CONREF) return "#CONREF";
if (pszAttVal == ATTV_CURRENT) return "#CURRENT";
return pszAttVal;
}
XDATA SvPROC EnumElements(XDATA xEnumData, XDATA xData) {
ELEMENTREC pR = (ELEMENTREC) xEnumData;
int j;
for (j = 0; j < pR->nAttCount; j++) {
char szBuf[255];
sprintf(szBuf, "Element: %s, Attribute: %s, Type: %s, Default Value: %s",
pR->pszGI,
pR->papszAttName[j],
apszAtToS[pR->paAttType[j]],
AttValToStr(pR->papszAttDefValue[j]));
MessageBox(0, szBuf, "Results", 0);
}
return 0;
}
void EnumDTDAttributeDecls(char *pszDTDFile) {
HDTD hDTD = SvOpenNameDTD(pszDTDFile, NULL, FALSE, NULL, 0);
if (hDTD) {
SvEnumDTDElements(hDTD, EnumElements, 0);
SvUnlock(hDTD);
}
}
Q: How can we implement a general query index on several documents loaded separately?
A: If you need full-text search capabilities over vast quantities of data, you should integrate ViewPort with a text database or some sort of indexing software. ViewPort has been integrated with most SGML databases (Verity, Open Text, SGML Server, Swish, Fulcrum, etc), and it was designed with such things in mind. With respect to retrieval, the key is of course to use the customizable Entity Manager, intercepting entity resolution at either the public or system identifier level.
The built-in searching of ViewPort is limited to the contents of a loaded document, so if you have all documents loaded, you could search your documents on-by-one, but there is no substitute for indexing software as the data volume becomes substantial.
ViewPort by itself can only search one document at a time. For document sets, you can either pre-process your documents using ViewPort or some other technology in conjunction with ViewPort, or do a dynamic search across documents. All of these approaches have been made by our customers.
There is a (somewhat cryptically named) API function SvSelectIDIHits to select "search hits" in a browsed document, where the hits are indicated by processing instruction begin/end pairs - this is to allow for external search engines to indicate regions or words to highlight that do not correspond to SGML elements, or where the sought text is being matched using e.g. synonym searches: looking up "animal" to find "dog" and "cat" in the text.
The PIs are not the only way of dealing with this - but certain software can easily generate them when you extract the contents from the text database (which is why we implemented this method in the first place). Note that there are functions in the ViewPort API dealing with highlighting spans (see e.g. SvAddSel and SvNewSel), so the suggested approach is not the only one, but one possible way. The toolkit is flexible.
Q: We have external "homemade" hyperlinks in our documents. As these are not based on any public convention or linking standard, how do we make them look and behave like regular hyperlinks? See the enclosed DTD fragment below:
<!ELEMENT extern.link - - ANY>
<!ATTLIST extern.link refid NAME #REQUIRED
file.name CDATA #REQUIRED>
The attribute file.name is the file name of the target document and refid denotes an ID attribute in the same. As the link is custom to our application and the file name is not declared as an entity, we need to implement the link resolution and entity retrieval ourselves.
A: ViewPort supports any element-based form of hyperlink. In order to make the element extern.link look and behave like a hyperlink in ViewPort, you should declare it to be a custom hyperlink. This will typically take the form of a processing instruction such as
<?TAGLINK extern.link "EREF">
In the above declaration, the element extern.link is declared to be a hyperlink with the hyperform EREF custom to your application. This declaration makes ViewPort display all extern.link element contents as hyperlinks, turning the mouse pointer into a pointer etc. Your hyperlink criteria can equally be based on elements with certain attributes or attribute values; see the section on the CB_HYPERCALL callback in the documentation.
As this declaration modifies your DTD or document instance remember that even when it's not possible to modify the DTD or the documents in reality, you can always work around such a restriction by redefining or augmenting either of these through the use of a temporary file or RAM buffer, from which the contents are sent to ViewPort.
When the end-user clicks on the extern.link element in the browser, activating the CB_HYPERCALL callback, the HTAG to the actual element and its hyperform are passed on too. At this point you can access the element attributes needed to resolve the hyperlink and do your custom processing. SvGetTagAttVal() takes as parameter an HTAG and attribute name and returns the corresponding attribute value. You can thus retrieve the file.name and refid attribute values in this way.
Once you have the file name, you can use e.g. SvOpenNameDoc() to open the file. In order to scroll to the element with an ID value of refid you have several options. The simplest is probably to use SvGetIDTag() followed by SvLocateObj(). If you want to call SvDisplayPage() yourself, note that there is a specific parameter for positioning at a specific tag (hAtTag). Alternatively, you can do a search, for instance using TEI pointers and the value of the refid attribute SvMoveTagToTEI(hTag, "ID (...)"), or using SvSearch().
Q: Is it possible to display tables and graphics behind icons?
A: Yes, this is just a Style Sheet setting, any element can be displayed "behind" an icon, revealed on click. Your application must act on the CB_GET_VIEWPAGE callback in order to make this work, providing ViewPort with an HPAGE in which to display the contents of the tag.
Q: Is it possible to generate a navigator window on the top of several independent SGML-instances? Think about the AECMA Data Module concept.
A: Yes, but it does require some work. The normal approach is to create a "virtual" document containing only the headings and administrative data. This can e.g. be done on-the-fly by assembling a document from the result of a database query. Tell ViewPort to display that document as a navigator, and register the CP_POSIT_NAV callback. This way, your application will be called when the user clicks in the navigator, and can retrieve the right document. You would typically have attributes or elements in the custom navigator document, whose data is used to fetch the corresponding document.
In this particular case, a (simpler) approach would be to create, on-the-fly, a compound document that contains the other SGML instances (the SGML parser in ViewPort is not a validating parser) and load that document instead. This would work fine as long as the documents are rather small; if each of the instances are multi-megabyte documents, this will not work, then the superior way is to have a text database to store the data.
Scenario: When the user clicks on a point in a zoom window, a new zoom window should be launched with the zoom factor 512, and the point in the graphic that was clicked should be centered in the new window. I'm a bit lost wrt to coordinates.
First of all, note that we have four coordinate system at work here. (The abbreviation CS will be used for "coordinate system" from here on).
Second, note that the coordinates supplied to SvDisplayZoomPage specifies the distance from the origin of CS[3] to the origin of CS[4]. Or, simpler, [uDX, uDY] specifies where the position of the top left corner of the window is on the graphic.
Fortunately, all four CS's are using the same measurement unit: pixels. So, what you have to do to make this work is:
Here is sample source for this:
void ZoomPtConvert(
HPAGE hPage, // The zoom page the user clicked in
int nPgX, // The mouse x-position
int nPgY, // The mouse y-position
int nWndW, // The width of the *new* zoom window
int nWndH, // The height of the *new* zoom window
int nWndZoom, // The zoom factor of the *new* zoom window (i.e. 512!)
int& rnDX, // The sought nDX for SvDisplayZoomPage
int& rnDY) // The sought nDY for SvDisplayZoomPage
{
// Get current zoom factor (in *old* window)
int nPgZoom = SvGetZoomPageFactor(hPage);
// Translate nPgX, nPgY from window coordinates to image coordinates
nPgX += SvGetZoomPageXOffs(hPage);
nPgY += SvGetZoomPageYOffs(hPage);
// Convert nPgX, nPgY from coordinate system of image (in hPage) to
// coordinate system of new image (Note: we have to be very careful
// about overflows here. Perhaps the int's should really be casted
// to floats?)
nPgX = int((long(nPgX) * long(nWndZoom) + nPgZoom / 2) / long(nPgZoom));
nPgY = int((long(nPgY) * long(nWndZoom) + nPgZoom / 2) / long(nPgZoom));
// If nPgX, nPgY should be centered in new window, then the offset to
// the windows left top corner should be:
nPgX -= nWndW / 2;
nPgY -= nWndH / 2;
// Ok. nPgX and nPgY should now contain the correct values for nDX, nDY
rnDX = nPgX;
rnDY = nPgY;
}
Q: I would like to display a document tag by tag or group of tags by group of tags. For example, if you had a SGML document containing the following:
<header stuff> ... </header> <step>This is the first step</step> <step>This is the second step</step> <parts><partli> <nomen>Gasket</nomen><idno>33-75A</idno><qty>2</qty> </partli></parts>
The viewer would show a blank page except for the words "This is the first step". The user would push a "next" button, and then the current words would be replaced by "This is the second step". When the user again pushes the "next" button, the whole part would show up, replacing the step text: Gasket 33-75A 3.
A: The principle is very easy. You should simply restrict the view when calling SvDisplayPage to the "limiting element", i.e. in the:
The view is restricted by the hSubTag parameter:
BOOL SvDisplayPage(
HPAGE hPage,
HDOC hDoc,
HSSH hSheet,
HNAV hNav,
HPAGE hPageMain,
HTAG hSubTag, // This param should point at the restricted element
HTAG hAtTag,
BITS bPageOpt);
But although this is simple in principle, it often requires a lot of thinking while designing the browser. Below are a few functions you may find useful.
Note that the function SvMoveTagToTEI can also be very useful, since it allows you to pick arbitrary elements from the documents, based on GI or attribute names/values:
BOOL SvMoveTagToTEI(HTAG hTag, PSZ pszTEI);
As an example, the action when the user presses the "Next" button could be:
MOVE_RESULT MoveToNext(HPAGE hPage)
{
MOVE_RESULT Result; // An invented "result code"
HTAG hTag = SvGetViewTag(hPage); // Get current view element
if (hTag) {
// Move to next sibling
if (SvMoveTagToNext(hTag)) {
HDOC hDoc = SvGetPageDoc(hTag);
HSSH hSheet = SvGetPageSheet(hPage);
SvDisplayPage(
hPage,
hDoc,
hSheet,
HNIL, // No navigator, or?
0, // Hence, no main page?
hTag,
HNIL, // Initially positioned at top, or?
SvGetPageOptions(hPage));
SvUnlock(hSheet);
SvUnlock(hDoc);
Result = MOVE_SUCCESS;
} else
Result = MOVE_NO_NEXT_TAG;
SvUnlock(hTag);
} else
Result = MOVE_NO_PAGE_TAG;
return Result;
}
NOTE: This code has not been compiled and may contain mistakes.
Q: I would like to open a non-sgml document like an MS Word document from Synex ViewPort. How should I do this?
A: First, define a notation type:
<!NOTATION word SYSTEM>
Then, define the entity as being of that notation type
<!ENTITY sample SYSTEM "sample.doc" NDATA word>
Include the entity somewhere in your document, either as a general entity reference or as an entity attribute:
&sample; or <XXX ent=sample>
Then, for specifying the actual application to launch for all notation entities of this type, add a line such as this to the SETUP file (normally placed in the VPHOME directory):
ENTITY word LAUNCH "WINWORD \file"
There you specify a "command line" for this notation type. The \file argument will be extended to the full path of the entity before it is passed to the application you specify (WINWORD in this case). If you want to launch Word without a document, just remove the \file part from the command line. Naturally, the application to be launched must be located in a location specified in the PATH environment variable (just as if it had been specified at a command prompt level.
I'm using ViewPort to display HTML and SGML. In viewing HTML files, a recurring problem is that the graphics markup needs to be modified before ViewPort (or any conforming SGML application, for that matter) knows how to display them. To be able to display a graphic marked up in HTML as
<IMG SRC="picture.bmp"> (1)
I need to do the required SGML declarations such as:
<!ENTITY picture SYSTEM "picture.bmp" NDATA bmp>
and change the HTML to
<IMG ENTITY=picture> (2)
Q: Is there any way to display the HTML shown in (1) above, without changing the source to (2)?
A: Yes. The simplest way is to use DEFAULT entities. (This approach forces you to edit the HTML DTD, but you won't have to declare each entity).
When you use DEFAULT entities, you define what can be viewed as an "entity declaration template", which all undeclared entities will use. The steps to achieve this is:
<!ATTLIST IMG
...
SRC ENTITY #IMPLIED
...
>
<!ENTITY #DEFAULT SYSTEM NDATA bmp>
When the markup <IMG SRC="picture.bmp"> now is processed, picture.bmp will be viewed as an entity name. Since this entity is undeclared, it will use the template:
<!ENTITY picture.bmp SYSTEM NDATA bmp>
Since the picture.bmp entity doesn't have an explicit SYSTEM identifier, Synex ViewPort will use the entity name itself as system identifier.
There is in principle (at least) one drawback to this scheme: All entities must now be BMP graphics. There is however a way to address this, using the notation RGR which stands for raster graphic supported by ViewPort. When ViewPort encounters an RGR entity, it simply calls the RasterMaster DLL, which will detect the actual format. The default entity should hence be declared as:
<!ENTITY picture.bmp SYSTEM NDATA rgr>
Alternatively, use the display manager API of version 2, which combines and improves the graphic manager and widget interface of previous versions. This API is in fact already used for an identical task, and works very well. The only drawback with the second alternative is that you will have to handle both image loading and display yourself. However, if you license a third party graphics toolkit, you can have this working in a single day. Contact Synex support if you need sample source.
Q: How do I address HTML-style links best?
A: You can make the A element a customized hyperlink. You will then get a callback whenever the user clicks on the element. This allows you to handle the link yourself, retrieving and processing the HREF attribute. Implementing this requires about a page of source.
Q: I have a requirement from the content authors to display animations (animated GIF files) Does ViewPort support these types of graphics? I have read that the Display Manager allows integration of any rectangular objects into the browser. Is this how animations could be displayed? If so, where can I find information which will help me to implement this?
A: Synex ViewPort version 2 supports animated TIFF graphics. Though Snowbound Software now supports animated GIFs, the version of their graphics DLL currently supplied with ViewPort does not. Also, as noted in the documentation, the formats GIF and compressed TIFF may require licensing from Unisys Corp., who own the LZW compression algorithm used in those formats. However, if you have a toolkit supporting animated GIFs, there is no technical problem to integrate it in ViewPort. The display manager can use either elements or entities. See the Windows-specific documentation for details on the Display Manager.
Q: I would like to implement popup style hotwords to handle e.g. glossary terms. When you click on a word, a rectangular box appears with the definition of the word. It would also be nice if the box disappeared upon release of the mouse click. How should this be implemented?
A: The easiest way is to use customized hypertext. The drawback with this approach is that you won't get the callback until the button is released (which will make it hard to have the box disappear the way you want.
An alternative method would be to peek at all WM_LBUTTONDOWN events, and use SvPickTag() to check if the element below the mouse pointer is a "hotword element". If not, the event must be passed on to ViewPort, or mouse-related actions such as selecting text or following links will cease to work.
If it is a "hotword", create a window with the right properties (no caption, just a thin border) and tell ViewPort to display the appropriate information. Use the ViewPort API to navigate the HTAG you got from SvPickTag() to the HTAG that contains the part of the document you want displayed, and pass that to SvDisplayPage() to restrict the view to that particular element.
You should probably create the window as "hidden" and let ViewPort do its formatting; use the ViewPort callback CB_FIT_PAGE to shrink (since the amount of text is probably small) the window to the size that ViewPort needs. Then move the window to a position slightly above the mouse pointer and show it. You should then call SetCapture() on the window. When you later get the WM_LBUTTONUP event, just delete the window.
Q: After a WM_LBUTTONDOWN/WM_LBUTTONUP mouse event sequence we want to display a context specific popup menu to show the contents of a glossary. This works fine, but if the user clicks on an hyperlink we want to avoid this functionality. What do we have to do?
A: The easiest way is to use the right mouse button for the menu, ViewPort will never touch that button and most Windows users seem to find that natural today since Windows 95 made it "standard" to right-click to open a popup menu.
If you have to use the left button (for backwards-compatibility with an old system), you could disable the link traversal (by adding a CB_EXEC_DOCLINK / CB_GOTO_DOCLINK callback) and using SvPickTag() / SvFollowLink() to do the actual link traversal.
Q: For zoom images, ViewPort uses the background color defined in the window class instead of a white brush. Do you know a way to define the brush with MFC? Because we use templates, it is not trivial: MFC is doing a lot of job for us but it seems that there is no background color defined in our case.
A: You should use MFC's AfxRegisterWndClass to register a special ViewPort class, and then in the method CWnd::PreCreateWindow (for the windows that ViewPort controls), set the name of the Windows class to the value returned by AfxRegisterWndClass.
Q: I would like to find a conjunction of 3 words inside a specific tag, such as finding "dog cat fish in <ANIMAL>". What is the right syntax to find those words?
A: If you want to highlight all the occurrences of the words "dog", "cat", and "fish" within <ANIMAL> elements, use the expression:
"(dog or cat or fish) in <ANIMAL>"
If you want to highlight all <ANIMAL> elements that contain either of the three words, try
"<ANIMAL> cont (dog or cat or fish)"
If you want to search for the string "dog cat fish" in sequence, use
"dog cat fish IN <ANIMAL>"
If you want to find the <ANIMAL> elements that contain all of the words "dog", "cat", and "fish" in any order, then try the somewhat more complicated expression:
(<ANIMAL> cont "dog") AND (<ANIMAL> cont "cat") AND (<ANIMAL> cont "fish")
Q: We would like to make a "case insensitive" AND accent insensitive search on a SGML document, i.e. if you search for "boitier" (French for gear box) we would like SvSearch to match:
Is there a good way to do this?
A: Not directly, as all the SDATA entities are (through the SDATA.MAP file) mapped to a proper platform-dependent character value when the document is parsed. You would thus need to implement an "accent insensitive" feature within your application, on top of ViewPort. If the user has selected the "accent insensitive" checkbox in your search dialog, you preprocess the search string, scanning it from start to end and replacing every occurrence of î (or any of the other i-characters) with "[iîïìí]". Using this kind of pre-processing, you could generate a hideous search string like "b[oôÔO][IÎiî]t[IÎiî][EeÊêéè]r" to be passed to ViewPort, but the user would never see it.
Regarding case, the normal ViewPort case insensitive searching would take care of the accented characters as well.
When a user clicks on an iconized DATA entity, ViewPort calls either CB_NOTATION or CB_GET_ZOOMPAGE unless the entity is an already loaded image, in which case CB_RAISE_PAGE is called. CB_NOTATION is only invoked for notations unknown to ViewPort.
Q: I'm a bit unclear on the use of callbacks with respect to notations and link resolution. Can you exemplify?
A: CB_NOTATION handles the situation where ViewPort says "OK, I give up - you do your best." CB_NOTATION is only invoked with notations unknown to ViewPort. I.e., the host application knows that if it doesn't handle this callback, nothing will happen. The callback CB_EXEC_NOTATION is invoked whenever a user clicks on an iconized data entity, to intercept the launching of all entities. CB_EXEC_NOTATION is for entities what the CB_EXEC_DOCLINK is for markup links.
CB_EXEC_DOCLINK is invoked prior to CB_GOTO_DOCLINK, allowing the host application to intercept the link resolution and define application-specific link semantics.
With CB_GOTO_DOCLINK, ViewPort essentially says "I have resolved the link, and I'm going to handle it, unless you want to do it?", allowing the host application to handle the traversal any way it sees fit.
Q: How do I format lists, for instance unordered lists with bullets?
A: Use e.g. the Symbol font (which has a nice bold bullet glyph) in Text Before for the list item. You can insert the bullet through the numeric character reference \183. You will probably want to fiddle a bit with left indent (for the first line) and space left, especially if you have nested lists. You would typically add +=20 points to the list itself, and have a bit of negative indent as first line indent so that the bullet is pulled out into the margin and that the list items align nicely when their entries are longer than a single line. The actual values will depend on your choice of font. Say that you have list elements LI inside of a list L, and are using 11 points Times New Roman. Then the following values would look good:
LI: Space Indent: =-8
Space Left: =+8
Text Before: \183
Text Before font family: Symbol
Text Before font color: #666666 (a shade of gray)
L: Space Left: +=20
Space Above: 3
Space Below: 3
Q: How would you define a style sheet for definition lists, typically using the markup below?
<demo>This is a definition list: <dl> <dt>Dog</dt> <dd>Furry animal, man's best friend. Wags tail when happy.</dd> <dt>Cat</dt> <dd>Furry animal, another one of man's best friend. Doesn't wag tail when happy.</dd> </dl>and this is the first line of text after the list. </demo>
A: Use Synex ViewPort's generic table handling (see the enclosed sample). In the style sheet, assign the dl element to be a table (without a grid), and assign dt to be a row, and dd to be a table cell.
You might also want to put the definition term in bold face with some (say, 5 pt) vertical space between the definitions, and 5 pt vertical space after the definition list, so that everything will be evenly spaced. If you can tag your definition lists freely and are using ViewPort version 2 (or above) for presentation, use a CALS table with no frame, and put the contents of the definition term into a first column with a fixed column width, with the definition going into a column with a relative (dynamic) width. This will give you precise control of the presentation.
Q: Can you give some examples with regards to nested lists and various types of autonumbering, e.g. in the style of
1. Scope
A. This covers the ....
2. Application
A. The term "klutz" ....
B. Wherever the term "gizmo" ...
(1) North of ...
(2) South of ...
A: Alphabetic and linear numbering are easy to achieve through the style sheet functionality. The third kind of numbering (2)is not directly supported. However, ViewPort allows you through callbacks to add up to three types of numbering schemes for the application, so you can do pretty much anything in practice (but using some programming effort). Alternatively, use the Text Before/After \gen() dynamic text insertion functionality. The downside of the latter methods is that the autonumbering will then become application dependent.
General advice:
The enclosed sample illustrates the following:
Getting the right indentation is the trickiest part, it has to be fine-tuned for a particular font and font size. However, as the values are in points, you will get the same effect in hard copy as you have on-screen.
Q: ViewPort provides me with some pre-defined dialogs. For instance, when the tags are showing in a browser window, there is a box that the user can click on to see the attributes for an element. The dialog that comes up to show the user the attributes has buttons for both OK and Cancel, both of which do the same thing (dismiss the dialog box). How do I change this behavior?
A: The ViewPort dialogs (annotation, link, etc) are supplied to simplify the integrator's work but can all be suppressed or customized. The callback CB_LAUNCH_ATTLIST allows you to design an alternative dialog or supply a dialog in another language (and to monitor its launching generally). However, under Windows you can use the CB_INIT_DLG callback instead which is a simpler solution. It is called at WM_INITDIALOG time, and you get a HWND handle to the Windows dialog and can use Windows functions such as GetDlgItem() to get a handle to the Cancel button and hide it.
The reason for the standard dialog design is that it is a generic list
dialog,
used by ViewPort ina all situations where it wants to present the user with a
list, not just in the Show Attributes
case. So in some cases (e.g. where you have multiple
targets from a link), the selected item is used by ViewPort, and the 2-button Cancel/OK
layout is needed.
Below is a code snippet to display only one Close button. Just add snippet one to your app and call snippet two (the SvAddCallback() calls) where you add all your other callbacks at startup.
The code uses the CB_INIT_DLG callback, but also the CB_LAUNCH_ATTLIST callback, just to set a global flag so that we don't remove the OK button in cases where it is needed.
The code hides the OK button and renames the Cancel button to Close
,
andas a special bonusit also centers the close button in the dialog.
/*************/
/* */
/* SNIPPET 1 */
/* */
/*************/
BOOL g_fIsLaunchingAttList = FALSE;
CBRET SvPROC CB_InitDlg(
HPAGE hPage,
CBMSG uMsg,
XDATA xP1,
XDATA xP2,
XDATA xData)
{
if (!strcmp((char *) xP1, "DlgList") && g_fIsLaunchingAttList)
{
HWND hOKBtn = GetDlgItem(hPage, IDOK);
HWND hCancelBtn = GetDlgItem(hPage, IDCANCEL);
if (hOKBtn && hCancelBtn)
{
int x, y;
RECT rDlg, rBtn, rDlgG, rBtnG;
// Hide the OK
ShowWindow(hOKBtn, FALSE);
// Change the name
SetWindowText(hCancelBtn, "Close");
// Center the button...
GetClientRect(hCancelBtn, &rBtn);
GetClientRect(hPage, &rDlg);
GetWindowRect(hCancelBtn, &rBtnG);
GetWindowRect(hPage, &rDlgG);
x = (rDlg.right - rBtn.right) / 2;
y = (rBtnG.top - rDlgG.top) -
((rDlgG.bottom - rDlgG.top) - rDlg.bottom);
MoveWindow(hCancelBtn, x, y, rBtn.right, rBtn.bottom, FALSE);
// All done, turn off the flag...
g_fIsLaunchingAttList = FALSE;
}
}
return CBRET_FALSE;
}
CBRET SvPROC CB_LaunchAttList(
HPAGE hPage,
CBMSG uMsg,
XDATA xP1,
XDATA xP2,
XDATA xData)
{
// Just a dummy, set the global flag...
g_fIsLaunchingAttList = TRUE;
return CBRET_FALSE;
}
And the second snippet:
/*************/ /* */ /* SNIPPET 2 */ /* */ /*************/ SvAddCallback(0, CB_INIT_DLG, CB_InitDlg, 0); SvAddCallback(0, CB_LAUNCH_ATTLIST, CB_LaunchAttList, 0);
Q: I need to convert SGML instances to another representation. How can I use ViewPort's SGML parser to help me with this task?
A: You simply loop through the SGML document tree recursively, and output the contents (and any other information the other format needs). In code, this would be:
void LoopDoc(HDOC hDoc)
{
HTAG hTag = SvGetRootTag(hDoc); // Fetch the root element
if (hTag) {
LoopTree(hTag); // Loop through the document tree
SvUnlock(hTag);
}
}
void LoopTree(HTAG hTag)
{
// Preprocessing: For instance, use
// - SvGetTagGI() to get the element name
// - Retrieve attributes and their values:
// SvGetTagAttCount() returns the attribute count
// SvGetTagAtt() returns the n:th attribute's name
// SvGetTagAttVal() returns the n:th attribute's value
// SvGetTagAttDefVal() returns the n:th attribute's default value
// (n goes from 0 to SvGetTagAttCount() - 1)
// Process the element contents
if (SvMoveTagToChild(hTag)) {
// The element has children. Loop through them.
do {
LoopTree(hTag); // Examine children recursively
} while (SvMoveTagToNext()); // Go to next sibling
// Return to the direct ancestor
SvMoveTagToPar(hTag);
} else {
// The element is a leaf (no children). This is a good spot
// to check the contents.
// SvGetTagText() returns all textual contents. However,
// it is safer to check the actual amount of text using
// SvGetTagLoc(), and then retrieve the text piece by piece
// using SvGetDocText(). Remember that a terminating null
// character \0 is appended to the text. The buffer
// used to retrieve the text must reserve space for this
// character.
}
// Postprocessing: whatever is needed.
// If you are converting to another DTD, you could e.g. generate
// </tagname>
}
Q: How do I integrate third party graphics?
A: You use ViewPort's versatile Display Manager interface. The enclosed zip archive shows how to integrate Snowbound Software's excellent RasterMaster library (tested using the version 8 Platinum DLL).
The zip archive contains the following C++ source files:
In order to test this integration, simply unzip the archive to a directory of your choice and add the two .cpp files, dspmgr.cpp and dmgr-ifl.cpp to your project.
#ifdef __cplusplus
extern "C" {
#endif
void InitIFL();
void FiniIFL();
#ifdef __cplusplus
}
#endif
That should be it! To verify that you are using the right DLL, the code in dmgr-ifl.zip draws an 'X' across all imagesonce you have verified that everything works as it should, simply remove the call to DrawCrossedBorder() from CIFL_Img::DrawImg().
Q: Do you have any sample source regarding the use of the Display Manager of version 2?
A: Yes, see the enclosed zip archive. This source uses the Display Manager's C++ API, which in turn uses the C API, so this illustrates both. The zip archive contains the following files:
Regarding the C++ API:
The sample source:
Sample SGML source: