OSDN Git Service

読み込み時に行分割を行わず、レタリング時に行分割を行うようにした
[fooeditengine/FooEditEngine.git] / Core / ViewBase.cs
1 /*
2  * Copyright (C) 2013 FooProject
3  * * This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by
4  * the Free Software Foundation; either version 3 of the License, or (at your option) any later version.
5
6  * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 
7  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
8
9 You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>.
10  */
11 using System;
12 using System.Collections.Generic;
13 using System.Linq;
14 using System.Text;
15 using System.Text.RegularExpressions;
16 using System.Threading.Tasks;
17
18 namespace FooEditEngine
19 {
20     /// <summary>
21     /// LineBreakMethod列挙体
22     /// </summary>
23     public enum LineBreakMethod
24     {
25         /// <summary>
26         /// 折り返さない
27         /// </summary>
28         None = 0,
29         /// <summary>
30         /// 右端で折り返す
31         /// </summary>
32         PageBound = 1,
33         /// <summary>
34         /// 文字数で折り返す
35         /// </summary>
36         CharUnit = 2
37     }
38
39     /// <summary>
40     /// 余白を表す
41     /// </summary>
42     public struct Padding
43     {
44         /// <summary>
45         /// 左余白
46         /// </summary>
47         public int Left;
48         /// <summary>
49         /// 上余白
50         /// </summary>
51         public int Top;
52         /// <summary>
53         /// 右余白
54         /// </summary>
55         public int Right;
56         /// <summary>
57         /// 下余白
58         /// </summary>
59         public int Bottom;
60         /// <summary>
61         /// コンストラクター
62         /// </summary>
63         /// <param name="left">左</param>
64         /// <param name="top">上</param>
65         /// <param name="right">右</param>
66         /// <param name="bottom">下</param>
67         public Padding(int left, int top, int right, int bottom)
68         {
69             this.Left = left;
70             this.Top = top;
71             this.Right = right;
72             this.Bottom = bottom;
73         }
74     }
75
76     abstract class ViewBase : IDisposable
77     {
78         const int SpiltCharCount = 1024;
79
80         Document _Document;
81         protected Rectangle _Rect;
82         protected double _LongestWidth,_LongestHeight;
83         Padding _Padding;
84
85         public ViewBase(Document doc, ITextRender r,Padding padding)
86         {
87             this._Padding = padding;
88             this.Document = doc;
89             this._LayoutLines.SpilitString = new SpilitStringEventHandler(LayoutLines_SpilitStringByChar);
90             this.render = r;
91             this.render.ChangedRenderResource += new ChangedRenderResourceEventHandler(render_ChangedRenderResource);
92             this.render.ChangedRightToLeft += render_ChangedRightToLeft;
93             this.SrcChanged += new EventHandler((s, e) => { });
94             this.PageBoundChanged += new EventHandler((s, e) => { });
95         }
96
97         public Document Document
98         {
99             get
100             {
101                 return this._Document;
102             }
103             set
104             {
105                 if(this._Document != null)
106                 {
107                     this._Document.Update -= new DocumentUpdateEventHandler(doc_Update);
108                     this._Document.LineBreakChanged -= Document_LineBreakChanged;
109                     this._Document.StatusUpdate -= Document_StatusUpdate;
110                     this._Document.PerformLayouted -= _Document_PerformLayouted;
111                 }
112
113                 this._Document = value;
114
115                 this._Document.Update += new DocumentUpdateEventHandler(doc_Update);
116                 this._Document.LineBreakChanged += Document_LineBreakChanged;
117                 this._Document.StatusUpdate += Document_StatusUpdate;
118                 this._Document.PerformLayouted += _Document_PerformLayouted;
119
120                 this.Document_LineBreakChanged(this, null);
121
122                 this.Document_StatusUpdate(this, null);
123             }
124         }
125
126         private void _Document_PerformLayouted(object sender, EventArgs e)
127         {
128             CalculateWhloeViewPort();
129             CalculateLineCountOnScreen();
130             if(this.PerformLayouted != null)
131                 this.PerformLayouted(this, e);
132         }
133
134         private void Document_StatusUpdate(object sender, EventArgs e)
135         {
136             if (this.render == null)
137                 return;
138             if (this.render.TabWidthChar != this.Document.TabStops)
139                 this.render.TabWidthChar = this.Document.TabStops;
140             if (this.render.RightToLeft != this.Document.RightToLeft)
141                 this.render.RightToLeft = this.Document.RightToLeft;
142             if (this.render.ShowFullSpace != this.Document.ShowFullSpace)
143                 this.render.ShowFullSpace = this.Document.ShowFullSpace;
144             if (this.render.ShowHalfSpace != this.Document.ShowHalfSpace)
145                 this.render.ShowHalfSpace = this.Document.ShowHalfSpace;
146             if (this.render.ShowTab != this.Document.ShowTab)
147                 this.render.ShowTab = this.Document.ShowTab;
148             if (this.render.ShowLineBreak != this.Document.ShowLineBreak)
149                 this.render.ShowLineBreak = this.Document.ShowLineBreak;
150             CalculateWhloeViewPort();
151             CalculateClipRect();
152             CalculateLineCountOnScreen();
153             this._LayoutLines.ClearLayoutCache();
154         }
155
156         private void Document_LineBreakChanged(object sender, EventArgs e)
157         {
158             if (this.Document.LineBreak == LineBreakMethod.PageBound)
159                 this._LayoutLines.WrapWidth = this.render.TextArea.Width - LineBreakingMarginWidth;  //余白を残さないと欠ける
160             else if (this.Document.LineBreak == LineBreakMethod.CharUnit)
161                 this._LayoutLines.WrapWidth = this.render.emSize.Width * this.Document.LineBreakCharCount;
162             else
163                 this._LayoutLines.WrapWidth = LineToIndexTable.NONE_BREAK_LINE;
164
165             this._LayoutLines.ClearLayoutCache();
166         }
167
168         protected LineToIndexTable _LayoutLines
169         {
170             get
171             {
172                 return this.Document.LayoutLines;
173             }
174         }
175
176         public event EventHandler SrcChanged;
177
178         [Obsolete]
179         public event EventHandler PerformLayouted;
180
181         public event EventHandler PageBoundChanged;
182
183         /// <summary>
184         /// テキストレンダラ―
185         /// </summary>
186         public ITextRender render
187         {
188             get;
189             set;
190         }
191
192         /// <summary>
193         /// 一ページの高さに収まる行数を返す
194         /// </summary>
195         public int LineCountOnScreen
196         {
197             get;
198             protected set;
199         }
200         
201         /// <summary>
202         /// 折り返し時の右マージン
203         /// </summary>
204         public double LineBreakingMarginWidth
205         {
206             get;
207             protected set;
208         }
209
210         /// <summary>
211         /// 保持しているレイアウト行
212         /// </summary>
213         public LineToIndexTable LayoutLines
214         {
215             get { return this._LayoutLines; }
216         }
217
218         /// <summary>
219         /// 最も長い行の幅
220         /// </summary>
221         public double LongestWidth
222         {
223             get { return this._LongestWidth; }
224         }
225
226         public double LongestHeight
227         {
228             get { return this._LongestHeight; }
229         }
230
231         public double LineNumberMargin
232         {
233             get
234             {
235                 return this.render.emSize.Width;
236             }
237         }
238
239         /// <summary>
240         /// シンタックスハイライター
241         /// </summary>
242         /// <remarks>差し替えた場合、再構築する必要があります</remarks>
243         public IHilighter Hilighter
244         {
245             get { return this._LayoutLines.Hilighter; }
246             set { this._LayoutLines.Hilighter = value; this._LayoutLines.ClearLayoutCache(); }
247         }
248
249         /// <summary>
250         /// すべてのレイアウト行を破棄し、再度レイアウトをやり直す
251         /// </summary>
252         [Obsolete]
253         public virtual void PerfomLayouts()
254         {
255             //互換性を保つために残しておく
256             this.Document.PerformLayout();
257         }
258
259         /// <summary>
260         /// 余白を表す
261         /// </summary>
262         public Padding Padding
263         {
264             get {
265                 return this._Padding;
266             }
267             set {
268                 this._Padding = value;
269                 CalculateClipRect();
270                 CalculateLineCountOnScreen();
271                 if (this.Document.RightToLeft)
272                     this._LayoutLines.ClearLayoutCache();
273                 this.PageBoundChanged(this, null);
274             }
275         }
276
277         /// <summary>
278         /// ページ全体を表す領域
279         /// </summary>
280         public Rectangle PageBound
281         {
282             get { return this._Rect; }
283             set
284             {
285                 if (value.Width < 0 || value.Height < 0)
286                     throw new ArgumentOutOfRangeException("");
287                 this._Rect = value;
288                 CalculateClipRect();
289                 CalculateLineCountOnScreen();
290                 if (this.Document.RightToLeft)
291                     this._LayoutLines.ClearLayoutCache();
292                 this.PageBoundChanged(this, null);
293             }
294         }
295
296         /// <summary>
297         /// Draw()の対象となる領域の左上を表す
298         /// </summary>
299         public SrcPoint Src
300         {
301             get { return this.Document.Src; }
302             set { this.Document.Src = value; }
303         }
304
305         public virtual void Draw(Rectangle updateRect, bool force = false)
306         {
307             return;
308         }
309
310         /// <summary>
311         /// スクロールを試行する
312         /// </summary>
313         /// <param name="x">X座標</param>
314         /// <param name="row">行</param>
315         /// <param name="rel_y">各行の左上を0とするY座標</param>
316         /// <returns>成功すれば偽、そうでなければ真</returns>
317         public virtual bool TryScroll(double x, int row,double rel_y = 0)
318         {
319             if (row < 0)
320                 return true;
321             if (row > this.LayoutLines.Count - 1)
322                 return true;
323             double abs_y = 0;
324             for (int i = 0; i < row; i++)
325                 abs_y += this.LayoutLines.GetLayout(i).Height;
326             this.Document.Src = new SrcPoint(x, row, rel_y);
327             this.SrcChanged(this,null);
328             return false;
329         }
330
331         /// <summary>
332         /// スクロールを試行する
333         /// </summary>
334         /// <param name="offset_x">X方向の移動量</param>
335         /// <param name="offset_y">Y方向の移動量</param>
336         /// <returns>成功すれば偽、そうでなければ真</returns>
337         public virtual bool TryScroll(double offset_x,double offset_y)
338         {
339             double x = this.Document.Src.X - offset_x;
340             if (x < 0)
341                 return true;
342             var t = GetNearstRowAndOffsetY(this.Document.Src.Row, -this.Document.Src.OffsetY + offset_y);
343             if (t == null)
344                 return true;
345             this.Document.Src = new SrcPoint(x, t.Item1, -t.Item2);
346             return false;
347         }
348
349         /// <summary>
350         /// srcRowを起点としてrect_heightが収まる行とオフセットYを求めます
351         /// </summary>
352         /// <param name="srcRow">起点となる行</param>
353         /// <param name="rect_hight">Y方向のバウンディングボックス</param>
354         /// <returns>失敗した場合、NULL。成功した場合、行とオフセットY</returns>
355         public Tuple<int,double> GetNearstRowAndOffsetY(int srcRow, double rect_hight)
356         {
357             if (rect_hight > 0)
358             {
359                 for (int i = srcRow; i < this.Document.LayoutLines.Count; i++)
360                 {
361                     ITextLayout layout = this.Document.LayoutLines.GetLayout(i);
362
363                     int lineHeadIndex = this.LayoutLines.GetIndexFromLineNumber(i);
364                     int lineLength = this.LayoutLines.GetLengthFromLineNumber(i);
365                     double layoutHeight = layout.Height;
366
367                     if (this.LayoutLines.FoldingCollection.IsHidden(lineHeadIndex))
368                         continue;
369
370                     if (rect_hight == 0)
371                         return new Tuple<int, double>(i, 0);
372
373                     if (rect_hight - layoutHeight < 0)
374                         return new Tuple<int, double>(i, rect_hight);
375
376                     rect_hight -= layoutHeight;
377                 }
378             }
379             else if(rect_hight < 0)
380             {
381                 for (int i = srcRow - 1; i >= 0; i--)
382                 {
383                     ITextLayout layout = this.Document.LayoutLines.GetLayout(i);
384
385                     int lineHeadIndex = this.LayoutLines.GetIndexFromLineNumber(i);
386                     int lineLength = this.LayoutLines.GetLengthFromLineNumber(i);
387                     double layoutHeight = layout.Height;
388
389                     if (this.LayoutLines.FoldingCollection.IsHidden(lineHeadIndex))
390                         continue;
391
392                     if(rect_hight == 0)
393                         return new Tuple<int, double>(i, 0);
394
395                     if (rect_hight + layoutHeight >= 0)
396                         return new Tuple<int, double>(i, layoutHeight + rect_hight);
397
398                     rect_hight += layoutHeight;
399                 }
400                 return new Tuple<int, double>(0, 0);
401             }
402             return null;
403         }
404
405         public void Dispose()
406         {
407             this.Dispose(true);
408             GC.SuppressFinalize(this);
409         }
410
411         public virtual void CalculateLineCountOnScreen()
412         {
413         }
414
415         public virtual void CalculateWhloeViewPort()
416         {
417         }
418
419         protected virtual void Dispose(bool disposing)
420         {
421             if (disposing)
422             {
423                 this._Document.Update -= new DocumentUpdateEventHandler(this.doc_Update);    //これをしないと複数のビューを作成した時に妙なエラーが発生する
424                 this._Document.LineBreakChanged -= Document_LineBreakChanged;
425                 this._Document.StatusUpdate -= Document_StatusUpdate;
426                 this._Document.PerformLayouted -= _Document_PerformLayouted;
427             }
428             this._LayoutLines.Clear();
429         }
430
431         protected virtual void CalculateClipRect()
432         {
433         }
434
435
436         protected virtual void OnSrcChanged(EventArgs e)
437         {
438             EventHandler handler = this.SrcChanged;
439             if (handler != null)
440                 this.SrcChanged(this, e);
441         }
442
443         protected virtual void OnPerformLayoutedChanged(EventArgs e)
444         {
445             EventHandler handler = this.PerformLayouted;
446             if (handler != null)
447                 this.PerformLayouted(this, e);
448         }
449
450         protected virtual void OnPageBoundChanged(EventArgs e)
451         {
452             EventHandler handler = this.PageBoundChanged;
453             if (handler != null)
454                 this.PageBoundChanged(this, e);
455         }
456
457         void render_ChangedRightToLeft(object sender, EventArgs e)
458         {
459             this.Document.Src = new SrcPoint(0, this.Document.Src.Row, this.Src.OffsetY);
460         }
461
462         void render_ChangedRenderResource(object sender, ChangedRenderRsourceEventArgs e)
463         {
464             this._LayoutLines.ClearLayoutCache();
465             if (e.type == ResourceType.Font)
466             {
467                 this.CalculateClipRect();
468                 this.CalculateLineCountOnScreen();
469                 this.CalculateWhloeViewPort();
470             }
471         }
472
473         void doc_Update(object sender, DocumentUpdateEventArgs e)
474         {
475             switch (e.type)
476             {
477                 case UpdateType.Clear:
478                     this._LongestWidth = 0;
479                     break;
480             }
481         }
482
483         IList<LineToIndexTableData> LayoutLines_SpilitStringByChar(object sender, SpilitStringEventArgs e)
484         {
485             return this.Document.CreateLineList(e.index, e.length, Document.MaximumLineLength);
486         }
487     }
488 }