FrameTabSessions.pas 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816
  1. {Frame que contiene un control similar a un TPageControl pero que abre ventanas de
  2. sesión (un panel de texto y una pantalla de Terminal.)
  3. Este frame es similar al usado en los compiladores PicPas y P65Pas.}
  4. unit FrameTabSessions;
  5. {$mode objfpc}{$H+}
  6. interface
  7. uses
  8. Classes, SysUtils, FileUtil, LazUTF8, LazFileUtils, Forms, Controls, Dialogs,
  9. ComCtrls, ExtCtrls, Graphics, LCLProc, Menus, LCLType, StdCtrls,
  10. fgl, Types, SynEdit, SynEditKeyCmds, Globales,
  11. uResaltTerm, FrameTabSession, SynFacilUtils, SynFacilBasic, MisUtils;
  12. type
  13. { TPage }
  14. TPage = class
  15. procedure SetVisible(state: boolean); virtual; abstract;
  16. end;
  17. TRecentLinks = specialize TFPGObjectList<TLabel>;
  18. TSessionTabEvent = procedure(ed: TfraTabSession) of object;
  19. { TfraTabSessions }
  20. TfraTabSessions = class(TFrame)
  21. published
  22. ImgCompletion: TImageList;
  23. lblNewSession: TLabel;
  24. lblOpenSession: TLabel;
  25. lblRecents: TLabel;
  26. mnCloseOthers: TMenuItem;
  27. mnCloseAll: TMenuItem;
  28. mnNewTab: TMenuItem;
  29. mnCloseTab: TMenuItem;
  30. mnNewTab1: TMenuItem;
  31. OpenDialog1: TOpenDialog;
  32. panHeader: TPanel;
  33. Panel2: TPanel;
  34. panContent: TPanel;
  35. PopUpTabs: TPopupMenu;
  36. UpDown1: TUpDown;
  37. procedure lblNewSessionMouseDown(Sender: TObject; Button: TMouseButton;
  38. Shift: TShiftState; X, Y: Integer);
  39. procedure lblNewSessionMouseEnter(Sender: TObject);
  40. procedure lblNewSessionMouseLeave(Sender: TObject);
  41. procedure lblOpenSessionMouseDown(Sender: TObject; Button: TMouseButton;
  42. Shift: TShiftState; X, Y: Integer);
  43. procedure lblOpenSessionMouseEnter(Sender: TObject);
  44. procedure lblOpenSessionMouseLeave(Sender: TObject);
  45. procedure lblRecentsMouseDown(Sender: TObject; Button: TMouseButton;
  46. Shift: TShiftState; X, Y: Integer);
  47. procedure lblRecentsMouseEnter(Sender: TObject);
  48. procedure lblRecentsMouseLeave(Sender: TObject);
  49. procedure mnCloseOthersClick(Sender: TObject);
  50. procedure mnCloseAllClick(Sender: TObject);
  51. procedure mnCloseTabClick(Sender: TObject);
  52. procedure mnNewTabClick(Sender: TObject);
  53. procedure UpDown1Click(Sender: TObject; Button: TUDBtnType);
  54. private //Métodos para dibujo de las lenguetas
  55. xIniTabs : integer; //Coordenada inicial desde donde se dibujan las lenguetas
  56. tabDrag : integer;
  57. tabSelec : integer;
  58. procedure lblRecentLinkClick(Sender: TObject);
  59. procedure MakeActiveTabVisible;
  60. procedure Panel1DragDrop(Sender, Source: TObject; X, Y: Integer);
  61. procedure Panel1DragOver(Sender, Source: TObject; X, Y: Integer;
  62. State: TDragState; var Accept: Boolean);
  63. procedure Panel1EndDrag(Sender, Target: TObject; X, Y: Integer);
  64. procedure RefreshTabs;
  65. procedure SetTabIndex(AValue: integer);
  66. procedure DibLeng(x1, x2: integer; coltex: TColor; Activo: boolean; txt: string
  67. ); //dibuja una lengueta
  68. procedure Panel1MouseDown(Sender: TObject; Button: TMouseButton;
  69. Shift: TShiftState; X, Y: Integer);
  70. procedure Panel1MouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer
  71. );
  72. procedure Panel1MouseUp(Sender: TObject; Button: TMouseButton;
  73. Shift: TShiftState; X, Y: Integer);
  74. procedure UpdateX1CoordTabs;
  75. procedure Panel1Paint(Sender: TObject);
  76. procedure InitTabs;
  77. private
  78. FTabIndex : integer;
  79. recentLinks: TRecentLinks; //Lista de enlaces para archivos recientes.
  80. showRecents: boolean;
  81. function LastIndex: integer;
  82. function NewName(prefix, ext: string): string;
  83. procedure DeleteEdit;
  84. public //Manejo de pestañas y páginas
  85. pages : TPages;
  86. property TabIndex: integer read FTabIndex write SetTabIndex; //panel actualmente activo
  87. function Count: integer;
  88. function ActivePage: TfraTabSession;
  89. function SearchEditorIdxByTab(tabName: string): integer;
  90. procedure SelectNextEditor;
  91. procedure SelectPrevEditor;
  92. function HasFocus: boolean;
  93. procedure SetFocus; override;
  94. procedure UpdateTabWidth(pag: TfraTabSession);
  95. public //Eventos
  96. OnSelectEditor: procedure of object; //Cuando cambia la selección de editor
  97. OnRequireSynEditConfig: procedure(ed: TsynEdit) of object;
  98. OnRequireSetCompletion: procedure(ed: TfraTabSession) of object;
  99. public
  100. {Evento general asociado a una página del control.
  101. El parámetro "event" es una cadena con el nombre del evento.
  102. La página se pasa en "page" como "TObject" para soportar cualquier tipo de "frame"
  103. como página.
  104. El parámetro "res" es la respuesta que se da al evento.}
  105. OnPageEvent: procedure(event: string; page: TObject; out res: string) of object;
  106. procedure PageEvent(event: string; page: TObject; out res: string);
  107. public //Administración de páginas
  108. tmpPath: string; //ruta usada para crear archivos temporales para los editores
  109. function AddPage(ext: string): TfraTabSession;
  110. function ClosePage: boolean;
  111. function CloseAll(out lstClosedFiles: string): boolean;
  112. public //Inicialización
  113. procedure UpdateSynEditConfig;
  114. procedure UpdateSynEditCompletion;
  115. procedure UpdateRecents(recents: TStringList);
  116. constructor Create(AOwner: TComponent); override;
  117. destructor Destroy; override;
  118. procedure SetLanguage;
  119. end;
  120. implementation
  121. {$R *.lfm}
  122. const
  123. SEPAR_TABS = 2; //Separación adicional, entre pestañas
  124. WIDTH_ADD_TAB = 40; //Ancho de botón "Agregar página"
  125. { TfraTabSessions }
  126. procedure TfraTabSessions.SetLanguage;
  127. begin
  128. //{$I ..\language\tra_FrameEditView.pas}
  129. end;
  130. procedure TfraTabSessions.RefreshTabs;
  131. begin
  132. if pages.Count=0 then begin
  133. panHeader.Visible := false;
  134. end else begin
  135. panHeader.Visible := true;
  136. end;
  137. panHeader.Invalidate; //para refrescar
  138. //Botones de desplazamiento horizontal
  139. if pages.Count > 1 then begin
  140. UpDown1.Enabled := true;
  141. end else begin
  142. UpDown1.Enabled := false;
  143. end;
  144. end;
  145. procedure TfraTabSessions.SetTabIndex(AValue: integer);
  146. {Define la sesión que se hará visible}
  147. var
  148. res: string;
  149. begin
  150. if AValue>pages.Count-1 then AValue := pages.Count-1;
  151. if FTabIndex = AValue then Exit;
  152. if FTabIndex<>-1 then begin //Que no sea la primera vez
  153. pages[FTabIndex].SetHide; //Oculta la sesión anterior.
  154. end;
  155. FTabIndex := AValue; //cambia valor
  156. // pages[FTabIndex].Visible := true; //Muestra la nueva sesión.
  157. PageEvent('req_activate', pages[FTabIndex], res);
  158. if OnSelectEditor<>nil then OnSelectEditor; //Dispara evento.
  159. RefreshTabs;
  160. end;
  161. //Métodos pàra el dibujo de lenguetas
  162. procedure TfraTabSessions.DibLeng(x1, x2: integer; coltex: TColor; Activo: boolean;
  163. txt: string);
  164. {Dibuja la lengueta en la posición indicada. Si "txt" es '+', se dibuja la lengueta
  165. para agregar página.}
  166. procedure GetX1X2(const xrmin: integer; y: integer; out xr1, xr2: integer);
  167. {devuelve las coordenadas x1 y x2 de la línea "y" de la lengueta}
  168. begin
  169. case y of
  170. 0: begin //Primera fila
  171. xr1 := x1+4;
  172. xr2 := xrmin -4;
  173. end;
  174. 1: begin
  175. xr1 := x1+2;
  176. xr2 := xrmin -2;
  177. end;
  178. 2: begin
  179. xr1 := x1+1;
  180. xr2 := xrmin ;
  181. end;
  182. 3: begin
  183. xr1 := x1+1;
  184. xr2 := xrmin + 1;
  185. end;
  186. else //otras filas
  187. xr1 := x1;
  188. xr2 := xrmin + (y div 2);
  189. end;
  190. end;
  191. var
  192. cv: TCanvas;
  193. y1, y2, alto, xr1, xr2, xrmin, xrmin2, i: Integer;
  194. r: TRect;
  195. colBorde: TColor;
  196. begin
  197. //Lee coordenadas horizontales
  198. alto := panHeader.Height;
  199. y1 := 0;
  200. y2 := y1 + alto;
  201. //Inicia dibujo
  202. cv := panHeader.canvas;
  203. cv.Font.Size:= FONT_TAB_SIZE;
  204. cv.Font.Bold := false;
  205. cv.Font.Color := clBlack;
  206. cv.Font.Color := coltex; //Color de texto
  207. //Fija Línea y color de fondo
  208. cv.Pen.Style := psSolid;
  209. cv.Pen.Width := 1;
  210. if Activo then cv.Pen.Color := clWhite else cv.Pen.Color := clMenu;
  211. //Dibuja fondo de lengueta. El dibujo es línea por línea
  212. xrmin := x2 - (alto div 4); //Corrige inicio, para que el punto medio de la pendiente, caiga en x2.
  213. xrmin2 := x2 + (alto div 4)+1; //Corrige inicio, para que el punto medio de la pendiente, caiga en x2.
  214. for i:=0 to alto-1 do begin
  215. GetX1X2(xrmin, i, xr1, xr2);
  216. cv.Line(xr1, i, xr2, i);
  217. end;
  218. //Dibuja borde de lengueta
  219. colBorde := clGray;
  220. cv.Pen.Color := colBorde;
  221. cv.Line(x1,y1+4,x1,y2); //lateral izquierdo
  222. cv.Line(x1+4,y1, xrmin-4, y1); //superior
  223. cv.Line(xrmin+2, y1+4, xrmin2, y2); //lateral derecho
  224. //Bordes
  225. GetX1X2(xrmin, 0, xr1, xr2);
  226. cv.Pixels[xr1,0] := colBorde;
  227. cv.Pixels[xr2,0] := colBorde;
  228. GetX1X2(xrmin, 1, xr1, xr2);
  229. cv.Pixels[xr1,1] := colBorde;
  230. cv.Pixels[xr1+1,1] := colBorde;
  231. cv.Pixels[xr2,1] := colBorde;
  232. cv.Pixels[xr2-1,1] := colBorde;
  233. GetX1X2(xrmin, 2, xr1, xr2);
  234. cv.Pixels[xr1,2] := colBorde;
  235. cv.Pixels[xr2,2] := colBorde;
  236. cv.Pixels[xr2-1,2] := colBorde;
  237. GetX1X2(xrmin, 3, xr1, xr2);
  238. cv.Pixels[xr1,3] := colBorde;
  239. cv.Pixels[xr2,3] := colBorde;
  240. if txt = '+' then begin //Lengueta para agregar
  241. //Elimina objetos y pone texto
  242. r.Top := y1;
  243. r.Bottom := y2;
  244. r.Left := x1+8; //Deja espacio para el ícono
  245. r.Right := x2-7; //Deja espacio para el botón de cierre
  246. cv.Font.Size:= 12;
  247. cv.Font.Bold := true;
  248. cv.TextRect(r, x1+13, 0, '+');
  249. end else begin //Lengueta normal
  250. //Dibuja ícono
  251. ImgCompletion.Draw(cv, x1+4, 6, 1);
  252. //Elimina objetos y pone texto
  253. r.Top := y1;
  254. r.Bottom := y2;
  255. r.Left := x1+20; //Deja espacio para el ícono
  256. r.Right := x2-7; //Deja espacio para el botón de cierre
  257. cv.TextRect(r, x1+23, 4 ,txt);
  258. end;
  259. end;
  260. procedure TfraTabSessions.Panel1MouseDown(Sender: TObject; Button: TMouseButton;
  261. Shift: TShiftState; X, Y: Integer);
  262. var
  263. x2, i: Integer;
  264. edi: TfraTabSession;
  265. res: string;
  266. begin
  267. {Se asuma que las lenguetas ya tienen su coordenada x1, actualizada, porque ya
  268. han sido dibujadas, así que no llamaremos a UpdateX1CoordTabs.}
  269. for i := 0 to pages.Count-1 do begin
  270. edi := pages[i];
  271. x2 := edi.x1 + edi.tabWidth;
  272. if (X>edi.x1) and (X<x2) then begin
  273. TabIndex := i; //Selecciona
  274. if Shift = [ssRight] then begin
  275. PopUpTabs.PopUp;
  276. end else if Shift = [ssMiddle] then begin
  277. //Cerrar el archivo
  278. ClosePage;
  279. end else if Shift = [ssLeft] then begin
  280. //Solo selección
  281. MakeActiveTabVisible;
  282. //Inicia el arrastre
  283. panHeader.BeginDrag(false, 10);
  284. tabDrag := i; //gaurda el índice del arrastrado
  285. end;
  286. exit;
  287. end;
  288. end;
  289. //Verifica si se pulsó en el botón '+'
  290. if pages.Count>0 then begin //Solo cuando se dibuja.
  291. if (X>x2) and (X<x2 + WIDTH_ADD_TAB) then begin
  292. PageEvent('req_new_page', nil, res);
  293. SetFocus;
  294. end;
  295. end;
  296. end;
  297. procedure TfraTabSessions.Panel1MouseMove(Sender: TObject; Shift: TShiftState; X,
  298. Y: Integer);
  299. begin
  300. // debugln('Move');
  301. end;
  302. procedure TfraTabSessions.Panel1DragOver(Sender, Source: TObject; X, Y: Integer;
  303. State: TDragState; var Accept: Boolean);
  304. var
  305. x2, i, x2Mid: Integer;
  306. edi: TfraTabSession;
  307. begin
  308. Accept := true;
  309. //Ve a cual lengüeta selecciona
  310. tabSelec := -1;
  311. for i := 0 to pages.Count-1 do begin
  312. edi := pages[i];
  313. x2Mid := edi.x1 + edi.tabWidth div 2;
  314. x2 := edi.x1 + edi.tabWidth;
  315. if (X>edi.x1) and (X<x2) then begin
  316. if X<x2Mid then begin
  317. //Está en la primera mitad.
  318. tabSelec := i; //Selecciona
  319. end else begin
  320. //En la mitad final, selecciona el siguiente
  321. tabSelec := i+1; //Selecciona
  322. end;
  323. end;
  324. end;
  325. //Genera marca en la lengüeta
  326. if tabSelec<>-1 then begin
  327. // debugln('leng selec: %d', [tabselec]);
  328. panHeader.Invalidate;
  329. end;
  330. end;
  331. procedure TfraTabSessions.Panel1EndDrag(Sender, Target: TObject; X, Y: Integer);
  332. {Se termina el arrastre, sea que se soltó en alguna parte, o se canceló.}
  333. begin
  334. tabSelec := -1;
  335. panHeader.Invalidate;
  336. end;
  337. procedure TfraTabSessions.Panel1DragDrop(Sender, Source: TObject; X, Y: Integer);
  338. {Se soltó la lengueta en el panel.}
  339. begin
  340. if TabIndex<0 then exit;
  341. if tabSelec<0 then exit;
  342. //Corrección
  343. if tabSelec>TabIndex then tabSelec := tabSelec-1;
  344. if tabSelec>pages.Count-1 then exit;
  345. // debugln('Panel1DragDrop: %d a %d', [TabIndex, tabSelec]);
  346. pages.Move(TabIndex, tabSelec);
  347. TabIndex := tabSelec;
  348. panHeader.Invalidate;
  349. end;
  350. procedure TfraTabSessions.Panel1MouseUp(Sender: TObject; Button: TMouseButton;
  351. Shift: TShiftState; X, Y: Integer);
  352. begin
  353. //Pasa el enfoque al editor que se ha seleccionado
  354. if TabIndex=-1 then exit;
  355. try
  356. if pages[TabIndex].Visible then begin //Si el "frame" es visible.
  357. pages[TabIndex].edPCom.SetFocus;
  358. end;
  359. except
  360. end;
  361. end;
  362. procedure TfraTabSessions.UpdateX1CoordTabs;
  363. {Actualiza la coordenada x1, de las lenguetas, considerando el valor actual de
  364. "xIniTabs". El valor x1, representa la coordenada en que se dibuajaría la lengueta.}
  365. var
  366. i, x1: integer;
  367. pag: TfraTabSession;
  368. begin
  369. {Este algoritmo debe ser igual a Panel1Paint(), para que no haya inconsistencias.}
  370. x1 := xIniTabs;
  371. for i := 0 to pages.Count-1 do begin
  372. pag := pages[i];
  373. pag.x1 := x1; //Actualiza coordenada
  374. //Calcula siguiente coordenada
  375. x1 := x1 + pag.tabWidth + SEPAR_TABS;
  376. end;
  377. end;
  378. procedure TfraTabSessions.MakeActiveTabVisible;
  379. {Configura "xIniTabs", de modo que haga visible la pestaña del editor activo.
  380. Solo trabaja sobre la prestaña o lengueta. No maneja a la ventana de la sesión.}
  381. var
  382. x1, x2: integer;
  383. begin
  384. if Count=0 then exit;
  385. UpdateX1CoordTabs;
  386. x1 := ActivePage.x1;
  387. x2 := ActivePage.x1 + ActivePage.tabWidth;
  388. if x2 > self.Width then begin
  389. //Pestaña sale de página, por la derecha
  390. xIniTabs := xIniTabs - (x2-self.Width);
  391. end else if x1 < Panel2.Width then begin
  392. //Pestaña sale de página, por la izquierda
  393. xIniTabs := xIniTabs + (Panel2.Width - x1);
  394. end else begin
  395. // debugln('Pestaña se dibuja adentro');
  396. end;
  397. end;
  398. procedure TfraTabSessions.Panel1Paint(Sender: TObject);
  399. var
  400. i, x1, xfin: Integer;
  401. cv: TCanvas;
  402. pag: TfraTabSession;
  403. begin
  404. //Actualiza coordenadas
  405. UpdateX1CoordTabs;
  406. //Dibuja las pestañas
  407. for i := 0 to pages.Count-1 do begin
  408. pag := pages[i];
  409. if i <> TabIndex then begin
  410. //Dibuja todo menos al activo, lo deja para después.
  411. DibLeng(pag.x1, pag.x1 + pag.tabWidth, clBlack, false, pag.Caption);
  412. end;
  413. end;
  414. //Dibuja lengueta "+"
  415. if pages.Count>0 then begin
  416. //Solo se dibuja si hay página seleccionada.
  417. pag := pages[pages.Count-1]; //ültima página
  418. xfin := pag.x1 + pag.tabWidth;
  419. DibLeng(xfin, xfin + WIDTH_ADD_TAB, clBlack, false, '+');
  420. end;
  421. //Dibuja al final al activo, para que aparezca encima
  422. if TabIndex<>-1 then begin
  423. pag := pages[TabIndex];
  424. DibLeng(pag.x1, pag.x1 + pag.tabWidth, clBlack, true, pag.Caption);
  425. end;
  426. //Dibuja la marca de movimiento de lengüeta
  427. if (tabSelec>=0) and (tabSelec<pages.Count) then begin
  428. pag := pages[tabSelec];
  429. x1 := pag.x1+2;
  430. cv := panHeader.canvas;
  431. cv.Pen.Width := 5;
  432. cv.Pen.Color := clGray;
  433. cv.Line(x1 ,0, x1, panHeader.Height);
  434. end else if tabSelec = pages.Count then begin
  435. //Se marca al final de la última pestaña
  436. pag := pages[pages.Count-1]; //el útlimo
  437. x1 := pag.x1 + pag.tabWidth +2;
  438. cv := panHeader.canvas;
  439. cv.Pen.Width := 5;
  440. cv.Pen.Color := clGray;
  441. cv.Line(x1 ,0, x1, panHeader.Height);
  442. end;
  443. end;
  444. procedure TfraTabSessions.UpdateTabWidth(pag: TfraTabSession);
  445. var
  446. w: Integer;
  447. begin
  448. panHeader.Canvas.Font.Size := FONT_TAB_SIZE; {Fija atrubutos de texto, para que el
  449. cálculo con "TextWidth", de ancho sea correcto}
  450. w := panHeader.Canvas.TextWidth(pag.Caption) + 30;
  451. if w < MIN_WIDTH_TAB then w := MIN_WIDTH_TAB;
  452. pag.tabWidth := w;
  453. panHeader.Invalidate; //Para refrescar el dibujo
  454. end;
  455. procedure TfraTabSessions.PageEvent(event: string; page: TObject; out
  456. res: string);
  457. {Forma corta de llamar al evento OnPageEvent. }
  458. begin
  459. if OnPageEvent=nil then begin
  460. //Fija salida por seguridad.
  461. res := '';
  462. end else begin
  463. //Llama al evento
  464. OnPageEvent(event, page, res);
  465. end;
  466. end;
  467. procedure TfraTabSessions.InitTabs;
  468. {Configura eventos para el control de las lenguetas}
  469. begin
  470. xIniTabs := panel2.Width; //Empeiza dibujando al lado de las flechas
  471. panHeader.OnMouseMove := @Panel1MouseMove;
  472. panHeader.OnMouseDown := @Panel1MouseDown;
  473. panHeader.OnMouseUp := @Panel1MouseUp;
  474. panHeader.OnDragOver := @Panel1DragOver;
  475. panHeader.OnDragDrop := @Panel1DragDrop;
  476. panHeader.OnEndDrag := @Panel1EndDrag;
  477. end;
  478. //////////////////////////////////////////////////////////////
  479. function TfraTabSessions.LastIndex: integer;
  480. {Devuelve el índice de la última pestaña.}
  481. begin
  482. Result :=pages.Count - 1;
  483. end;
  484. function TfraTabSessions.NewName(prefix, ext: string): string;
  485. {Genera un nombre de archivo, a partir de "prefix", que no se repita entre las pestañas
  486. abiertas y que no exista en disco.}
  487. var
  488. n: Integer;
  489. begin
  490. n := 0;
  491. repeat
  492. inc(n);
  493. Result := prefix + IntToStr(n) + ext;
  494. until (SearchEditorIdxByTab(Result)=-1) and (not FileExists(Result));
  495. end;
  496. function TfraTabSessions.AddPage(ext: string): TfraTabSession;
  497. {Agrega una nueva ventana de eición a la vista, y devuelve la referencia.}
  498. var
  499. page: TfraTabSession;
  500. res: string;
  501. begin
  502. //Crea página.
  503. page := TfraTabSession.Create(nil); //No se define "Owner" porque se administrará dentro de muestra propia lista.
  504. page.Parent := self.panContent;
  505. page.Align := alClient;
  506. page.Name := 'Page'+IntToStr(pages.Count); //Nombre único. Se usaría NewName(ext), pero incuye un caracter punto.
  507. page.Caption := NewName('Page', ext); //Fija nombre de la pestaña. El nombre del archivo lo decidirá el frame.
  508. UpdateTabWidth(page); //Cambia el título Hay que actualizar ancho de lengueta.
  509. pages.Add(page); //Agrega a la lista.
  510. //Evento de requerimiento de inicialización de página.
  511. PageEvent('req_init', page, res);
  512. //Activa la página. Debe hacerse después de llamar a 'req_init'.
  513. TabIndex := LastIndex; //Selecciona la última sesión agregada
  514. //Configura desplazamiento para asegurarse que la pestaña se mostrará visible.
  515. MakeActiveTabVisible;
  516. //Actualiza referencias.
  517. Result := page;
  518. end;
  519. procedure TfraTabSessions.DeleteEdit;
  520. {Elimina al editor activo.}
  521. begin
  522. if TabIndex=-1 then exit;
  523. pages.Delete(TabIndex);
  524. //Hay que actualiza TabIndex
  525. if pages.Count = 0 then begin
  526. //Era el único
  527. FTabIndex := -1;
  528. end else begin
  529. //Había al menos 2
  530. if TabIndex > pages.Count - 1 then begin
  531. //Quedó apuntando fuera
  532. FTabIndex := pages.Count - 1; //limita
  533. //No es necesario ocultar el anterior, porque se eliminó
  534. pages[FTabIndex].Visible := true; //muestra el nuevo
  535. end else begin
  536. //Queda apuntando al siguiente. No es necesario modificar.
  537. //No es necesario ocultar el anterior, porque se eliminó
  538. pages[FTabIndex].Visible := true; //muestra el nuevo
  539. end;
  540. end;
  541. MakeActiveTabVisible;
  542. if OnSelectEditor<>nil then OnSelectEditor;
  543. RefreshTabs;
  544. end;
  545. ///Manejo de pestañas
  546. function TfraTabSessions.Count: integer;
  547. begin
  548. Result := pages.Count;
  549. end;
  550. function TfraTabSessions.ActivePage: TfraTabSession;
  551. {Devuelve el editor SynEditor, activo, es decir el que se encuentra en la lengueta
  552. activa. }
  553. var
  554. i: Integer;
  555. begin
  556. if pages.Count = 0 then exit(nil);
  557. i := TabIndex;
  558. Result := pages[i]; //Solo funcionará si no se desordenan las enguetas
  559. end;
  560. function TfraTabSessions.SearchEditorIdxByTab(tabName: string): integer;
  561. var
  562. ed: TfraTabSession;
  563. i: integer;
  564. begin
  565. for i:=0 to pages.Count-1 do begin
  566. ed := pages[i];
  567. if Upcase(ed.Caption) = UpCase(tabName) then exit(i);
  568. end;
  569. exit(-1);
  570. end;
  571. procedure TfraTabSessions.SelectNextEditor;
  572. {Selecciona al siguiente editor.}
  573. begin
  574. if Count = 0 then exit;
  575. if TabIndex=-1 then exit;
  576. if TabIndex = LastIndex then TabIndex := 0 else TabIndex := TabIndex + 1;
  577. SetFocus;
  578. MakeActiveTabVisible;
  579. end;
  580. procedure TfraTabSessions.SelectPrevEditor;
  581. {Selecciona al editor anterior.}
  582. begin
  583. if Count = 0 then exit;
  584. if TabIndex=-1 then exit;
  585. if TabIndex = 0 then TabIndex := LastIndex else TabIndex := TabIndex -1;
  586. SetFocus;
  587. MakeActiveTabVisible;
  588. end;
  589. function TfraTabSessions.HasFocus: boolean;
  590. {Indica si alguno de los editores, tiene el enfoque.}
  591. var
  592. i: Integer;
  593. begin
  594. for i:=0 to pages.Count-1 do begin
  595. if pages[i].edPCom.Focused then exit(true);
  596. end;
  597. exit(false);
  598. end;
  599. procedure TfraTabSessions.SetFocus;
  600. begin
  601. // inherited SetFocus;
  602. if TabIndex = -1 then exit;
  603. if pages[TabIndex].Visible then begin //Si el "frame" es visible.
  604. pages[TabIndex].edPCom.SetFocus;
  605. end;
  606. end;
  607. function TfraTabSessions.ClosePage: boolean;
  608. {Cierra la página actual.
  609. Si se cierra la página, o no hay página actual, se devuelve TRUE.
  610. Si no se puede cerrar, devuelve FALSE}
  611. var
  612. res: string;
  613. begin
  614. if ActivePage=nil then exit(true);
  615. PageEvent('query_close', ActivePage, res);
  616. if (res='N') or (res='') then exit(false); //Cancelado. No se debe cerrar.
  617. //Hay que proceder con el cierre
  618. DeleteEdit;
  619. exit(true);
  620. end;
  621. function TfraTabSessions.CloseAll(out lstClosedFiles: string): boolean;
  622. {Cierra todas las ventanas, pidiendo confirmación. Si se cancela, devuelve FALSE.
  623. Se devuelve en "lstOpenedFiles" una lista con los archivos que estaban abiertos.}
  624. var
  625. res: string;
  626. begin
  627. lstClosedFiles := '';
  628. while pages.Count>0 do begin
  629. lstClosedFiles := lstClosedFiles + ActivePage.ePCom.FileName + LineEnding;
  630. if ActivePage = nil then exit(true);
  631. PageEvent('query_close', ActivePage, res);
  632. if (res='N') or (res='') then exit(false); //Cancelado. No se debe cerrar.
  633. DeleteEdit;
  634. end;
  635. exit(true);
  636. end;
  637. //Inicialización
  638. procedure TfraTabSessions.UpdateSynEditConfig;
  639. {Indica que se desea cambiar la configuración de todos los SynEdit abiertos.}
  640. var
  641. i: Integer;
  642. begin
  643. //Pide configuración para todos los editores abiertos
  644. for i:=0 to pages.Count-1 do begin
  645. if OnRequireSynEditConfig<>nil then begin
  646. OnRequireSynEditConfig(pages[i].edPCom);
  647. end;
  648. end;
  649. end;
  650. procedure TfraTabSessions.UpdateSynEditCompletion;
  651. var
  652. i: Integer;
  653. begin
  654. //Pide configurar completado para todos los editores abiertos
  655. for i:=0 to pages.Count-1 do begin
  656. if OnRequireSetCompletion<>nil then OnRequireSetCompletion(pages[i]);
  657. end;
  658. end;
  659. procedure TfraTabSessions.lblRecentLinkClick(Sender: TObject);
  660. var
  661. res: string;
  662. begin
  663. //MsgBox(TLabel(Sender).Caption);
  664. PageEvent('req_open_ses', Sender, res);
  665. SetFocus;
  666. end;
  667. procedure TfraTabSessions.UpdateRecents(recents: TStringList);
  668. {Actualiza la lista de la sesiones recientes que se mostrará en este frame.}
  669. var
  670. rec: String;
  671. lblRecentLink: TLabel;
  672. i: Integer;
  673. begin
  674. i := 0;
  675. for rec in recents do begin
  676. // MsgBox(rec);
  677. lblRecentLink := TLabel.Create(nil);
  678. lblRecentLink.Name := 'lbl' + IntToStr(i);
  679. lblRecentLink.Visible:= false;
  680. lblRecentLink.Caption:= rec;
  681. lblRecentLink.Parent := panContent;
  682. lblRecentLink.left:= 70;
  683. lblRecentLink.Top:= 20*i + lblRecents.Top + lblRecents.Height;
  684. lblRecentLink.Font.Size:=11;
  685. lblRecentLink.Font.Color:=clBlue;
  686. lblRecentLink.OnClick:=@lblRecentLinkClick;
  687. recentLinks.Add(lblRecentLink);
  688. inc(i);
  689. end;
  690. end;
  691. constructor TfraTabSessions.Create(AOwner: TComponent);
  692. begin
  693. inherited;
  694. pages:= TPages.Create(true);
  695. panHeader.OnPaint := @Panel1Paint;
  696. FTabIndex := -1;
  697. InitTabs;
  698. tabSelec := -1;
  699. RefreshTabs;
  700. recentLinks:= TRecentLinks.Create(true);
  701. end;
  702. destructor TfraTabSessions.Destroy;
  703. begin
  704. recentLinks.Destroy;
  705. pages.Destroy;
  706. inherited Destroy;
  707. end;
  708. //Menú
  709. procedure TfraTabSessions.mnNewTabClick(Sender: TObject);
  710. var
  711. res: string;
  712. begin
  713. PageEvent('req_new_page', nil, res);
  714. SetFocus;
  715. end;
  716. procedure TfraTabSessions.UpDown1Click(Sender: TObject; Button: TUDBtnType);
  717. begin
  718. case Button of
  719. btNext: SelectNextEditor;
  720. btPrev: SelectPrevEditor;
  721. end;
  722. end;
  723. procedure TfraTabSessions.mnCloseTabClick(Sender: TObject);
  724. begin
  725. ClosePage;
  726. SetFocus;
  727. end;
  728. procedure TfraTabSessions.mnCloseAllClick(Sender: TObject);
  729. begin
  730. while self.Count>0 do begin
  731. if not ClosePage then
  732. break; //Se canceló
  733. end;
  734. SetFocus;
  735. end;
  736. procedure TfraTabSessions.mnCloseOthersClick(Sender: TObject);
  737. var
  738. nBefore, i, nAfter: Integer;
  739. begin
  740. //Cierra anteriores
  741. nBefore := TabIndex;
  742. for i:= 1 to nBefore do begin
  743. TabIndex := 0;
  744. if not ClosePage then
  745. break; //Se canceló
  746. end;
  747. //Cierra posteriores
  748. nAfter := Count - TabIndex - 1;
  749. for i:= 1 to nAfter do begin
  750. TabIndex := Count-1;
  751. if not ClosePage then
  752. break; //Se canceló
  753. end;
  754. SetFocus;
  755. end;
  756. //Eventos del botón "Nueva Sesión".
  757. procedure TfraTabSessions.lblNewSessionMouseEnter(Sender: TObject);
  758. begin
  759. lblNewSession.Font.Bold := true;
  760. end;
  761. procedure TfraTabSessions.lblNewSessionMouseLeave(Sender: TObject);
  762. begin
  763. lblNewSession.Font.Bold := false;
  764. end;
  765. procedure TfraTabSessions.lblNewSessionMouseDown(Sender: TObject;
  766. Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
  767. var
  768. res: string;
  769. begin
  770. PageEvent('req_new_page', nil, res);
  771. SetFocus;
  772. end;
  773. //Eventos del botón "Abrir Sesión".
  774. procedure TfraTabSessions.lblOpenSessionMouseEnter(Sender: TObject);
  775. begin
  776. lblOpenSession.Font.Bold := true;
  777. end;
  778. procedure TfraTabSessions.lblOpenSessionMouseLeave(Sender: TObject);
  779. begin
  780. lblOpenSession.Font.Bold := false;
  781. end;
  782. procedure TfraTabSessions.lblOpenSessionMouseDown(Sender: TObject;
  783. Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
  784. var
  785. res: string;
  786. begin
  787. PageEvent('req_open_page', nil, res);
  788. SetFocus;
  789. end;
  790. //Eventos del botón "Recientes".
  791. procedure TfraTabSessions.lblRecentsMouseEnter(Sender: TObject);
  792. begin
  793. lblRecents.Font.Bold := true;
  794. end;
  795. procedure TfraTabSessions.lblRecentsMouseLeave(Sender: TObject);
  796. begin
  797. lblRecents.Font.Bold := false;
  798. end;
  799. procedure TfraTabSessions.lblRecentsMouseDown(Sender: TObject;
  800. Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
  801. var
  802. lbl: TLabel;
  803. begin
  804. showRecents := not showRecents;
  805. for lbl in recentLinks do lbl.Visible := showRecents;
  806. end;
  807. end.
  808. //1482