Mercurial > ~dholland > hg > ag > index.cgi
comparison anagram/vaclgui/helpview.cpp @ 0:13d2b8934445
Import AnaGram (near-)release tree into Mercurial.
author | David A. Holland |
---|---|
date | Sat, 22 Dec 2007 17:52:45 -0500 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:13d2b8934445 |
---|---|
1 /* | |
2 * AnaGram, A System for Syntax Directed Programming | |
3 * Copyright 1997-2002 Parsifal Software. All Rights Reserved. | |
4 * See the file COPYING for license and usage terms. | |
5 * | |
6 * helpview.cpp | |
7 */ | |
8 | |
9 #include <icoordsy.hpp> | |
10 #include <windows.h> | |
11 | |
12 #include "agcstack.h" | |
13 #include "agstack.h" | |
14 #include "agstring.h" | |
15 #include "ctrlpanel.hpp" | |
16 #include "dspar.hpp" | |
17 #include "dview.hpp" | |
18 #include "file.h" | |
19 #include "help.h" | |
20 #include "helpview.hpp" | |
21 #include "minmax.h" | |
22 #include "stacks.h" | |
23 #include "vaclgui-res.h" | |
24 #include "vaclgui.hpp" | |
25 | |
26 //#define INCLUDE_LOGGING | |
27 #include "log.h" | |
28 | |
29 | |
30 const int AgHelpView::defaultWindowHeight = 15; | |
31 AgBalancedTree<AgString> DrawingArea::traversedLinks; | |
32 AgBalancedTree<DrawingArea *> DrawingArea::activeViews; | |
33 | |
34 | |
35 DigSetter::Style DrawingArea::displayStyle[4] = { | |
36 DigSetter::Style(FontSpec::help, ColorSpec::helpText), | |
37 DigSetter::Style(FontSpec::help, ColorSpec::helpLink), | |
38 DigSetter::Style(FontSpec::help, ColorSpec::helpUsedLink), | |
39 DigSetter::Style(FontSpec::helpTitle, ColorSpec::helpText) | |
40 }; | |
41 | |
42 static int leading(IFont &f) { | |
43 return f.maxSize().height() + f.externalLeading(); | |
44 } | |
45 | |
46 HelpWord::HelpWord(char *text, int nChar, cint where, Style index) | |
47 : DigSetter::Dig(text, nChar, where, index) | |
48 , linktopic("") | |
49 , indent(0) | |
50 , bullet(0) | |
51 , forceBreak(0) | |
52 , noBreak(0) | |
53 , linkTraversed(0) | |
54 , highlight(0) | |
55 { | |
56 //LOGSECTION("HelpWord::HelpWord"); | |
57 //LOGV(AgString(text, nChar)); | |
58 //LOGV(nChar); | |
59 } | |
60 | |
61 HelpWord::HelpWord(const HelpWord &word) | |
62 : DigSetter::Dig(word) | |
63 , linktopic(word.linktopic) | |
64 , indent(word.indent) | |
65 , bullet(word.bullet) | |
66 , forceBreak(word.forceBreak) | |
67 , noBreak(word.noBreak) | |
68 , linkTraversed(word.linkTraversed) | |
69 , highlight(word.highlight) | |
70 { | |
71 //LOGSECTION("HelpWord::HelpWord(const HelpWord &)"); | |
72 } | |
73 | |
74 IRectangle HelpWord::rect(DigSetter::Style *style) { | |
75 IFont &font = style[styleIndex].font; | |
76 int above = font.maxAscender(); | |
77 int below = font.maxDescender(); | |
78 IPoint location(where.x, where.y - above); | |
79 ISize size(width, above+below); | |
80 return IRectangle(location,size); | |
81 } | |
82 | |
83 DrawingArea::DrawingArea(IWindow *owner_, AgString topic_) | |
84 : IStaticText(nextChildId(), owner_, owner_) | |
85 , windowFont(FontSpec::help) | |
86 , margin(windowFont.charWidth('M')) | |
87 , paraLeading((windowFont.maxSize().height() + | |
88 windowFont.externalLeading())/2) | |
89 , topic(topic_) | |
90 , setter(this, displayStyle) | |
91 , painting(0) | |
92 , popUpMenu(this, nextChildId()) | |
93 , refreshAction(*this, refreshAllLinks) | |
94 , frameWindow(0) | |
95 , highlightText(0) | |
96 , nHighlight(0) | |
97 , fingerCursorSet(0) | |
98 , fingerCursor(IResourceLibrary().loadPointer(IDI_FINGER_CUR)) | |
99 , helpShowing(0) | |
100 , rightButtonState(buttonIdle) | |
101 { | |
102 LOGSECTION("DrawingArea::DrawingArea"); | |
103 IWindow *f; | |
104 for (f = parent(); !f->isFrameWindow(); f=f->parent()) { | |
105 /* nothing */ | |
106 } | |
107 frameWindow = (AgFrame *) f; | |
108 //LOGV(int(f)); | |
109 | |
110 int i; | |
111 for (i = 0; i < 256; i++) { | |
112 charWidth[i] = windowFont.charWidth(i); | |
113 } | |
114 activeViews.insert(this); | |
115 LOGV((int) this); | |
116 #ifdef INCLUDE_LOGGING | |
117 for (i = 0; i < activeViews.size(); i++) { | |
118 //LOGV((int) activeViews.sortedItem(i)); | |
119 //LOGV(activeViews.sortedItem(i)->topic); | |
120 LOGV((int) activeViews[i]) LCV(activeViews[i]->topic); | |
121 } | |
122 #endif | |
123 //paraIndent = windowFont.charWidth('M') + margin; | |
124 paraIndent = margin; | |
125 helpText = getHelpText(topic.pointer()); | |
126 links = findLinks(helpText); | |
127 LOGV(links.size()) LCV(activeViews.size()) LCV(topic); | |
128 sortedLinks = sortLinks(links); | |
129 setMinimumSize(calcMinimumSize()); | |
130 LOGV(minimumSize().asString()); | |
131 int minWidth = minimumSize().width() + 2*margin;; | |
132 int maxWidth = IWindow::desktopWindow()->size().width(); | |
133 measure = 56*avgCharWidth() + 2*margin; | |
134 if (measure < minWidth) { | |
135 measure = minWidth; | |
136 } | |
137 if (measure > maxWidth) { | |
138 measure = maxWidth; | |
139 } | |
140 LOGV(measure); | |
141 measureText(); | |
142 preferredSize = ISize(longestLine, nLines); | |
143 | |
144 initPopUp(); | |
145 | |
146 ICommandHandler ::handleEventsFor(this); | |
147 IMenuHandler ::handleEventsFor(this); | |
148 //IMenuHandler ::handleEventsFor(frameWindow); | |
149 IMouseHandler ::handleEventsFor(this); | |
150 IPaintHandler ::handleEventsFor(this); | |
151 //AgHelpHandler ::handleEventsFor(&popUpMenu); | |
152 AgHelpHandler ::handleEventsFor(frameWindow); | |
153 //LOGS("Help handler attached"); | |
154 } | |
155 | |
156 | |
157 DrawingArea::~DrawingArea() { | |
158 LOGSECTION("DrawingArea::~DrawingArea"); | |
159 LOGV((int) this); | |
160 #ifdef INCLUDE_LOGGING | |
161 for (int i = 0; i < activeViews.size(); i++) { | |
162 //LOGV((int) activeViews.sortedItem(i)); | |
163 //LOGV(activeViews.sortedItem(i)->topic); | |
164 LOGV((int) activeViews[i]) LCV(activeViews[i]->topic); | |
165 } | |
166 #endif | |
167 int flag = activeViews.remove(this); | |
168 assert(flag); | |
169 #ifdef INCLUDE_LOGGING | |
170 for (i = 0; i < activeViews.size(); i++) { | |
171 //LOGV((int) activeViews.sortedItem(i)); | |
172 //LOGV(activeViews.sortedItem(i)->topic); | |
173 LOGV((int)activeViews[i]) LCV(activeViews[i]->topic); | |
174 } | |
175 #endif | |
176 LOGV(topic) LCV(activeViews.size()); | |
177 ICommandHandler ::stopHandlingEventsFor(this); | |
178 IMenuHandler ::stopHandlingEventsFor(this); | |
179 //IMenuHandler ::stopHandlingEventsFor(frameWindow); | |
180 IMouseHandler ::stopHandlingEventsFor(this); | |
181 IPaintHandler ::stopHandlingEventsFor(this); | |
182 //AgHelpHandler ::stopHandlingEventsFor(&popUpMenu); | |
183 AgHelpHandler ::stopHandlingEventsFor(frameWindow); | |
184 } | |
185 | |
186 void DrawingArea::reset() { | |
187 LOGSECTION("DrawingArea::reset"); | |
188 disableUpdate(); | |
189 windowFont = FontSpec::help; | |
190 setFont(FontSpec::help); | |
191 for (int i = 0; i < 256; i++) { | |
192 charWidth[i] = windowFont.charWidth(i); | |
193 } | |
194 margin = windowFont.charWidth('M'); | |
195 //paraIndent = windowFont.charWidth('M') + margin; | |
196 paraIndent = margin; | |
197 paraLeading = (windowFont.maxSize().height() + | |
198 windowFont.externalLeading())/2; | |
199 LOGV(nWords); | |
200 LOGV(word.size()); | |
201 int minWidth = minimumSize().width() + 2*margin;; | |
202 int maxWidth = IWindow::desktopWindow()->size().width(); | |
203 measure = 56*avgCharWidth() + 2*margin; | |
204 if (measure < minWidth) { | |
205 measure = minWidth; | |
206 } | |
207 if (measure > maxWidth) { | |
208 measure = maxWidth; | |
209 } | |
210 /* | |
211 for (int j = 0; j < nWords; j++) { | |
212 setter.measureDig(word[j]); | |
213 } | |
214 remeasureText(); | |
215 */ | |
216 helpText = getHelpText(topic.pointer()); | |
217 measureText(); | |
218 //remeasureText(); | |
219 enableUpdate(); | |
220 show(); | |
221 } | |
222 | |
223 DrawingArea &DrawingArea::copyTo(IClipboard &clipboard) { | |
224 LOGSECTION("DrawingArea::copyTo"); | |
225 int i; | |
226 AgCharStack charStack; | |
227 LOGV(nWords); | |
228 for (i = 0; i < nWords; i++) { | |
229 if (word[i].indent) { | |
230 charStack.push(" "); | |
231 } | |
232 charStack.push(word[i].text, word[i].textLength); | |
233 if (word[i].forceBreak) { | |
234 charStack.push("\r\n\r\n "); | |
235 } | |
236 else { | |
237 charStack.push(' '); | |
238 } | |
239 } | |
240 clipboard.setText(charStack.popString().pointer()); | |
241 return *this; | |
242 } | |
243 | |
244 void AgHelpView::onFontChange() { | |
245 LOGSECTION("AgHelpView::onFontChange"); | |
246 disableUpdate(); | |
247 dataArea.reset(); | |
248 doLayout(); | |
249 enableUpdate(); | |
250 show(); | |
251 } | |
252 | |
253 void DrawingArea::refreshAllLinks() { | |
254 LOGSECTION("DrawingArea::refreshAllLinks"); | |
255 int i; | |
256 LOGV(topic); | |
257 LOGV(activeViews.size()); | |
258 for (i = 0; i < activeViews.size(); i++) { | |
259 //LOGV(activeViews.sortedItem(i)->topic); | |
260 LOGV(activeViews[i]->topic); | |
261 //activeViews.sortedItem(i)->refreshLinks(); | |
262 activeViews[i]->refreshLinks(); | |
263 } | |
264 } | |
265 | |
266 DrawingArea &DrawingArea::refreshLinks() { | |
267 LOGSECTION("DrawingArea::refreshLinks"); | |
268 AgStack<AgString> tLinks; | |
269 int n = links.size(); | |
270 LOGV(topic); | |
271 LOGV(links.size()); | |
272 int i; | |
273 for (i = 0; i < n; i++) if (traversedLinks.includes(links[i])) { | |
274 tLinks.push(links[i]); | |
275 } | |
276 n = tLinks.size(); | |
277 LOGV(tLinks.size()); | |
278 if (n == 0) { | |
279 return *this; | |
280 } | |
281 for (i = 0; i < n; i++) { | |
282 int j; | |
283 AgString link = tLinks[i]; | |
284 int nLinkedWords = linkedWords.size(); | |
285 for (j = 0; j < nLinkedWords; j++) { | |
286 int k = linkedWords[j]; | |
287 if (word[k].linktopic != link) { | |
288 continue; | |
289 } | |
290 if (word[k].linkTraversed) { | |
291 continue; | |
292 } | |
293 word[k].linkTraversed = 1; | |
294 word[k].styleIndex = HelpWord::usedStyle; | |
295 setter.refresh(word[k]); | |
296 } | |
297 } | |
298 return *this; | |
299 } | |
300 | |
301 Boolean DrawingArea::paintWindow(IPaintEvent &event) { | |
302 //LOGSECTION("DrawingArea::paintWindow"); | |
303 if (painting) { | |
304 return true; | |
305 } | |
306 painting++; | |
307 IPresSpaceHandle handle = event.presSpaceHandle(); //cookie | |
308 SetBkMode(handle, TRANSPARENT); | |
309 IRectangle invalidRect( | |
310 ICoordinateSystem::isConversionNeeded() | |
311 ? ICoordinateSystem::convertToApplication(event.rect(),size()) | |
312 : event.rect() | |
313 ); | |
314 //LOGV(invalidRect.asString()); | |
315 IColor bgColor(ColorSpec::helpText.bg()); | |
316 setter.setEvent(event); | |
317 setter.clear(invalidRect); | |
318 int top = invalidRect.minY(); | |
319 int bot = invalidRect.maxY(); | |
320 IFont &font = FontSpec::help; | |
321 int descender = font.maxDescender(); | |
322 int ascender = font.maxAscender(); | |
323 //LOGV(ascender) LCV(descender); | |
324 int first = 0; | |
325 int last = nWords - 1; | |
326 //LOGV(top) LCV(bot); | |
327 while (first < last) { | |
328 int middle = (first + last) /2; | |
329 //LOGV(first) LCV(middle) LCV(last); | |
330 //LOGV(word[middle].where.y + descender); | |
331 if (setter.bottom(word[middle]) < top) { | |
332 first = middle + 1; | |
333 } | |
334 else { | |
335 last = middle - 1; | |
336 } | |
337 } | |
338 //LOGS("found first dig"); | |
339 char *endHighlight = highlightText + nHighlight; | |
340 | |
341 while (first < nWords && setter.top(word[first]) <= bot) { | |
342 HelpWord &aWord = word[first]; | |
343 if (endHighlight && aWord.text < endHighlight | |
344 && aWord.text + aWord.textLength >= highlightText) | |
345 { | |
346 HelpWord highlightWord = aWord; | |
347 int n = highlightText - highlightWord.text; | |
348 if (n > 0) { | |
349 DigSetter::Dig dig = highlightWord; | |
350 dig.textLength = n; | |
351 setter.measureDig(dig); | |
352 setter.setDig(dig); | |
353 highlightWord.where.x += dig.width; | |
354 highlightWord.text += n; | |
355 highlightWord.textLength -= n; | |
356 } | |
357 n = highlightWord.text + highlightWord.textLength - | |
358 (highlightText + nHighlight); | |
359 if (n > 0) { | |
360 highlightWord.textLength -= n; | |
361 } | |
362 setter.measureDig(highlightWord); | |
363 setter.setDig(highlightWord, 1); | |
364 if (n > 0) { | |
365 DigSetter::Dig dig = highlightWord; | |
366 dig.textLength = n; | |
367 dig.text += highlightWord.textLength; | |
368 setter.measureDig(highlightWord); | |
369 dig.where.x += highlightWord.width; | |
370 setter.setDig(dig); | |
371 } | |
372 //LOGV(aWord.where) LCV(aWord.width) LCV(aWord.forceBreak); | |
373 if (first+1 < word.size()) { | |
374 HelpWord &bWord = word[first+1]; | |
375 //LOGV(bWord.where) LCV(bWord.width) LCV(bWord.forceBreak); | |
376 if (aWord.where.y == bWord.where.y && | |
377 aWord.text + aWord.textLength < endHighlight) { | |
378 //LOGSECTION("BridgeHighlights"); | |
379 cint where = aWord.where; | |
380 where.x += aWord.width; | |
381 where.y -= ascender; | |
382 int bWidth = bWord.where.x - where.x; | |
383 int aWidth = bWidth/2; | |
384 bWidth -= aWidth; | |
385 int height = ascender+descender; | |
386 //LOGV(aWidth) LCV(bWidth) LCV(height); | |
387 //LOGV(where); | |
388 DigSetter::Hole firstHalf(where, cint(aWidth, height), | |
389 aWord.styleIndex); | |
390 where.x += aWidth; | |
391 //LOGV(where); | |
392 DigSetter::Hole secondHalf(where, cint(bWidth, height), | |
393 bWord.styleIndex); | |
394 setter.reverse(firstHalf); | |
395 setter.reverse(secondHalf); | |
396 } | |
397 } | |
398 } | |
399 else { | |
400 setter.setDig(aWord); | |
401 } | |
402 first++; | |
403 //LOGV(first); | |
404 } | |
405 //LOGS("loop done"); | |
406 setter.closeEvent(); | |
407 painting = 0; | |
408 return true; | |
409 } | |
410 | |
411 DrawingArea &DrawingArea::refreshWords(char *p, int n) { | |
412 LOGSECTION("DrawingArea::refreshWords"); | |
413 LOGV((int) p) LCV(n); | |
414 if (p == 0 || n == 0) { | |
415 return *this; | |
416 } | |
417 int first = 1; | |
418 int middle; | |
419 int last = nWords - 1; | |
420 while (first < last) { | |
421 middle = (first + last) /2; | |
422 if (p < word[middle].text) { | |
423 last = middle - 1; | |
424 } | |
425 else if (p < word[middle].text + word[middle].textLength) { | |
426 first = middle; | |
427 break; | |
428 } | |
429 else { | |
430 first = middle + 1; | |
431 } | |
432 } | |
433 char *end = p + nHighlight; | |
434 last = first; | |
435 DigSetter::Hole hole = setter.makeHole(word[first]); | |
436 while (end > word[last].text + word[last].textLength) { | |
437 last++; | |
438 if (word[last].where.y != word[first].where.y) { | |
439 setter.refresh(hole); | |
440 hole = setter.makeHole(word[last]); | |
441 LOGV(hole.where) LCV(hole.size); | |
442 } | |
443 else { | |
444 hole.size.x = word[last].width + word[last].where.x - hole.where.x; | |
445 } | |
446 } | |
447 setter.refresh(hole); | |
448 LOGV(hole.where) LCV(hole.size); | |
449 LOGV(first) LCV(middle) LCV(last); | |
450 LOGV((int) word[first].text) LCV(word[first].textLength); | |
451 LOGV((int) word[middle].text) LCV(word[middle].textLength); | |
452 LOGV((int) word[last].text) LCV(word[last].textLength); | |
453 return *this; | |
454 } | |
455 | |
456 AgHelpView &AgHelpView::positionWords(char *p, int n) { | |
457 LOGSECTION("AgHelpView::positionWords"); | |
458 if (p == 0 || n == 0) { | |
459 return *this; | |
460 } | |
461 int first = 1; | |
462 int middle; | |
463 int last = dataArea.nWords - 1; | |
464 while (first < last) { | |
465 middle = (first + last) /2; | |
466 if (p < dataArea.word[middle].text) { | |
467 last = middle - 1; | |
468 } | |
469 else if (p < | |
470 dataArea.word[middle].text + dataArea.word[middle].textLength) { | |
471 first = middle; | |
472 break; | |
473 } | |
474 else { | |
475 first = middle + 1; | |
476 } | |
477 } | |
478 char *end = p + dataArea.nHighlight; | |
479 last = first; | |
480 while (end > dataArea.word[last].text + dataArea.word[last].textLength) { | |
481 last++; | |
482 } | |
483 LOGV(first) LCV(middle) LCV(last); | |
484 HelpWord &firstWord = dataArea.word[first]; | |
485 IFLOG(HelpWord &middleWord = ) dataArea.word[middle]; | |
486 HelpWord &lastWord = dataArea.word[last]; | |
487 LOGV((int) firstWord.text) LCV(firstWord.textLength); | |
488 LOGV((int) middleWord.text) LCV(middleWord.textLength); | |
489 LOGV((int) lastWord.text) LCV(lastWord.textLength); | |
490 IFont &font = dataArea.displayStyle[firstWord.styleIndex].font; | |
491 int descender = font.maxDescender(); | |
492 int ascender = font.maxAscender(); | |
493 int bottom = verticalScrollBar.scrollBoxPosition() + size().height(); | |
494 int top = verticalScrollBar.scrollBoxPosition(); | |
495 LOGV(top) LCV(bottom); | |
496 LOGV(firstWord.where.y - ascender); | |
497 LOGV(lastWord.where.y + descender); | |
498 if (lastWord.where.y + descender > bottom) { | |
499 int yPos = lastWord.where.y + descender - size().height(); | |
500 verticalScrollBar.moveScrollBoxTo(yPos); | |
501 repositionWindow(); | |
502 } | |
503 else if (firstWord.where.y - ascender < top) { | |
504 int yPos = firstWord.where.y - ascender; | |
505 verticalScrollBar.moveScrollBoxTo(yPos); | |
506 repositionWindow(); | |
507 } | |
508 return *this; | |
509 } | |
510 | |
511 Boolean AgHelpView::findNext(AgString s) { | |
512 LOGSECTION("AgHelpView::findNext"); | |
513 LOGV(s); | |
514 searchProcess.setKey(s); | |
515 | |
516 char *start = dataArea.highlightText; | |
517 if (start == 0) { | |
518 start = dataArea.helpText.pointer(); | |
519 } | |
520 else if (s.size() == dataArea.nHighlight | |
521 && strnicmp(s.pointer(), dataArea.highlightText, s.size()) == 0) { | |
522 start++; | |
523 } | |
524 | |
525 int length = dataArea.myTextLength - (start - dataArea.helpText.pointer()); | |
526 LOGV((int) start) LCV(length); | |
527 if (length <= 0) { | |
528 return 0; | |
529 } | |
530 char *p = searchProcess.scanForward(start, length); | |
531 if (p == 0) { | |
532 return 0; | |
533 } | |
534 char *oldHighlight = dataArea.highlightText; | |
535 int oldCount = dataArea.nHighlight; | |
536 dataArea.highlightText = p; | |
537 dataArea.nHighlight = s.size(); | |
538 dataArea.refreshWords(oldHighlight, oldCount); | |
539 dataArea.refreshWords(dataArea.highlightText, dataArea.nHighlight); | |
540 positionWords(dataArea.highlightText, dataArea.nHighlight); | |
541 return 1; | |
542 } | |
543 | |
544 Boolean AgHelpView::findPrev(AgString s) { | |
545 LOGSECTION("AgHelpView::findPrev"); | |
546 LOGV(s); | |
547 searchProcess.setKey(s); | |
548 | |
549 char *start = dataArea.helpText.pointer(); | |
550 int length = dataArea.helpText.size(); | |
551 if (dataArea.highlightText) { | |
552 length = dataArea.highlightText + dataArea.nHighlight - start; | |
553 } | |
554 | |
555 if (dataArea.nHighlight) { | |
556 if (s.size() == dataArea.nHighlight && | |
557 !strnicmp(s.pointer(), dataArea.highlightText, s.size())) { | |
558 length--; | |
559 } | |
560 } | |
561 char *p = searchProcess.scanReverse(start, length); | |
562 if (p == 0) { | |
563 return 0; | |
564 } | |
565 char *oldHighlight = dataArea.highlightText; | |
566 int oldCount = dataArea.nHighlight; | |
567 dataArea.highlightText = p; | |
568 dataArea.nHighlight = s.size(); | |
569 dataArea.refreshWords(oldHighlight, oldCount); | |
570 dataArea.refreshWords(dataArea.highlightText, dataArea.nHighlight); | |
571 positionWords(dataArea.highlightText, dataArea.nHighlight); | |
572 return 1; | |
573 } | |
574 | |
575 Boolean DrawingArea::mouseMoved(IMouseEvent &event) { | |
576 //LOGSECTION("DrawingArea::mouseMoved"); | |
577 IPoint pWhere(event.mousePosition()); | |
578 cint where(pWhere.x(), pWhere.y()); | |
579 //LOGV(where); | |
580 //IFont &font = FontSpec::help; | |
581 int first = 0; | |
582 int last = nWords - 1; | |
583 while (first < last) { | |
584 int middle = (first + last) /2; | |
585 if (setter.bottom(word[middle]) < where.y) { | |
586 first = middle + 1; | |
587 } | |
588 else if (setter.top(word[middle]) > where.y) { | |
589 last = middle - 1; | |
590 } | |
591 else if (word[middle].where.x + word[middle].width < where.x) { | |
592 first = middle + 1; | |
593 } | |
594 else if (word[middle].where.x > where.x) { | |
595 last = middle - 1; | |
596 } | |
597 else { | |
598 first = middle; | |
599 break; | |
600 } | |
601 } | |
602 //LOGV(first); | |
603 int haslink = 0; | |
604 if (first < nWords && word[first].linktopic != "") { | |
605 haslink = 1; | |
606 } | |
607 if (where.x < word[first].where.x | |
608 && (first <= 0 | |
609 || setter.bottom(word[first-1]) < where.y)) { | |
610 haslink = 0; | |
611 } | |
612 if (where.x > word[first].where.x + word[first].width | |
613 && (first+1 >= nWords | |
614 || setter.top(word[first+1]) > where.y)) { | |
615 haslink = 0; | |
616 } | |
617 //LOGV(haslink); | |
618 //IMousePointerEvent pointerEvent(event); | |
619 if (haslink && !fingerCursorSet) { | |
620 //LOGS("Set fingerCursor"); | |
621 frameWindow->setMousePointer(fingerCursor); | |
622 //pointerEvent.setMousePointer(fingerCursor); | |
623 //SetCursor(fingerCursor); | |
624 fingerCursorSet = true; | |
625 } | |
626 else if (!haslink && fingerCursorSet) { | |
627 //LOGS("Reset activeCursor"); | |
628 frameWindow->setMousePointer(ControlPanel::activeCursor); | |
629 //pointerEvent.setMousePointer(ControlPanel::activeCursor); | |
630 //SetCursor(ControlPanel::activeCursor); | |
631 fingerCursorSet = false; | |
632 } | |
633 return true; | |
634 } | |
635 | |
636 Boolean DrawingArea::mouseClicked(IMouseClickEvent &event) { | |
637 LOGSECTION("DrawingArea::mouseClicked"); | |
638 if (event.mouseButton() == IMouseClickEvent::button2) { | |
639 if (event.mouseAction() == IMouseClickEvent::down) { | |
640 rightButtonState = buttonDown; | |
641 } | |
642 else if (event.mouseAction() == IMouseClickEvent::up) { | |
643 rightButtonState = waitingForClick; | |
644 } | |
645 else if (event.mouseAction() == IMouseClickEvent::click) { | |
646 rightButtonState = buttonIdle; | |
647 } | |
648 return false; | |
649 } | |
650 if (event.mouseButton() != IMouseClickEvent::button1) { | |
651 return false; | |
652 } | |
653 if (ControlPanel::helpCursorSet) { | |
654 if (event.mouseAction() == IMouseClickEvent::down) { | |
655 ControlPanel::helpCursorSet = 0; | |
656 ControlPanel::resetCursor(); | |
657 } | |
658 } | |
659 switch (event.mouseAction()) { | |
660 case IMouseClickEvent::doubleClick: | |
661 case IMouseClickEvent::click: { | |
662 if (event.windowUnderPointer() != handle()) { | |
663 return false; | |
664 } | |
665 IPoint pWhere(event.mousePosition()); | |
666 cint where(pWhere.x(), pWhere.y()); | |
667 //LOGV(where); | |
668 //IFont &font = FontSpec::help; | |
669 int first = 0; | |
670 int last = nWords - 1; | |
671 while (first < last) { | |
672 int middle = (first + last) /2; | |
673 //LOGV(first) LCV(middle) LCV(last); | |
674 //LOGV(word[middle].where.x) LCV(word[middle].where.y); | |
675 //LOGV(setter.top(word[middle])) LCV(setter.bottom(word[middle])); | |
676 if (setter.bottom(word[middle]) < where.y) { | |
677 first = middle + 1; | |
678 } | |
679 else if (setter.top(word[middle]) > where.y) { | |
680 last = middle - 1; | |
681 } | |
682 else if (word[middle].where.x + word[middle].width < where.x) { | |
683 first = middle + 1; | |
684 } | |
685 else if (word[middle].where.x > where.x) { | |
686 last = middle - 1; | |
687 } | |
688 else { | |
689 first = middle; | |
690 break; | |
691 } | |
692 } | |
693 AgString linktopic = ""; | |
694 //LOGV(first); | |
695 for (;first < nWords; first++) { | |
696 if (setter.top(word[first]) > where.y) { | |
697 return false; | |
698 } | |
699 //LOGV(word[first].where) LCV(word[first].link); | |
700 int xr = word[first].where.x + word[first].width; | |
701 //LOGV(word[first].width) LCV(xr); | |
702 if (xr < where.x) { | |
703 linktopic = word[first].linktopic; | |
704 continue; | |
705 } | |
706 int xl = word[first].where.x; | |
707 //LOGS("contact") LV(xr) LCV(xl) LCV(link) LCV(word[first].link); | |
708 if ( where.x < xl && linktopic != word[first].linktopic) { | |
709 return false; | |
710 } | |
711 if (word[first].linktopic == "") { | |
712 return false; | |
713 } | |
714 break; | |
715 } | |
716 if (first >= nWords) { | |
717 return false; | |
718 } | |
719 //LOGV(first) LCV(word[first].linktopic); | |
720 | |
721 word[first].linkTraversed = 1; | |
722 linktopic = word[first].linktopic; | |
723 traversedLinks.insert(linktopic); | |
724 | |
725 int i; | |
726 | |
727 int iMin = first, iMax = first; | |
728 for (i = first; i >= 0 && word[i].linktopic == linktopic; i--) { | |
729 word[i].styleIndex = HelpWord::usedStyle; | |
730 iMin = i; | |
731 } | |
732 for (i = first + 1 ; i < nWords && word[i].linktopic == linktopic; i++) { | |
733 iMax = i; | |
734 word[i].styleIndex = HelpWord::usedStyle; | |
735 } | |
736 HelpWord &wmn = word[iMin]; | |
737 HelpWord &wmx = word[iMax]; | |
738 | |
739 int minX = min(wmn.where.x,wmx.where.x); | |
740 int maxX = max(wmn.where.x + wmn.width, wmx.where.x + wmx.width); | |
741 int minY = min(setter.top(wmn), setter.top(wmx)); | |
742 int maxY = max(setter.bottom(wmn), setter.bottom(wmx)); | |
743 | |
744 IRectangle r(IPoint(minX, minY), IPoint(maxX, maxY)); | |
745 //LOGV(r.asString()); | |
746 refresh(r); | |
747 | |
748 const char *newTopic = word[first].linktopic.pointer(); | |
749 //LOGV(newTopic); | |
750 AgString title = AgString::format("Help - %s", newTopic); | |
751 IFrameWindow *helpWindow = AgFrame::windowRegistry.find(title); | |
752 if (helpWindow == 0) { | |
753 helpWindow = new AgHelpWindow(newTopic); | |
754 helpWindow->setAutoDeleteObject(); | |
755 } | |
756 helpWindow->show().setFocus(); | |
757 | |
758 refreshAction.performDeferred(); | |
759 return true; | |
760 } | |
761 } | |
762 return false; | |
763 } | |
764 | |
765 ISize DrawingArea::calcMinimumSize() const { | |
766 LOGSECTION("DrawingArea::calcMinimumSize"); | |
767 unsigned char *p = (unsigned char *) helpText.pointer(); | |
768 IFont &font = FontSpec::help; | |
769 int enWidth = font.avgCharWidth(); | |
770 int minWidth = font.minTextWidth((char *)p); | |
771 LOGV(minWidth); | |
772 LOGV(margin); | |
773 LOGV(enWidth); | |
774 | |
775 IFont &titleFont = FontSpec::helpTitle; | |
776 int width = titleFont.textWidth(topic.pointer()); | |
777 if (width > minWidth) { | |
778 minWidth = width; | |
779 } | |
780 while (*p) { | |
781 if (*p == ' ') { | |
782 unsigned char *q = p; | |
783 int k = 0; | |
784 while (*q == ' ') { | |
785 k++; | |
786 q++; | |
787 } | |
788 p = q; | |
789 int width = 0; | |
790 while (*q && *q != '\n') { | |
791 width += charWidth[*q++]; | |
792 } | |
793 width += k*enWidth; | |
794 if (width > minWidth) { | |
795 minWidth = width; | |
796 } | |
797 p = ++q; | |
798 continue; | |
799 } | |
800 while (*p && *p != '\n') { | |
801 p++; | |
802 } | |
803 if (*p) { | |
804 p++; | |
805 } | |
806 } | |
807 minWidth += 2*margin; | |
808 int height = windowFont.maxCharHeight() + windowFont.externalLeading(); | |
809 return ISize(minWidth, 5*height); | |
810 } | |
811 | |
812 #define BULLET 7 | |
813 | |
814 DrawingArea &DrawingArea::measureText() { | |
815 LOGSECTION("DrawingArea::measureText"); | |
816 IFont &textFont = FontSpec::help; | |
817 IFont &titleFont = FontSpec::helpTitle; | |
818 AgString auxText(helpText.pointer()); | |
819 | |
820 | |
821 AgStack<int> linkedWordStack; | |
822 | |
823 unsigned char *p = (unsigned char *) helpText.pointer(); | |
824 unsigned char *aux = (unsigned char *) auxText.pointer(); | |
825 *aux = 0; | |
826 | |
827 int lineHeight = textFont.maxCharHeight() + textFont.externalLeading(); | |
828 | |
829 int spaceWidth = charWidth[' ']; | |
830 int whereX = paraIndent; | |
831 int whereY = leading(titleFont); | |
832 nWords = 0; | |
833 word.discardData(); | |
834 | |
835 //LOGFont(windowFont); | |
836 //LOGV(margin); | |
837 //LOGV(paraIndent); | |
838 | |
839 int titleWidth = titleFont.textWidth(topic.pointer()); | |
840 //LOGV((char *) titleFont.name()) LCV(titleFont.pointSize()); | |
841 //LOGV(titleWidth); | |
842 | |
843 whereX = (measure - titleWidth)/2; | |
844 LOGV(whereX) LCV(whereY); | |
845 | |
846 word.push(HelpWord(topic.pointer(), strlen(topic.pointer()), | |
847 cint(whereX, whereY), HelpWord::titleStyle)); | |
848 word[0].width = titleWidth; | |
849 nWords = 1; | |
850 whereY += titleFont.maxDescender(); | |
851 whereY += paraLeading; | |
852 word[0].forceBreak = 1; | |
853 | |
854 whereX = paraIndent; | |
855 | |
856 int indentedLine = 0; | |
857 int tabbedLine = 0; | |
858 | |
859 int linkIndex = 0; | |
860 | |
861 int highlightBegin = 0; | |
862 int highlightEnd = 0; | |
863 longestLine = 0; | |
864 int enWidth = avgCharWidth(); | |
865 int emWidth = font().charWidth('M'); | |
866 LOGV(enWidth) LCV(emWidth); | |
867 | |
868 LOGV(whereY); | |
869 //int indentLevel = 0; | |
870 int forceBreak = 0; | |
871 int bulletedLine = 0; | |
872 while (*p) { | |
873 if (*p == ' ') { | |
874 int k = 0; | |
875 while (*p == ' ') { | |
876 p++; | |
877 k++; | |
878 } | |
879 whereX = k*enWidth + margin; | |
880 word[nWords-1].forceBreak = 1; | |
881 indentedLine = 1; | |
882 //indentLevel = k; | |
883 } | |
884 else if (*p == '\t') { | |
885 p++; | |
886 whereX = 2*emWidth + margin; | |
887 word[nWords-1].forceBreak = 1; | |
888 tabbedLine = 1; | |
889 LOGS("tabbed Line"); | |
890 } | |
891 else if (*p == BULLET) { | |
892 whereX = emWidth + margin; | |
893 if (nWords > 0) { | |
894 word[nWords-1].forceBreak = 1; | |
895 } | |
896 bulletedLine = 1; | |
897 LOGS("bulleted Line") LCV(whereX); | |
898 } | |
899 int wordLength = 0; | |
900 unsigned char *beginWord = aux; | |
901 unsigned char *q = beginWord; | |
902 int dotFlag = 0; | |
903 while (*p && *p != ' ' && *p != '\t' && *p != '\n') { | |
904 if (*p == (unsigned char) 169) { | |
905 //LOGV(nWords) LCV(whereX) LCV(whereY); | |
906 highlightBegin++; | |
907 p++; | |
908 continue; | |
909 } | |
910 if (*p == (unsigned char) 170) { | |
911 //LOGV(nWords) LCV(whereX) LCV(whereY); | |
912 highlightEnd++; | |
913 p++; | |
914 continue; | |
915 } | |
916 //*q++ = *p++; | |
917 | |
918 *q++ = *p; | |
919 if (*p == 0 || *p++ != '.') { | |
920 continue; | |
921 } | |
922 if (strchr(" \r\n\t", *p) == 0) { | |
923 dotFlag = 1; | |
924 } | |
925 break; | |
926 } | |
927 | |
928 int nChars = q-beginWord; | |
929 *q = 0; | |
930 wordLength = windowFont.textWidth((char *) beginWord); | |
931 LOGV(nWords) LCV(wordLength) LCV(nChars) LCV(beginWord); | |
932 *q++ = ' '; | |
933 aux = q; | |
934 while (*p == ' ' || (*p == '\t' && !tabbedLine)) { | |
935 p++; | |
936 } | |
937 //forceBreak = (indentedLine || tabbedLine) && *p == '\n'; | |
938 //forceBreak = (bulletedLine || tabbedLine) && *p == '\n'; | |
939 forceBreak = tabbedLine && *p == '\n'; | |
940 int endLine = *p == '\n'; | |
941 if (endLine) { | |
942 p++; | |
943 } | |
944 if (*p == '\n') { // More than one newline? | |
945 forceBreak = 1; // Yes | |
946 while (*p == '\n') { | |
947 p++; | |
948 } | |
949 } | |
950 //LOGV(forceBreak) LCV(indentLevel) LCV(indentedLine) LCV(dotFlag); | |
951 LOGV(whereX); | |
952 if (!dotFlag && | |
953 !tabbedLine && | |
954 !indentedLine && | |
955 whereX + wordLength > measure - margin) { | |
956 if (whereX > longestLine) longestLine = whereX; | |
957 LOGV(longestLine); | |
958 whereX = margin; | |
959 whereY += lineHeight; | |
960 } | |
961 AgString temp((char *) beginWord, 10); | |
962 LOGV(word.size()) LCV(temp.pointer()) LCV(nChars) LCV(whereX) LCV(whereY); | |
963 HelpWord thisWord((char *) beginWord, nChars, cint(whereX, whereY)); | |
964 if (*beginWord == BULLET) { | |
965 //whereX = margin + 2*emWidth; | |
966 *beginWord = 183; | |
967 LOGS("replaced bullet") LCV(whereX) LCV(emWidth); | |
968 } | |
969 else { | |
970 whereX += wordLength; | |
971 if (!dotFlag) { | |
972 whereX += spaceWidth; | |
973 } | |
974 LOGV(whereX); | |
975 if (tabbedLine && !endLine && *p == '\t') { | |
976 p++; | |
977 int newX = margin; | |
978 while (newX < whereX) { | |
979 newX += 2*emWidth; | |
980 } | |
981 LOGV(newX); | |
982 whereX = newX; | |
983 while (*p == '\t') { | |
984 whereX += 2*emWidth; | |
985 p++; | |
986 } | |
987 } | |
988 } | |
989 LOGV(whereX); | |
990 thisWord.indent = indentedLine || tabbedLine; | |
991 thisWord.bullet = !thisWord.indent && bulletedLine != 0; | |
992 thisWord.forceBreak = forceBreak; | |
993 thisWord.noBreak = dotFlag; | |
994 thisWord.width = wordLength; | |
995 LOGV(thisWord.bullet) LCV(thisWord.forceBreak) LCV(thisWord.indent); | |
996 if (highlightBegin) { | |
997 //LOGS("set up links"); | |
998 //LOGV(linkIndex); | |
999 LOGV(links.size()) LCV(topic); | |
1000 thisWord.linktopic = links[linkIndex]; | |
1001 //LOGV(thisWord.link); | |
1002 if (traversedLinks.includes(thisWord.linktopic)) { | |
1003 thisWord.linkTraversed = 1; | |
1004 } | |
1005 //LOGV(dict_str(help_dict, thisWord.link)); | |
1006 //LOGV(thisWord.where); | |
1007 if (thisWord.linkTraversed) { | |
1008 thisWord.styleIndex = HelpWord::usedStyle; | |
1009 } | |
1010 else { | |
1011 thisWord.styleIndex = HelpWord::linkStyle; | |
1012 } | |
1013 } | |
1014 if (highlightEnd) { | |
1015 highlightBegin = highlightEnd = 0; | |
1016 linkIndex++; | |
1017 } | |
1018 if (forceBreak) { | |
1019 LOGS("forced break"); | |
1020 if ((tabbedLine || indentedLine ) && whereX > longestLine) { | |
1021 longestLine = whereX; | |
1022 LOGV(longestLine); | |
1023 } | |
1024 whereX = indentedLine || tabbedLine ? margin : paraIndent; | |
1025 if ((tabbedLine || bulletedLine) && *p == ' ') { | |
1026 whereX = paraIndent; | |
1027 while (*p == ' ') p++; | |
1028 } | |
1029 //whereX = paraIndent; | |
1030 whereY += lineHeight; | |
1031 if (!indentedLine && !tabbedLine) { | |
1032 whereY += paraLeading; | |
1033 } | |
1034 } | |
1035 word.push(thisWord); | |
1036 linkedWordStack.push(nWords); | |
1037 nWords++; | |
1038 if (forceBreak) { | |
1039 indentedLine = tabbedLine = bulletedLine = 0; | |
1040 } | |
1041 //LOGV(nWords); | |
1042 } | |
1043 whereY += 2*lineHeight; | |
1044 longestLine += margin; | |
1045 LOGV(longestLine); | |
1046 nLines = whereY/lineHeight; | |
1047 //LOGV(nLines); | |
1048 //LOGV(nWords); | |
1049 spaceRequired = measure * whereY; | |
1050 linkedWords = AgArray<int>(linkedWordStack); | |
1051 *aux = 0; | |
1052 helpText = auxText; | |
1053 myTextLength = helpText.size(); | |
1054 return *this; | |
1055 } | |
1056 | |
1057 DrawingArea &DrawingArea::remeasureText() { | |
1058 LOGSECTION("DrawingArea::remeasureText"); | |
1059 //LOGV(measure); | |
1060 IFont &textFont = FontSpec::help; | |
1061 IFont &titleFont = FontSpec::helpTitle; | |
1062 int lineHeight = textFont.maxCharHeight() + textFont.externalLeading(); | |
1063 //int emWidth = textFont.maxSize().width(); | |
1064 int emWidth = textFont.charWidth('M'); | |
1065 | |
1066 LOGV(emWidth); | |
1067 //int paraIndent = margin + emWidth; | |
1068 int paraIndent = margin; | |
1069 | |
1070 int spaceWidth = charWidth[' ']; | |
1071 //LOGV(spaceWidth); | |
1072 cint where(paraIndent, paraLeading); | |
1073 int forceBreak = 0; | |
1074 int k = 0; | |
1075 HelpWord &title = word[0]; | |
1076 int titleWidth = titleFont.textWidth(topic.pointer()); | |
1077 //LOGV((char *) titleFont.name()) LCV(titleFont.pointSize()); | |
1078 //LOGV(titleWidth); | |
1079 word[0].width = titleWidth; | |
1080 title.where.x = (measure - titleWidth)/2; | |
1081 title.where.y = leading(titleFont); | |
1082 { | |
1083 //IFont &f = displayStyle[title.styleIndex].font; | |
1084 //LOGV(f.name()) LCV(f.pointSize()); | |
1085 } | |
1086 where.y += 3*leading(titleFont)/2; | |
1087 where.y += titleFont.maxDescender(); | |
1088 word[1].where.y = where.y; | |
1089 int indentedLine = 0; | |
1090 LOGV(paraLeading) LCV(lineHeight); | |
1091 for (k = 1; k < nWords; k++) { | |
1092 HelpWord &textWord = word[k]; | |
1093 int width = word[k].width; | |
1094 //int i = k; | |
1095 //while (i < nWords-1 && word[i++].noBreak) { | |
1096 // width += word[i].width; | |
1097 //} | |
1098 char buf[100]; | |
1099 strncpy(buf, textWord.text, textWord.textLength); | |
1100 buf[textWord.textLength] = 0; | |
1101 LOGV(k) LCV(textWord.indent) LCV(textWord.where) | |
1102 LCV(*textWord.text) LCV(buf); | |
1103 LOGV(textWord.bullet); | |
1104 int rightIndent = textWord.bullet ? 2*emWidth : 0; | |
1105 if (forceBreak) { | |
1106 where.y += lineHeight; | |
1107 where.x = textWord.where.x; | |
1108 if (textWord.bullet) { | |
1109 where.x = margin + 2*emWidth; | |
1110 } | |
1111 //if (!textWord.indent && where.x > margin) { | |
1112 // where.y += paraLeading; | |
1113 //} | |
1114 if (!textWord.indent) { | |
1115 where.y += paraLeading; | |
1116 } | |
1117 indentedLine = textWord.indent; | |
1118 } | |
1119 else if (!indentedLine && | |
1120 where.x + width > measure - margin - rightIndent) { | |
1121 where.x = margin; | |
1122 where.y += lineHeight; | |
1123 if (textWord.bullet) { | |
1124 where.x += 2*emWidth; | |
1125 } | |
1126 } | |
1127 textWord.where.y = where.y; | |
1128 | |
1129 //if (!forceBreak || !textWord.indent) | |
1130 if (!forceBreak && !textWord.indent) { | |
1131 textWord.where.x = where.x; | |
1132 } | |
1133 | |
1134 LOGV(where) LCV(forceBreak) LCV(indentedLine); | |
1135 if (textWord.linktopic != "") { | |
1136 //LOGV(textWord.linktopic); | |
1137 //LOGV(k) LV(where); | |
1138 //LOGV(where.x + textWord.width); | |
1139 } | |
1140 if (*textWord.text == (char) 183) { | |
1141 //where.x += emWidth; //bullet | |
1142 } | |
1143 else { | |
1144 where.x += textWord.width + spaceWidth; | |
1145 } | |
1146 forceBreak = textWord.forceBreak; | |
1147 AgString temp((char *)textWord.text, 10); | |
1148 LOGV(k) LCV(temp) LCV(textWord.where); | |
1149 } | |
1150 where.y += 2*lineHeight; | |
1151 | |
1152 nLines = where.y/lineHeight; | |
1153 //LOGV(nLines); | |
1154 return *this; | |
1155 } | |
1156 | |
1157 AgString DrawingArea::getHelpText(const char *topic) { | |
1158 LOGSECTION("getHelpText"); | |
1159 const HelpTopic *ht = help_topic(topic); | |
1160 LOGV((int)ht); | |
1161 AgString ret = helptopic_gettext(ht); | |
1162 LOGV(ret); | |
1163 return ret; | |
1164 } | |
1165 | |
1166 AgArray<AgString> DrawingArea::findLinks(AgString msg) { | |
1167 char *t = msg.pointer(); | |
1168 LOGSECTION("DrawingArea::findLinks"); | |
1169 LOGV(topic); | |
1170 AgStack<AgString> stack; | |
1171 while(1) { | |
1172 t = strchr(t, 169); | |
1173 if (t != NULL && t[1] == '\'') { | |
1174 t = strchr(t+1, 169); | |
1175 } | |
1176 if (t == NULL) { | |
1177 break; | |
1178 } | |
1179 AgCharStack charStack; | |
1180 t++; | |
1181 while (1) { | |
1182 unsigned char c = *t++; | |
1183 switch (c) { | |
1184 //case '.': | |
1185 case '\n': | |
1186 case '\r': | |
1187 case ' ' : | |
1188 case '\t': { | |
1189 charStack.push(' '); | |
1190 while (1) { | |
1191 switch (*t) { | |
1192 case '\n': | |
1193 case '\r': | |
1194 case ' ' : | |
1195 case '\t': { | |
1196 t++; | |
1197 continue; | |
1198 } | |
1199 default: break; | |
1200 } | |
1201 break; | |
1202 } | |
1203 continue; | |
1204 } | |
1205 case 0: | |
1206 case 170: | |
1207 break; | |
1208 default: | |
1209 charStack.push(c); | |
1210 continue; | |
1211 } | |
1212 break; | |
1213 } | |
1214 if (charStack.size() == 0) { | |
1215 continue; | |
1216 } | |
1217 AgString word = charStack.popString(); | |
1218 LOGV(word.pointer()); | |
1219 | |
1220 if (!help_topicexists(word.pointer())) { | |
1221 int n = word.size(); | |
1222 int k = word[n-1]; | |
1223 if (tolower(k) == 's') { | |
1224 word[n-1] = 0; | |
1225 } | |
1226 } | |
1227 if (!help_topicexists(word.pointer())) { | |
1228 LOGS("Dangling help link: ") LV(word); | |
1229 } | |
1230 | |
1231 // Canonicalize the name of the topic. | |
1232 const HelpTopic *ht = help_topic(word.pointer()); | |
1233 word = helptopic_gettitle(ht); | |
1234 | |
1235 stack.push(word); | |
1236 LOGV(AgString(t, 10).pointer()); | |
1237 } | |
1238 stack.push("Using Help"); | |
1239 LOGV(stack.size()); | |
1240 return AgArray<AgString>(stack); | |
1241 } | |
1242 | |
1243 | |
1244 static int OPTLINK linksortfunc(const void *av, const void *bv) { | |
1245 const AgString &as = *(const AgString *)av; | |
1246 const AgString &bs = *(const AgString *)bv; | |
1247 | |
1248 int r = stricmp(as.pointer(), bs.pointer()); | |
1249 if (r==0) { | |
1250 r = strcmp(as.pointer(), bs.pointer()); | |
1251 } | |
1252 return r; | |
1253 } | |
1254 | |
1255 AgArray<AgString> DrawingArea::sortLinks(AgArray<AgString> links) { | |
1256 LOGSECTION("DrawingArea::sortLinks"); | |
1257 | |
1258 // This used to sort and uniq by inserting into an AgBalancedTree. | |
1259 // The problem (apart from overelaboration) is that this requires a | |
1260 // custom wrapper class to insert the desired sort function, and | |
1261 // thus its own template instantiation. Which is a hassle, and | |
1262 // fairly silly... | |
1263 | |
1264 int i, n = links.size(); | |
1265 LOGV(n); | |
1266 AgArray<AgString> tmp(n); | |
1267 for (i=0; i<n; i++) { | |
1268 tmp[i] = links[i]; | |
1269 } | |
1270 qsort(tmp.pointer(), tmp.size(), sizeof(AgString), linksortfunc); | |
1271 | |
1272 // Now uniq. Since AgArray isn't expandable, count first. | |
1273 int j, ct; | |
1274 | |
1275 for (i=ct=0; i<n; i++) { | |
1276 if (i==0 || tmp[i] != tmp[i-1]) { | |
1277 ct++; | |
1278 } | |
1279 } | |
1280 | |
1281 AgArray<AgString> result(ct); | |
1282 for (i=j=0; i<n; i++) { | |
1283 if (i==0 || tmp[i] != tmp[i-1]) { | |
1284 result[j++] = tmp[i]; | |
1285 } | |
1286 } | |
1287 | |
1288 return result; | |
1289 } | |
1290 | |
1291 AgHelpView &AgHelpView::layout() { | |
1292 LOGSECTION("AgHelpView::layout"); | |
1293 if (size().width() == 0 || layoutActive) { | |
1294 return *this; | |
1295 } | |
1296 layoutActive++; | |
1297 doLayout(); | |
1298 setLayoutDistorted(0, IWindow::layoutChanged); | |
1299 layoutActive--; | |
1300 return *this; | |
1301 } | |
1302 | |
1303 AgHelpView::AgHelpView(IWindow *ownerWindow_, | |
1304 AgString topic) | |
1305 : ICanvas(nextChildId(), ownerWindow_, ownerWindow_) | |
1306 , verticalScrollBar(nextChildId(), this, this, IRectangle(), | |
1307 IScrollBar::vertical | IWindow::visible) | |
1308 , dataArea(this, topic) | |
1309 , vsbShowing(false) | |
1310 , layoutActive(0) | |
1311 , colorChange(this, onColorChange) | |
1312 , fontChange(this, onFontChange) | |
1313 , highlightIndex(0) | |
1314 { | |
1315 LOGSECTION("AgHelpView::AgHelpView"); | |
1316 windowId = id(); | |
1317 LOGV(id()); | |
1318 | |
1319 colorChange.attach(&ColorSpec::helpText); | |
1320 colorChange.attach(&ColorSpec::helpLink); | |
1321 colorChange.attach(&ColorSpec::helpUsedLink); | |
1322 | |
1323 fontChange.attach(&FontSpec::help); | |
1324 fontChange.attach(&FontSpec::helpTitle); | |
1325 | |
1326 | |
1327 setMinimumSize(calcMinimumSize()); | |
1328 verticalScrollBar.setScrollableRange(IRange(0,100)); | |
1329 verticalScrollBar.moveScrollBoxTo(0); | |
1330 | |
1331 IKeyboardHandler::handleEventsFor(this); | |
1332 IResizeHandler ::handleEventsFor(this); | |
1333 IScrollHandler ::handleEventsFor(this); | |
1334 | |
1335 dataArea.show(); | |
1336 show().setFocus(); | |
1337 } | |
1338 | |
1339 AgHelpView::~AgHelpView() { | |
1340 IKeyboardHandler::stopHandlingEventsFor(this); | |
1341 IResizeHandler ::stopHandlingEventsFor(this); | |
1342 IScrollHandler ::stopHandlingEventsFor(this); | |
1343 } | |
1344 | |
1345 Boolean AgHelpView::windowResize(IResizeEvent &event){ | |
1346 setLayoutDistorted(IWindow::layoutChanged, 0); | |
1347 return false; | |
1348 } | |
1349 | |
1350 ISize AgHelpView::calcMinimumSize() const { | |
1351 int width = dataArea.minimumSize().width() | |
1352 + verticalScrollBar.minimumSize().width(); | |
1353 return ISize(width, dataArea.minimumSize().height()); | |
1354 } | |
1355 | |
1356 ISize AgHelpView::suggestSize() { | |
1357 LOGSECTION("AgHelpView::suggestSize"); | |
1358 ISize preferredSize = dataArea.preferredSize; | |
1359 int width = preferredSize.width(); | |
1360 int nLines = preferredSize.height(); | |
1361 if (nLines > defaultWindowHeight) { | |
1362 nLines = defaultWindowHeight; | |
1363 width += verticalScrollBar.minimumSize().width(); | |
1364 } | |
1365 return ISize(width, nLines * lineHeight()); | |
1366 } | |
1367 | |
1368 AgHelpView &AgHelpView::repositionWindow() { | |
1369 int y = -verticalScrollBar.scrollBoxPosition(); | |
1370 dataArea.moveTo(IPoint(0,y)); | |
1371 return *this; | |
1372 } | |
1373 | |
1374 | |
1375 /* | |
1376 * | |
1377 * Layout considerations: | |
1378 * | |
1379 * The data view consists of up to four parts: two scroll bars, a heading | |
1380 * and a data area. | |
1381 * | |
1382 * The presence or absence of the scroll bars depends on the relative size | |
1383 * of the data area and the size of the table to be displayed. | |
1384 */ | |
1385 | |
1386 AgHelpView &AgHelpView::doLayout() { | |
1387 LOGSECTION("AgHelpView::doLayout"); | |
1388 LOGV(id()); | |
1389 | |
1390 ISize canvasSize = size(); | |
1391 int canvasWidth = canvasSize.width(); | |
1392 int canvasHeight = canvasSize.height(); | |
1393 | |
1394 LOGV(canvasWidth); | |
1395 LOGV(canvasHeight); | |
1396 | |
1397 int dataWidth = canvasWidth; | |
1398 int dataHeight = canvasHeight; | |
1399 //int dataY = 0; | |
1400 | |
1401 LOGV(dataWidth); | |
1402 LOGV(dataHeight); | |
1403 | |
1404 | |
1405 | |
1406 int vsbWidth = verticalScrollBar.minimumSize().width(); | |
1407 | |
1408 int verticalPosition = verticalScrollBar.scrollBoxPosition(); | |
1409 | |
1410 Boolean vsb = dataWidth * dataHeight < dataArea.spaceRequired; | |
1411 | |
1412 dataArea.measure = dataWidth; | |
1413 if (vsb) { | |
1414 dataArea.measure -= vsbWidth+1; | |
1415 } | |
1416 dataArea.remeasureText(); | |
1417 dataHeight = dataArea.nLines*lineHeight(); | |
1418 if (dataHeight <= canvasHeight && vsb) { | |
1419 vsb = 0; | |
1420 dataArea.measure = dataWidth; | |
1421 dataArea.remeasureText(); | |
1422 dataHeight = canvasHeight; | |
1423 } | |
1424 else if (dataHeight > canvasHeight && !vsb) { | |
1425 vsb = 1; | |
1426 dataArea.measure = dataWidth - vsbWidth+1; | |
1427 dataArea.remeasureText(); | |
1428 dataHeight = dataArea.nLines*lineHeight(); | |
1429 } | |
1430 else if (dataHeight <= canvasHeight) { | |
1431 dataHeight = canvasHeight; | |
1432 } | |
1433 dataArea.sizeTo(ISize(dataArea.measure, dataHeight)); | |
1434 | |
1435 if (vsb && !vsbShowing) { | |
1436 verticalScrollBar.moveScrollBoxTo(0); | |
1437 verticalScrollBar.show(); | |
1438 vsbShowing = true; | |
1439 } | |
1440 if (!vsb && vsbShowing) { | |
1441 verticalScrollBar.hide(); | |
1442 vsbShowing = false; | |
1443 verticalScrollBar.moveScrollBoxTo(0); | |
1444 } | |
1445 | |
1446 if (vsbShowing) { | |
1447 dataWidth -= vsbWidth + 1; | |
1448 verticalScrollBar.moveTo(IPoint(dataWidth+1, 0)); | |
1449 verticalScrollBar.sizeTo(ISize(vsbWidth, canvasHeight)); | |
1450 } | |
1451 | |
1452 LOGV(dataArea.size().asString()); | |
1453 LOGV(dataArea.parentSize().asString()); | |
1454 | |
1455 LOGV(rect().asString()); | |
1456 LOGV(dataArea.rect().asString()); | |
1457 | |
1458 | |
1459 LOGV(dataArea.size().asString()); | |
1460 | |
1461 verticalScrollBar.setScrollableRange(IRange(0, dataHeight-1)); | |
1462 verticalScrollBar.setVisibleCount(canvasHeight); | |
1463 verticalPosition = min((int) verticalScrollBar.scrollBoxRange().upperBound(), | |
1464 verticalPosition); | |
1465 | |
1466 verticalScrollBar.setMinScrollIncrement(lineHeight()); | |
1467 | |
1468 verticalScrollBar.moveScrollBoxTo(verticalPosition); | |
1469 LOGV(verticalPosition); | |
1470 repositionWindow(); | |
1471 return *this; | |
1472 } | |
1473 | |
1474 Boolean AgHelpView::lineDown(IScrollEvent &event) { | |
1475 if (event.scrollBarWindow() != &verticalScrollBar) { | |
1476 return false; | |
1477 } | |
1478 moveScrollBox(event); | |
1479 repositionWindow(); | |
1480 return true; | |
1481 } | |
1482 | |
1483 Boolean AgHelpView::lineUp(IScrollEvent &event) { | |
1484 if (event.scrollBarWindow() != &verticalScrollBar) { | |
1485 return false; | |
1486 } | |
1487 moveScrollBox(event); | |
1488 repositionWindow(); | |
1489 return true; | |
1490 } | |
1491 | |
1492 Boolean AgHelpView::pageDown(IScrollEvent &event) { | |
1493 if (event.scrollBarWindow() != &verticalScrollBar) { | |
1494 return false; | |
1495 } | |
1496 moveScrollBox(event); | |
1497 repositionWindow(); | |
1498 return true; | |
1499 } | |
1500 | |
1501 Boolean AgHelpView::pageUp(IScrollEvent &event) { | |
1502 if (event.scrollBarWindow() != &verticalScrollBar) { | |
1503 return false; | |
1504 } | |
1505 moveScrollBox(event); | |
1506 repositionWindow(); | |
1507 return true; | |
1508 } | |
1509 | |
1510 Boolean AgHelpView::scrollBoxTrack(IScrollEvent &event) { | |
1511 moveScrollBox(event); | |
1512 repositionWindow(); | |
1513 return true; | |
1514 } | |
1515 | |
1516 Boolean AgHelpView::virtualKeyPress(IKeyboardEvent &event) { | |
1517 LOGSECTION("AgHelpView::Virtualkeypress"); | |
1518 switch (event.virtualKey()) { | |
1519 case IKeyboardEvent::up: { | |
1520 int topLine = verticalScrollBar.scrollBoxPosition() | |
1521 - verticalScrollBar.minScrollIncrement(); | |
1522 verticalScrollBar.moveScrollBoxTo(topLine); | |
1523 repositionWindow(); | |
1524 return true; | |
1525 } | |
1526 case IKeyboardEvent::down: { | |
1527 LOGSECTION("Cursor down one line"); | |
1528 int topLine = verticalScrollBar.scrollBoxPosition() | |
1529 + verticalScrollBar.minScrollIncrement(); | |
1530 verticalScrollBar.moveScrollBoxTo(topLine); | |
1531 repositionWindow(); | |
1532 return true; | |
1533 } | |
1534 case IKeyboardEvent::home: { | |
1535 if (!event.isCtrlDown()) { | |
1536 messageBeep(); | |
1537 return true; | |
1538 } | |
1539 } | |
1540 case IKeyboardEvent::pageUp: { | |
1541 int topLine = verticalScrollBar.scrollBoxPosition(); | |
1542 topLine -= verticalScrollBar.visibleCount(); | |
1543 if (event.isCtrlDown()) { | |
1544 topLine = 0; | |
1545 } | |
1546 verticalScrollBar.moveScrollBoxTo(topLine); | |
1547 repositionWindow(); | |
1548 return true; | |
1549 } | |
1550 case IKeyboardEvent::end: { | |
1551 if (!event.isCtrlDown()) { | |
1552 messageBeep(); | |
1553 return true; | |
1554 } | |
1555 } | |
1556 case IKeyboardEvent::pageDown: { | |
1557 int topLine = verticalScrollBar.scrollBoxPosition(); | |
1558 if (event.isCtrlDown()) { | |
1559 topLine = verticalScrollBar.scrollBoxRange().upperBound(); | |
1560 } | |
1561 topLine += verticalScrollBar.visibleCount(); | |
1562 verticalScrollBar.moveScrollBoxTo(topLine); | |
1563 repositionWindow(); | |
1564 return true; | |
1565 } | |
1566 } | |
1567 return false; | |
1568 } | |
1569 | |
1570 Boolean DrawingArea::command(ICommandEvent &event) { | |
1571 LOGSECTION("DrawingArea::command"); | |
1572 unsigned index = event.commandId(); | |
1573 if (index >= sortedLinks.size()) { | |
1574 LOGS("bad index"); | |
1575 messageBeep(); | |
1576 return true; | |
1577 } | |
1578 char *topic = sortedLinks[index].pointer(); | |
1579 LOGV(topic); | |
1580 AgString title = AgString::format("Help - %s", topic); | |
1581 IFrameWindow *helpWindow = AgFrame::windowRegistry.find(title); | |
1582 if (helpWindow) { | |
1583 helpWindow->setFocus(); | |
1584 return true; | |
1585 } | |
1586 helpWindow = new AgHelpWindow(topic); | |
1587 helpWindow->setAutoDeleteObject(); | |
1588 return true; | |
1589 } | |
1590 | |
1591 DrawingArea &DrawingArea::initPopUp() { | |
1592 LOGSECTION("DrawingArea::initPopUp"); | |
1593 LOGV((int) &popUpMenu); | |
1594 int n = sortedLinks.size(); | |
1595 int i; | |
1596 for (i = 0; i < n; i++) { | |
1597 AgString topic = sortedLinks[i]; | |
1598 LOGV(i) LCV(topic); | |
1599 popUpMenu.addText(i, topic.pointer()); | |
1600 } | |
1601 return *this; | |
1602 } | |
1603 | |
1604 Boolean DrawingArea::makePopUpMenu(IMenuEvent &event ) { | |
1605 LOGSECTION("DrawingArea::makePopUpMenu called"); | |
1606 //if (helpShowing) return true; | |
1607 //AgHelpHandler::handleEventsFor(frameWindow); | |
1608 //AgHelpHandler::handleEventsFor(&popUpMenu); | |
1609 //helpShowing = true; | |
1610 IPoint where(0,0); | |
1611 AgHelpWindow *frame = (AgHelpWindow *) frameWindow; | |
1612 where.setY(frame->helpView.verticalScrollBar.scrollBoxPosition()); | |
1613 if (rightButtonState == waitingForClick) { | |
1614 LOGV(event.mousePosition().asString()); | |
1615 where = event.mousePosition(); | |
1616 } | |
1617 //popUpMenu.show(event.mousePosition()); | |
1618 popUpMenu.show(where); | |
1619 //AgHelpHandler::stopHandlingEventsFor(frameWindow); | |
1620 //AgHelpHandler::stopHandlingEventsFor(&popUpMenu); | |
1621 //helpShowing = false; | |
1622 | |
1623 return true; | |
1624 } | |
1625 | |
1626 Boolean DrawingArea::showHelp(IEvent &event) { | |
1627 LOGSECTION("DrawingArea::showHelp"); | |
1628 AgString topic; | |
1629 LOGV((int) frameWindow); | |
1630 LOGV((int) this); | |
1631 LOGV((int) event.controlWindow()); | |
1632 LOGV((int) event.dispatchingWindow()); | |
1633 | |
1634 if (event.controlWindow() == frameWindow) { | |
1635 if (helpShowing) { | |
1636 return true; | |
1637 } | |
1638 helpShowing = true; | |
1639 //IPoint where = rect().minXMinY(); | |
1640 IPoint where(0, 0); | |
1641 AgHelpWindow *frame = (AgHelpWindow *) frameWindow; | |
1642 where.setY(frame->helpView.verticalScrollBar.scrollBoxPosition()); | |
1643 LOGV(where.asString()); | |
1644 popUpMenu.show(where); | |
1645 helpShowing = false; | |
1646 return true; | |
1647 } | |
1648 return false; | |
1649 } | |
1650 | |
1651 | |
1652 AgHelpWindow::AgHelpWindow(AgString topic) | |
1653 : AgFrame(IFrameWindow::systemMenu | |
1654 | IFrameWindow::maximizeButton | |
1655 | IFrameWindow::sizingBorder) | |
1656 , helpView(this, topic) | |
1657 { | |
1658 LOGSECTION("AgHelpWindow::AgHelpWindow"); | |
1659 AgString titleString = | |
1660 AgString::format("AnaGram Help - %s", topic.pointer()); | |
1661 AgString simpleTitle = | |
1662 AgString::format("Help - %s", topic.pointer()); | |
1663 | |
1664 windowTitle.setObjectText(titleString.pointer()); | |
1665 | |
1666 registerTitle(simpleTitle.pointer()); | |
1667 copyTitleText = simpleTitle; | |
1668 | |
1669 LOGV(simpleTitle.pointer()); | |
1670 setClient(&helpView); | |
1671 | |
1672 | |
1673 ISize minSize = frameRectFor(helpView.minimumSize()).size(); | |
1674 setMinimumSize(minSize); | |
1675 | |
1676 LOGV(helpView.minimumSize().asString()); | |
1677 LOGV(minSize.asString()); | |
1678 ISize tableSize = helpView.suggestSize(); | |
1679 | |
1680 int width = 56*font().avgCharWidth(); | |
1681 int testWidth = tableSize.width(); | |
1682 | |
1683 int titleWidth | |
1684 = windowTitle.displaySize(windowTitle.text()).width() | |
1685 + windowTitle.minimumSize().width(); | |
1686 | |
1687 LOGV(windowTitle.text()); | |
1688 LOGV(titleWidth); | |
1689 LOGV(testWidth); | |
1690 | |
1691 if (testWidth < titleWidth) { | |
1692 testWidth = titleWidth; | |
1693 } | |
1694 LOGV(width); | |
1695 LOGV(testWidth); | |
1696 | |
1697 if (testWidth < width/2) { | |
1698 width = width/2; | |
1699 } | |
1700 else if (testWidth > 2*width) { | |
1701 width = 2*width; | |
1702 } | |
1703 else { | |
1704 width = testWidth; | |
1705 } | |
1706 | |
1707 ISize clientSize(width, tableSize.height()); | |
1708 IRectangle frameRect(frameRectFor(clientSize)); | |
1709 LOGV(clientSize.asString()); | |
1710 LOGV(frameRect.size().asString()); | |
1711 sizeTo(frameRect.size()); | |
1712 | |
1713 positionFrame(); | |
1714 show(); | |
1715 } | |
1716 | |
1717 AgHelpWindow::~AgHelpWindow() {} | |
1718 | |
1719 Boolean AgHelpWindow::showHelp(AgString topic) { | |
1720 LOGSECTION("AgHelpWindow::showHelp"); | |
1721 LOGV(topic); | |
1722 | |
1723 AgString windowTitle; | |
1724 | |
1725 if (topic.exists()) { | |
1726 windowTitle = AgString::format("Help - %s", topic.pointer()); | |
1727 } | |
1728 if (!windowTitle.exists()) { | |
1729 return false; | |
1730 } | |
1731 | |
1732 LOGV(windowTitle); | |
1733 | |
1734 IFrameWindow *helpWindow = AgFrame::windowRegistry.find(windowTitle); | |
1735 LOGV((int) helpWindow); | |
1736 if (helpWindow == 0) { | |
1737 helpWindow = new AgHelpWindow(topic); | |
1738 helpWindow->setAutoDeleteObject(); | |
1739 } | |
1740 helpWindow->show(); | |
1741 helpWindow->setFocus(); | |
1742 //BringWindowToTop(helpWindow->handle()); | |
1743 return true; | |
1744 } | |
1745 | |
1746 Boolean AgHelpWindow::showHelpCentered(AgString topic) { | |
1747 LOGSECTION("AgHelpWindow::showHelp"); | |
1748 AgString windowTitle; | |
1749 | |
1750 if (topic.exists()) { | |
1751 windowTitle = AgString::format("Help - %s", topic.pointer()); | |
1752 } | |
1753 if (!windowTitle.exists()) { | |
1754 return false; | |
1755 } | |
1756 | |
1757 LOGV(topic.pointer()); | |
1758 | |
1759 IFrameWindow *helpWindow = AgFrame::windowRegistry.find(windowTitle); | |
1760 LOGV((int) helpWindow); | |
1761 if (helpWindow == 0) { | |
1762 helpWindow = new AgHelpWindow(topic); | |
1763 helpWindow->setAutoDeleteObject(); | |
1764 } | |
1765 IPoint where = (IPair) place(IWindow::desktopWindow()->size(), | |
1766 helpWindow->size(), 11); | |
1767 helpWindow->moveTo(where); | |
1768 helpWindow->show().setFocus(); | |
1769 return true; | |
1770 } | |
1771 | |
1772 |