outline.pas 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685
  1. unit outline;
  2. {$CODEPAGE cp437}
  3. {***************************************************************************}
  4. interface
  5. {***************************************************************************}
  6. uses drivers,objects,views;
  7. type Pnode=^Tnode;
  8. Tnode=record
  9. next:Pnode;
  10. text:Pstring;
  11. childlist:Pnode;
  12. expanded:boolean;
  13. end;
  14. Poutlineviewer=^Toutlineviewer;
  15. Toutlineviewer=object(Tscroller)
  16. foc:sw_integer;
  17. constructor init(var bounds:Trect;
  18. AHscrollbar,AVscrollbar:Pscrollbar);
  19. procedure adjust(node:pointer;expand:boolean);virtual;
  20. function creategraph(level:integer;lines:longint;
  21. flags:word;levwidth,endwidth:integer;
  22. const chars:string):string;
  23. procedure draw;virtual;
  24. procedure expandall(node:pointer);
  25. function firstthat(test:pointer):pointer;
  26. procedure focused(i:sw_integer);virtual;
  27. procedure foreach(action:pointer);
  28. function getchild(node:pointer;i:sw_integer):pointer;virtual;
  29. function getgraph(level:integer;lines:longint;flags:word):string;
  30. function getnode(i:sw_integer):pointer;virtual;
  31. function getnumchildren(node:pointer):sw_integer;virtual;
  32. function getpalette:Ppalette;virtual;
  33. function getroot:pointer;virtual;
  34. function gettext(node:pointer):string;virtual;
  35. procedure handleevent(var event:Tevent);virtual;
  36. function haschildren(node:pointer):boolean;virtual;
  37. function isexpanded(node:pointer):boolean;virtual;
  38. function isselected(i:sw_integer):boolean;virtual;
  39. procedure selected(i:sw_integer);virtual;
  40. procedure setstate(Astate:word;enable:boolean);virtual;
  41. procedure update;
  42. private
  43. procedure set_focus(Afocus:sw_integer);
  44. function do_recurse(action,callerframe:pointer;
  45. stop_if_found:boolean):pointer;
  46. end;
  47. Poutline=^Toutline;
  48. Toutline=object(Toutlineviewer)
  49. root:Pnode;
  50. constructor init(var bounds:Trect;
  51. AHscrollbar,AVscrollbar:Pscrollbar;
  52. Aroot:Pnode);
  53. procedure adjust(node:pointer;expand:boolean);virtual;
  54. function getchild(node:pointer;i:sw_integer):pointer;virtual;
  55. function getnumchildren(node:pointer):sw_integer;virtual;
  56. function getroot:pointer;virtual;
  57. function gettext(node:pointer):string;virtual;
  58. function haschildren(node:pointer):boolean;virtual;
  59. function isexpanded(node:pointer):boolean;virtual;
  60. destructor done;virtual;
  61. end;
  62. const ovExpanded = $1;
  63. ovChildren = $2;
  64. ovLast = $4;
  65. Coutlineviewer=Cscroller+#8#8;
  66. function newnode(const Atext:string;Achildren,Anext:Pnode):Pnode;
  67. procedure disposenode(node:Pnode);
  68. {***************************************************************************}
  69. implementation
  70. {***************************************************************************}
  71. type TMyFunc = function(_EBP: Pointer; Cur: Pointer;
  72. Level, Position: sw_integer; Lines: LongInt;
  73. Flags: Word): Boolean;
  74. function newnode(const Atext:string;Achildren,Anext:Pnode):Pnode;
  75. begin
  76. newnode:=new(Pnode);
  77. with newnode^ do
  78. begin
  79. next:=Anext;
  80. text:=newstr(Atext);
  81. childlist:=Achildren;
  82. expanded:=true;
  83. end;
  84. end;
  85. procedure disposenode(node:Pnode);
  86. var next:Pnode;
  87. begin
  88. while node<>nil do
  89. begin
  90. disposenode(node^.childlist);
  91. disposestr(node^.text);
  92. next:=node^.next;
  93. dispose(node);
  94. node:=next;
  95. end;
  96. end;
  97. {+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++}
  98. { Toutlineviewer object methods }
  99. {+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++}
  100. constructor Toutlineviewer.init(var bounds:Trect;
  101. AHscrollbar,AVscrollbar:Pscrollbar);
  102. begin
  103. inherited init(bounds,AHscrollbar,AVscrollbar);
  104. foc:=0;
  105. growmode:=gfGrowHiX+gfGrowHiY;
  106. end;
  107. procedure Toutlineviewer.adjust(node:pointer;expand:boolean);
  108. begin
  109. abstract;
  110. end;
  111. function TOutlineViewer.CreateGraph(Level: Integer; Lines: LongInt;
  112. Flags: Word; LevWidth, EndWidth: Integer;
  113. const Chars: String): String;
  114. const
  115. FillerOrBar = 0;
  116. YorL = 2;
  117. StraightOrTee= 4;
  118. Retracted = 6;
  119. var
  120. Last, Children, Expanded: Boolean;
  121. I , J : Byte;
  122. Graph : String;
  123. begin
  124. { Load registers }
  125. graph:=space(Level*LevWidth+EndWidth+1);
  126. { Write bar characters }
  127. J := 1;
  128. while (Level > 0) do
  129. begin
  130. Inc(J);
  131. if (Lines and 1) <> 0 then
  132. Graph[J] := Chars[FillerOrBar+2]
  133. else
  134. Graph[J] := Chars[FillerOrBar+1];
  135. for I := 1 to LevWidth - 1 do
  136. Graph[I]:= Chars[FillerOrBar+1];
  137. J := J + LevWidth - 1;
  138. Dec(Level);
  139. Lines := Lines shr 1;
  140. end;
  141. { Write end characters }
  142. Dec(EndWidth);
  143. if EndWidth > 0 then
  144. begin
  145. Inc(J);
  146. if Flags and ovLast <> 0 then
  147. Graph[J] := Chars[YorL+2]
  148. else
  149. Graph[J] := Chars[YorL+1];
  150. Dec(EndWidth);
  151. if EndWidth > 0 then
  152. begin
  153. Dec(EndWidth);
  154. for I := 1 to EndWidth do
  155. Graph[I]:= Chars[StraightOrTee+1];
  156. J := J + EndWidth;
  157. Inc(J);
  158. if (Flags and ovChildren) <> 0 then
  159. Graph[J] := Chars[StraightOrTee+2]
  160. else
  161. Graph[J] := Chars[StraightOrTee+1];
  162. end;
  163. Inc(J);
  164. if Flags and ovExpanded <> 0 then
  165. Graph[J] := Chars[Retracted+2]
  166. else
  167. Graph[J] := Chars[Retracted+1];
  168. end;
  169. Graph[0] := Char(J);
  170. CreateGraph := Graph;
  171. end;
  172. function Toutlineviewer.do_recurse(action,callerframe:pointer;
  173. stop_if_found:boolean):pointer;
  174. var position:sw_integer;
  175. r:pointer;
  176. function recurse(cur:pointer;level:integer;lines:longint;lastchild:boolean):pointer;
  177. var i,childcount:sw_integer;
  178. child:pointer;
  179. flags:word;
  180. children,expanded,found:boolean;
  181. begin
  182. inc(position);
  183. recurse:=nil;
  184. children:=haschildren(cur);
  185. expanded:=isexpanded(cur);
  186. {Determine flags.}
  187. flags:=0;
  188. if not children or expanded then
  189. inc(flags,ovExpanded);
  190. if children and expanded then
  191. inc(flags,ovChildren);
  192. if lastchild then
  193. inc(flags,ovLast);
  194. {Call the function.}
  195. found:=TMyFunc(action)(callerframe,cur,level,position,lines,flags);
  196. if stop_if_found and found then
  197. recurse:=cur
  198. else if children and expanded then {Recurse children?}
  199. begin
  200. if not lastchild then
  201. lines:=lines or (1 shl level);
  202. {Iterate all childs.}
  203. childcount:=getnumchildren(cur);
  204. for i:=0 to childcount-1 do
  205. begin
  206. child:=getchild(cur,i);
  207. if (child<>nil) and (level<31) then
  208. recurse:=recurse(child,level+1,lines,i=childcount-1);
  209. {Did we find a node?}
  210. if recurse<>nil then
  211. break;
  212. end;
  213. end;
  214. end;
  215. begin
  216. position:=-1;
  217. r:=getroot;
  218. if r<>nil then
  219. do_recurse:=recurse(r,0,0,true)
  220. else
  221. do_recurse:=nil;
  222. end;
  223. procedure Toutlineviewer.draw;
  224. var c_normal,c_normal_x,c_select,c_focus:byte;
  225. maxpos:sw_integer;
  226. b:Tdrawbuffer;
  227. function draw_item(cur:pointer;level,position:sw_integer;
  228. lines:longint;flags:word):boolean;
  229. var c,i:byte;
  230. s,t:string;
  231. begin
  232. draw_item:=position>=delta.y+size.y;
  233. if (position<delta.y) or draw_item then
  234. exit;
  235. maxpos:=position;
  236. s:=getgraph(level,lines,flags);
  237. t:=gettext(cur);
  238. {Determine text colour.}
  239. if (foc=position) and (state and sffocused<>0) then
  240. c:=c_focus
  241. else if isselected(position) then
  242. c:=c_select
  243. else if flags and ovexpanded<>0 then
  244. c:=c_normal_x
  245. else
  246. c:=c_normal;
  247. {Fill drawbuffer with graph and text to draw.}
  248. for i:=0 to size.x-1 do
  249. begin
  250. wordrec(b[i]).hi:=c;
  251. if i+delta.x<length(s) then
  252. wordrec(b[i]).lo:=byte(s[1+i+delta.x])
  253. else if 1+i+delta.x-length(s)<=length(t) then
  254. wordrec(b[i]).lo:=byte(t[1+i+delta.x-length(s)])
  255. else
  256. wordrec(b[i]).lo:=byte(' ');
  257. end;
  258. {Draw!}
  259. writeline(0,position-delta.y,size.x,1,b);
  260. end;
  261. begin
  262. c_normal:=getcolor(4);
  263. c_normal_x:=getcolor(1);
  264. c_focus:=getcolor(2);
  265. c_select:=getcolor(3);
  266. maxpos:=-1;
  267. foreach(@draw_item);
  268. movechar(b,' ',c_normal,size.x);
  269. writeline(0,maxpos+1,size.x,size.y-(maxpos-delta.y),b);
  270. end;
  271. procedure Toutlineviewer.expandall(node:pointer);
  272. var i:sw_integer;
  273. begin
  274. if haschildren(node) then
  275. begin
  276. for i:=0 to getnumchildren(node)-1 do
  277. expandall(getchild(node,i));
  278. adjust(node,true);
  279. end;
  280. end;
  281. function Toutlineviewer.firstthat(test:pointer):pointer;
  282. begin
  283. firstthat:=do_recurse(test,get_caller_frame(get_frame),true);
  284. end;
  285. procedure Toutlineviewer.focused(i:sw_integer);
  286. begin
  287. foc:=i;
  288. end;
  289. procedure Toutlineviewer.foreach(action:pointer);
  290. begin
  291. do_recurse(action,get_caller_frame(get_frame),false);
  292. end;
  293. function Toutlineviewer.getchild(node:pointer;i:sw_integer):pointer;
  294. begin
  295. abstract;
  296. end;
  297. function Toutlineviewer.getgraph(level:integer;lines:longint;
  298. flags:word):string;
  299. begin
  300. getgraph:=creategraph(level,lines,flags,3,3,' ³ÃÀÄÄ+Ä');
  301. end;
  302. function Toutlineviewer.getnode(i:sw_integer):pointer;
  303. function test_position(node:pointer;level,position:sw_integer;lines:longInt;
  304. flags:word):boolean;
  305. begin
  306. test_position:=position=i;
  307. end;
  308. begin
  309. getnode:=firstthat(@test_position);
  310. end;
  311. function Toutlineviewer.getnumchildren(node:pointer):sw_integer;
  312. begin
  313. abstract;
  314. end;
  315. function Toutlineviewer.getpalette:Ppalette;
  316. const p:string[length(Coutlineviewer)]=Coutlineviewer;
  317. begin
  318. getpalette:=@p;
  319. end;
  320. function Toutlineviewer.getroot:pointer;
  321. begin
  322. abstract;
  323. end;
  324. function Toutlineviewer.gettext(node:pointer):string;
  325. begin
  326. abstract;
  327. end;
  328. procedure Toutlineviewer.handleevent(var event:Tevent);
  329. var mouse:Tpoint;
  330. cur:pointer;
  331. new_focus:sw_integer;
  332. count:byte;
  333. handled,m,mouse_drag:boolean;
  334. graph:string;
  335. function graph_of_focus(var graph:string):pointer;
  336. var _level:sw_integer;
  337. _lines:longInt;
  338. _flags:word;
  339. function find_focused(cur:pointer;level,position:sw_integer;
  340. lines:longint;flags:word):boolean;
  341. begin
  342. find_focused:=position=foc;
  343. if find_focused then
  344. begin
  345. _level:=level;
  346. _lines:=lines;
  347. _flags:=flags;
  348. end;
  349. end;
  350. begin
  351. graph_of_focus:=firstthat(@find_focused);
  352. graph:=getgraph(_level,_lines,_flags);
  353. end;
  354. const skip_mouse_events=3;
  355. begin
  356. inherited handleevent(event);
  357. case event.what of
  358. evKeyboard:
  359. begin
  360. new_focus:=foc;
  361. handled:=true;
  362. case ctrltoarrow(event.keycode) of
  363. kbUp,kbLeft:
  364. dec(new_focus);
  365. kbDown,kbRight:
  366. inc(new_focus);
  367. kbPgDn:
  368. inc(new_focus,size.y-1);
  369. kbPgUp:
  370. dec(new_focus,size.y-1);
  371. kbCtrlPgUp:
  372. new_focus:=0;
  373. kbCtrlPgDn:
  374. new_focus:=limit.y-1;
  375. kbHome:
  376. new_focus:=delta.y;
  377. kbEnd:
  378. new_focus:=delta.y+size.y-1;
  379. kbCtrlEnter,kbEnter:
  380. selected(new_focus);
  381. else
  382. case event.charcode of
  383. '-','+':
  384. begin
  385. adjust(getnode(new_focus),event.charcode='+');
  386. update;
  387. end;
  388. '*':
  389. begin
  390. expandall(getnode(new_focus));
  391. update;
  392. end;
  393. else
  394. handled:=false;
  395. end;
  396. end;
  397. if new_focus<0 then
  398. new_focus:=0;
  399. if new_focus>=limit.y then
  400. new_focus:=limit.y-1;
  401. if foc<>new_focus then
  402. set_focus(new_focus);
  403. if handled then
  404. clearevent(event);
  405. end;
  406. evMouseDown:
  407. begin
  408. count:=1;
  409. mouse_drag:=false;
  410. repeat
  411. makelocal(event.where,mouse);
  412. if mouseinview(event.where) then
  413. new_focus:=delta.y+mouse.y
  414. else
  415. begin
  416. inc(count,byte(event.what=evMouseAuto));
  417. if count and skip_mouse_events=0 then
  418. begin
  419. if mouse.y<0 then
  420. dec(new_focus);
  421. if mouse.y>=size.y then
  422. inc(new_focus);
  423. end;
  424. end;
  425. if new_focus<0 then
  426. new_focus:=0;
  427. if new_focus>=limit.y then
  428. new_focus:=limit.y-1;
  429. if foc<>new_focus then
  430. set_focus(new_focus);
  431. m:=mouseevent(event,evMouseMove+evMouseAuto);
  432. if m then
  433. mouse_drag:=true;
  434. until not m;
  435. if event.double then
  436. selected(foc)
  437. else if not mouse_drag then
  438. begin
  439. cur:=graph_of_focus(graph);
  440. if mouse.x<length(graph) then
  441. begin
  442. adjust(cur,not isexpanded(cur));
  443. update;
  444. end;
  445. end;
  446. end;
  447. end;
  448. end;
  449. function Toutlineviewer.haschildren(node:pointer):boolean;
  450. begin
  451. abstract;
  452. end;
  453. function Toutlineviewer.isexpanded(node:pointer):boolean;
  454. begin
  455. abstract;
  456. end;
  457. function Toutlineviewer.isselected(i:sw_integer):boolean;
  458. begin
  459. isselected:=foc=i;
  460. end;
  461. procedure Toutlineviewer.selected(i:sw_integer);
  462. begin
  463. {Does nothing by default.}
  464. end;
  465. procedure Toutlineviewer.set_focus(Afocus:sw_integer);
  466. begin
  467. assert((Afocus>=0) and (Afocus<limit.y));
  468. focused(Afocus);
  469. if Afocus<delta.y then
  470. scrollto(delta.x,Afocus)
  471. else if Afocus-size.y>=delta.y then
  472. scrollto(delta.x,Afocus-size.y+1);
  473. drawview;
  474. end;
  475. procedure Toutlineviewer.setstate(Astate:word;enable:boolean);
  476. begin
  477. if Astate and sffocused<>0 then
  478. drawview;
  479. inherited setstate(Astate,enable);
  480. end;
  481. procedure Toutlineviewer.update;
  482. var count:sw_integer;
  483. maxwidth:byte;
  484. procedure check_item(cur:pointer;level,position:sw_integer;
  485. lines:longint;flags:word);
  486. var width:word;
  487. begin
  488. inc(count);
  489. width:=length(gettext(cur))+length(getgraph(level,lines,flags));
  490. if width>maxwidth then
  491. maxwidth:=width;
  492. end;
  493. begin
  494. count:=0;
  495. maxwidth:=0;
  496. foreach(@check_item);
  497. setlimit(maxwidth,count);
  498. set_focus(foc);
  499. end;
  500. {+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++}
  501. { Toutline object methods }
  502. {+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++}
  503. constructor Toutline.init(var bounds:Trect;
  504. AHscrollbar,AVscrollbar:Pscrollbar;
  505. Aroot:Pnode);
  506. begin
  507. inherited init(bounds,AHscrollbar,AVscrollbar);
  508. root:=Aroot;
  509. update;
  510. end;
  511. procedure Toutline.adjust(node:pointer;expand:boolean);
  512. begin
  513. assert(node<>nil);
  514. Pnode(node)^.expanded:=expand;
  515. end;
  516. function Toutline.getnumchildren(node:pointer):sw_integer;
  517. var p:Pnode;
  518. begin
  519. assert(node<>nil);
  520. p:=Pnode(node)^.childlist;
  521. getnumchildren:=0;
  522. while p<>nil do
  523. begin
  524. inc(getnumchildren);
  525. p:=p^.next;
  526. end;
  527. end;
  528. function Toutline.getchild(node:pointer;i:sw_integer):pointer;
  529. begin
  530. assert(node<>nil);
  531. getchild:=Pnode(node)^.childlist;
  532. while i<>0 do
  533. begin
  534. dec(i);
  535. getchild:=Pnode(getchild)^.next;
  536. end;
  537. end;
  538. function Toutline.getroot:pointer;
  539. begin
  540. getroot:=root;
  541. end;
  542. function Toutline.gettext(node:pointer):string;
  543. begin
  544. assert(node<>nil);
  545. gettext:=Pnode(node)^.text^;
  546. end;
  547. function Toutline.haschildren(node:pointer):boolean;
  548. begin
  549. assert(node<>nil);
  550. haschildren:=Pnode(node)^.childlist<>nil;
  551. end;
  552. function Toutline.isexpanded(node:pointer):boolean;
  553. begin
  554. assert(node<>nil);
  555. isexpanded:=Pnode(node)^.expanded;
  556. end;
  557. destructor Toutline.done;
  558. begin
  559. disposenode(root);
  560. inherited done;
  561. end;
  562. end.