outline.pas 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705
  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,
  284. { On most systems, locals are accessed relative to base pointer,
  285. but for MIPS cpu, they are accessed relative to stack pointer.
  286. This needs adaptation for so low level routines,
  287. like MethodPointerLocal and related objects unit functions. }
  288. {$ifndef FPC_LOCALS_ARE_STACK_REG_RELATIVE}
  289. get_caller_frame(get_frame,get_pc_addr)
  290. {$else}
  291. get_frame
  292. {$endif}
  293. ,true);
  294. end;
  295. procedure Toutlineviewer.focused(i:sw_integer);
  296. begin
  297. foc:=i;
  298. end;
  299. procedure Toutlineviewer.foreach(action:pointer);
  300. begin
  301. do_recurse(action,
  302. { On most systems, locals are accessed relative to base pointer,
  303. but for MIPS cpu, they are accessed relative to stack pointer.
  304. This needs adaptation for so low level routines,
  305. like MethodPointerLocal and related objects unit functions. }
  306. {$ifndef FPC_LOCALS_ARE_STACK_REG_RELATIVE}
  307. get_caller_frame(get_frame,get_pc_addr)
  308. {$else}
  309. get_frame
  310. {$endif}
  311. ,false);
  312. end;
  313. function Toutlineviewer.getchild(node:pointer;i:sw_integer):pointer;
  314. begin
  315. abstract;
  316. end;
  317. function Toutlineviewer.getgraph(level:integer;lines:longint;
  318. flags:word):string;
  319. begin
  320. getgraph:=creategraph(level,lines,flags,3,3,' ³ÃÀÄÄ+Ä');
  321. end;
  322. function Toutlineviewer.getnode(i:sw_integer):pointer;
  323. function test_position(node:pointer;level,position:sw_integer;lines:longInt;
  324. flags:word):boolean;
  325. begin
  326. test_position:=position=i;
  327. end;
  328. begin
  329. getnode:=firstthat(@test_position);
  330. end;
  331. function Toutlineviewer.getnumchildren(node:pointer):sw_integer;
  332. begin
  333. abstract;
  334. end;
  335. function Toutlineviewer.getpalette:Ppalette;
  336. const p:string[length(Coutlineviewer)]=Coutlineviewer;
  337. begin
  338. getpalette:=@p;
  339. end;
  340. function Toutlineviewer.getroot:pointer;
  341. begin
  342. abstract;
  343. end;
  344. function Toutlineviewer.gettext(node:pointer):string;
  345. begin
  346. abstract;
  347. end;
  348. procedure Toutlineviewer.handleevent(var event:Tevent);
  349. var mouse:Tpoint;
  350. cur:pointer;
  351. new_focus:sw_integer;
  352. count:byte;
  353. handled,m,mouse_drag:boolean;
  354. graph:string;
  355. function graph_of_focus(var graph:string):pointer;
  356. var _level:sw_integer;
  357. _lines:longInt;
  358. _flags:word;
  359. function find_focused(cur:pointer;level,position:sw_integer;
  360. lines:longint;flags:word):boolean;
  361. begin
  362. find_focused:=position=foc;
  363. if find_focused then
  364. begin
  365. _level:=level;
  366. _lines:=lines;
  367. _flags:=flags;
  368. end;
  369. end;
  370. begin
  371. graph_of_focus:=firstthat(@find_focused);
  372. graph:=getgraph(_level,_lines,_flags);
  373. end;
  374. const skip_mouse_events=3;
  375. begin
  376. inherited handleevent(event);
  377. case event.what of
  378. evKeyboard:
  379. begin
  380. new_focus:=foc;
  381. handled:=true;
  382. case ctrltoarrow(event.keycode) of
  383. kbUp,kbLeft:
  384. dec(new_focus);
  385. kbDown,kbRight:
  386. inc(new_focus);
  387. kbPgDn:
  388. inc(new_focus,size.y-1);
  389. kbPgUp:
  390. dec(new_focus,size.y-1);
  391. kbCtrlPgUp:
  392. new_focus:=0;
  393. kbCtrlPgDn:
  394. new_focus:=limit.y-1;
  395. kbHome:
  396. new_focus:=delta.y;
  397. kbEnd:
  398. new_focus:=delta.y+size.y-1;
  399. kbCtrlEnter,kbEnter:
  400. selected(new_focus);
  401. else
  402. case event.charcode of
  403. '-','+':
  404. begin
  405. adjust(getnode(new_focus),event.charcode='+');
  406. update;
  407. end;
  408. '*':
  409. begin
  410. expandall(getnode(new_focus));
  411. update;
  412. end;
  413. else
  414. handled:=false;
  415. end;
  416. end;
  417. if new_focus<0 then
  418. new_focus:=0;
  419. if new_focus>=limit.y then
  420. new_focus:=limit.y-1;
  421. if foc<>new_focus then
  422. set_focus(new_focus);
  423. if handled then
  424. clearevent(event);
  425. end;
  426. evMouseDown:
  427. begin
  428. count:=1;
  429. mouse_drag:=false;
  430. repeat
  431. makelocal(event.where,mouse);
  432. if mouseinview(event.where) then
  433. new_focus:=delta.y+mouse.y
  434. else
  435. begin
  436. inc(count,byte(event.what=evMouseAuto));
  437. if count and skip_mouse_events=0 then
  438. begin
  439. if mouse.y<0 then
  440. dec(new_focus);
  441. if mouse.y>=size.y then
  442. inc(new_focus);
  443. end;
  444. end;
  445. if new_focus<0 then
  446. new_focus:=0;
  447. if new_focus>=limit.y then
  448. new_focus:=limit.y-1;
  449. if foc<>new_focus then
  450. set_focus(new_focus);
  451. m:=mouseevent(event,evMouseMove+evMouseAuto);
  452. if m then
  453. mouse_drag:=true;
  454. until not m;
  455. if event.double then
  456. selected(foc)
  457. else if not mouse_drag then
  458. begin
  459. cur:=graph_of_focus(graph);
  460. if mouse.x<length(graph) then
  461. begin
  462. adjust(cur,not isexpanded(cur));
  463. update;
  464. end;
  465. end;
  466. end;
  467. end;
  468. end;
  469. function Toutlineviewer.haschildren(node:pointer):boolean;
  470. begin
  471. abstract;
  472. end;
  473. function Toutlineviewer.isexpanded(node:pointer):boolean;
  474. begin
  475. abstract;
  476. end;
  477. function Toutlineviewer.isselected(i:sw_integer):boolean;
  478. begin
  479. isselected:=foc=i;
  480. end;
  481. procedure Toutlineviewer.selected(i:sw_integer);
  482. begin
  483. {Does nothing by default.}
  484. end;
  485. procedure Toutlineviewer.set_focus(Afocus:sw_integer);
  486. begin
  487. assert((Afocus>=0) and (Afocus<limit.y));
  488. focused(Afocus);
  489. if Afocus<delta.y then
  490. scrollto(delta.x,Afocus)
  491. else if Afocus-size.y>=delta.y then
  492. scrollto(delta.x,Afocus-size.y+1);
  493. drawview;
  494. end;
  495. procedure Toutlineviewer.setstate(Astate:word;enable:boolean);
  496. begin
  497. if Astate and sffocused<>0 then
  498. drawview;
  499. inherited setstate(Astate,enable);
  500. end;
  501. procedure Toutlineviewer.update;
  502. var count:sw_integer;
  503. maxwidth:byte;
  504. procedure check_item(cur:pointer;level,position:sw_integer;
  505. lines:longint;flags:word);
  506. var width:word;
  507. begin
  508. inc(count);
  509. width:=length(gettext(cur))+length(getgraph(level,lines,flags));
  510. if width>maxwidth then
  511. maxwidth:=width;
  512. end;
  513. begin
  514. count:=0;
  515. maxwidth:=0;
  516. foreach(@check_item);
  517. setlimit(maxwidth,count);
  518. set_focus(foc);
  519. end;
  520. {+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++}
  521. { Toutline object methods }
  522. {+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++}
  523. constructor Toutline.init(var bounds:Trect;
  524. AHscrollbar,AVscrollbar:Pscrollbar;
  525. Aroot:Pnode);
  526. begin
  527. inherited init(bounds,AHscrollbar,AVscrollbar);
  528. root:=Aroot;
  529. update;
  530. end;
  531. procedure Toutline.adjust(node:pointer;expand:boolean);
  532. begin
  533. assert(node<>nil);
  534. Pnode(node)^.expanded:=expand;
  535. end;
  536. function Toutline.getnumchildren(node:pointer):sw_integer;
  537. var p:Pnode;
  538. begin
  539. assert(node<>nil);
  540. p:=Pnode(node)^.childlist;
  541. getnumchildren:=0;
  542. while p<>nil do
  543. begin
  544. inc(getnumchildren);
  545. p:=p^.next;
  546. end;
  547. end;
  548. function Toutline.getchild(node:pointer;i:sw_integer):pointer;
  549. begin
  550. assert(node<>nil);
  551. getchild:=Pnode(node)^.childlist;
  552. while i<>0 do
  553. begin
  554. dec(i);
  555. getchild:=Pnode(getchild)^.next;
  556. end;
  557. end;
  558. function Toutline.getroot:pointer;
  559. begin
  560. getroot:=root;
  561. end;
  562. function Toutline.gettext(node:pointer):string;
  563. begin
  564. assert(node<>nil);
  565. gettext:=Pnode(node)^.text^;
  566. end;
  567. function Toutline.haschildren(node:pointer):boolean;
  568. begin
  569. assert(node<>nil);
  570. haschildren:=Pnode(node)^.childlist<>nil;
  571. end;
  572. function Toutline.isexpanded(node:pointer):boolean;
  573. begin
  574. assert(node<>nil);
  575. isexpanded:=Pnode(node)^.expanded;
  576. end;
  577. destructor Toutline.done;
  578. begin
  579. disposenode(root);
  580. inherited done;
  581. end;
  582. end.