outline.pas 16 KB

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