OSDN Git Service

LineToIndexを最適化した
[fooeditengine/FooEditEngine.git] / Common / ViewBase.cs
1 /*\r
2  * Copyright (C) 2013 FooProject\r
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\r
4  * the Free Software Foundation; either version 3 of the License, or (at your option) any later version.\r
5 \r
6  * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of \r
7  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.\r
8 \r
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/>.\r
10  */\r
11 using System;\r
12 using System.Collections.Generic;\r
13 using System.Linq;\r
14 using System.Text;\r
15 using System.Threading.Tasks;\r
16 \r
17 namespace FooEditEngine\r
18 {\r
19     /// <summary>\r
20     /// LineBreakMethod列挙体\r
21     /// </summary>\r
22     public enum LineBreakMethod\r
23     {\r
24         /// <summary>\r
25         /// 折り返さない\r
26         /// </summary>\r
27         None = 0,\r
28         /// <summary>\r
29         /// 右端で折り返す\r
30         /// </summary>\r
31         PageBound = 1,\r
32         /// <summary>\r
33         /// 文字数で折り返す\r
34         /// </summary>\r
35         CharUnit = 2\r
36     }\r
37 \r
38     abstract class ViewBase : IDisposable\r
39     {\r
40         const int SpiltCharCount = 1024;\r
41 \r
42         protected Document Document;\r
43         protected LineToIndexTable _LayoutLines;\r
44         protected Point2 _Src = new Point2();\r
45         protected Rectangle _Rect;\r
46         protected double _LongestWidth;\r
47         bool _DrawLineNumber;\r
48         LineBreakMethod _LineBreak;\r
49         int _LineBreakCharCount = 80;\r
50 \r
51         public ViewBase(Document doc, ITextRender r)\r
52         {\r
53             this.Document = doc;\r
54             this.Document.UpdateCalledAlways += new DocumentUpdateEventHandler(doc_Update);\r
55             this._LayoutLines = new LineToIndexTable(this.Document, r);\r
56             this._LayoutLines.SpilitString = new SpilitStringEventHandler(LayoutLines_SpilitStringByChar);\r
57             this.render = r;\r
58             this.render.ChangedRenderResource += new ChangedRenderResourceEventHandler(render_ChangedRenderResource);\r
59             this.render.ChangedRightToLeft += render_ChangedRightToLeft;\r
60             this.SrcChanged += new EventHandler((s, e) => { });\r
61             this.PerformLayouted += new EventHandler((s, e) => { });\r
62             this.PageBoundChanged += new EventHandler((s, e) => { });\r
63         }\r
64 \r
65         public event EventHandler SrcChanged;\r
66 \r
67         public event EventHandler PerformLayouted;\r
68 \r
69         public event EventHandler PageBoundChanged;\r
70 \r
71         /// <summary>\r
72         /// URLをハイパーリンクとして表示するなら真。そうでないなら偽\r
73         /// </summary>\r
74         public bool UrlMark\r
75         {\r
76             get { return this.LayoutLines.UrlMark; }\r
77             set { this.LayoutLines.UrlMark = value; }\r
78         }\r
79 \r
80         /// <summary>\r
81         /// テキストレンダラ―\r
82         /// </summary>\r
83         public ITextRender render\r
84         {\r
85             get;\r
86             set;\r
87         }\r
88 \r
89         /// <summary>\r
90         /// 一ページの高さに収まる行数を返す\r
91         /// </summary>\r
92         public int LineCountOnScreen\r
93         {\r
94             get;\r
95             protected set;\r
96         }\r
97 \r
98         /// <summary>\r
99         /// 折り返し時の右マージン\r
100         /// </summary>\r
101         public double LineBreakingMarginWidth\r
102         {\r
103             get;\r
104             protected set;\r
105         }\r
106 \r
107         /// <summary>\r
108         /// 保持しているレイアウト行\r
109         /// </summary>\r
110         public LineToIndexTable LayoutLines\r
111         {\r
112             get { return this._LayoutLines; }\r
113         }\r
114 \r
115         /// <summary>\r
116         /// 最も長い行の幅\r
117         /// </summary>\r
118         public double LongestWidth\r
119         {\r
120             get { return this._LongestWidth; }\r
121         }\r
122 \r
123         /// <summary>\r
124         /// 桁折り処理の方法を指定する\r
125         /// </summary>\r
126         /// <remarks>\r
127         /// 変更した場合、呼び出し側で再描写とレイアウトの再構築を行う必要があります\r
128         /// </remarks>\r
129         public LineBreakMethod LineBreak\r
130         {\r
131             get\r
132             {\r
133                 return this._LineBreak;\r
134             }\r
135             set\r
136             {\r
137                 this._LineBreak = value;\r
138                 if (value != LineBreakMethod.None)\r
139                     this._LayoutLines.SpilitString = new SpilitStringEventHandler(LayoutLines_SpilitStringByPixelbase);\r
140                 else\r
141                     this._LayoutLines.SpilitString = new SpilitStringEventHandler(LayoutLines_SpilitStringByChar);\r
142             }\r
143         }\r
144 \r
145         /// <summary>\r
146         /// 折り返し行う文字数。実際に折り返しが行われる幅はem単位×この値となります\r
147         /// </summary>\r
148         public int LineBreakCharCount\r
149         {\r
150             get\r
151             {\r
152                 return this._LineBreakCharCount;\r
153             }\r
154             set\r
155             {\r
156                 this._LineBreakCharCount = value;\r
157             }\r
158         }\r
159 \r
160         /// <summary>\r
161         /// シンタックスハイライター\r
162         /// </summary>\r
163         public IHilighter Hilighter\r
164         {\r
165             get { return this._LayoutLines.Hilighter; }\r
166             set { this._LayoutLines.Hilighter = value; }\r
167         }\r
168 \r
169         /// <summary>\r
170         /// タブの幅\r
171         /// </summary>\r
172         /// <remarks>変更した場合、呼び出し側で再描写する必要があります</remarks>\r
173         public int TabStops\r
174         {\r
175             get { return this.render.TabWidthChar; }\r
176             set { this.render.TabWidthChar = value; }\r
177         }\r
178 \r
179         /// <summary>\r
180         /// すべてのレイアウト行を破棄し、再度レイアウトをやり直す\r
181         /// </summary>\r
182         public virtual void PerfomLayouts()\r
183         {\r
184             this.doc_Update(this.Document, new DocumentUpdateEventArgs(UpdateType.Clear, -1, -1, -1));\r
185             this.doc_Update(this.Document, new DocumentUpdateEventArgs(UpdateType.Replace, 0, 0, this.Document.Length));\r
186             CalculateLineCountOnScreen();\r
187             this.PerformLayouted(this, null);\r
188         }\r
189 \r
190         /// <summary>\r
191         /// ページ全体を表す領域\r
192         /// </summary>\r
193         public Rectangle PageBound\r
194         {\r
195             get { return this._Rect; }\r
196             set\r
197             {\r
198                 if (value.Width < 0 || value.Height < 0)\r
199                     throw new ArgumentOutOfRangeException("");\r
200                 this._Rect = value;\r
201                 CalculateClipRect();\r
202                 CalculateLineCountOnScreen();\r
203                 if (this.render.RightToLeft)\r
204                     this._LayoutLines.ClearLayoutCache();\r
205                 this.PageBoundChanged(this, null);\r
206             }\r
207         }\r
208 \r
209         /// <summary>\r
210         /// Draw()の対象となる領域の左上を表す\r
211         /// </summary>\r
212         public Point2 Src\r
213         {\r
214             get { return this._Src; }\r
215             set { this._Src = value; }\r
216         }\r
217 \r
218         /// <summary>\r
219         /// 行番号を表示するかどうか\r
220         /// </summary>\r
221         public bool DrawLineNumber\r
222         {\r
223             get { return this._DrawLineNumber; }\r
224             set\r
225             {\r
226                 this._DrawLineNumber = value;\r
227                 this._LayoutLines.ClearLayoutCache();\r
228                 CalculateClipRect();\r
229             }\r
230         }\r
231 \r
232         public virtual void Draw(Rectangle updateRect)\r
233         {\r
234         }\r
235 \r
236         public virtual bool TryScroll(double x, int row)\r
237         {\r
238             if (row < 0)\r
239                 return true;\r
240             if (row > this.LayoutLines.Count - 1)\r
241                 return true;\r
242             this._Src.X = x;\r
243             this._Src.Row = row;\r
244             CalculateLineCountOnScreen();\r
245             this.SrcChanged(this,null);\r
246             return false;\r
247         }\r
248 \r
249         public void Dispose()\r
250         {\r
251             this.Dispose(true);\r
252             GC.SuppressFinalize(this);\r
253         }\r
254 \r
255         public virtual void CalculateLineCountOnScreen()\r
256         {\r
257         }\r
258 \r
259         protected virtual void Dispose(bool disposing)\r
260         {\r
261             if (disposing)\r
262             {\r
263                 this.Document.UpdateCalledAlways -= new DocumentUpdateEventHandler(this.doc_Update);    //これをしないと複数のビューを作成した時に妙なエラーが発生する\r
264             }\r
265             this._LayoutLines.Clear();\r
266         }\r
267 \r
268         protected virtual void CalculateClipRect()\r
269         {\r
270         }\r
271 \r
272 \r
273         protected virtual void OnSrcChanged(EventArgs e)\r
274         {\r
275             EventHandler handler = this.SrcChanged;\r
276             if (handler != null)\r
277                 this.SrcChanged(this, e);\r
278         }\r
279 \r
280         protected virtual void OnPerformLayoutedChanged(EventArgs e)\r
281         {\r
282             EventHandler handler = this.PerformLayouted;\r
283             if (handler != null)\r
284                 this.PerformLayouted(this, e);\r
285         }\r
286 \r
287         protected virtual void OnPageBoundChanged(EventArgs e)\r
288         {\r
289             EventHandler handler = this.PageBoundChanged;\r
290             if (handler != null)\r
291                 this.PageBoundChanged(this, e);\r
292         }\r
293 \r
294         void render_ChangedRightToLeft(object sender, EventArgs e)\r
295         {\r
296             this._Src.X = 0;\r
297             this._LayoutLines.ClearLayoutCache();\r
298             this.CalculateClipRect();\r
299         }\r
300 \r
301         void render_ChangedRenderResource(object sender, ChangedRenderRsourceEventArgs e)\r
302         {\r
303             this._LayoutLines.ClearLayoutCache();\r
304             if (e.type == ResourceType.Font)\r
305             {\r
306                 this.CalculateClipRect();\r
307                 this.CalculateLineCountOnScreen();\r
308             }\r
309         }\r
310 \r
311         void doc_Update(object sender, DocumentUpdateEventArgs e)\r
312         {\r
313             switch (e.type)\r
314             {\r
315                 case UpdateType.Replace:\r
316                     this._LayoutLines.UpdateAsReplace(e.startIndex, e.removeLength, e.insertLength);\r
317                     break;\r
318                 case UpdateType.Clear:\r
319                     this._LayoutLines.Clear();\r
320                     this._LongestWidth = 0;\r
321                     break;\r
322             }\r
323         }\r
324 \r
325         IList<LineToIndexTableData> LayoutLines_SpilitStringByPixelbase(object sender, SpilitStringEventArgs e)\r
326         {\r
327             double WrapWidth;\r
328             if (_LineBreak == LineBreakMethod.PageBound)\r
329                 WrapWidth = this.render.TextArea.Width - LineBreakingMarginWidth;  //余白を残さないと欠ける\r
330             else\r
331                 WrapWidth = this.render.emSize.Width * this._LineBreakCharCount;\r
332 \r
333             if (WrapWidth < 0 && this._LineBreak != LineBreakMethod.None)\r
334                 throw new InvalidOperationException();\r
335 \r
336             int startIndex = e.index;\r
337             int endIndex = e.index + e.length - 1;\r
338 \r
339             LineToIndexTable layoutLineCollection = (LineToIndexTable)sender;\r
340 \r
341             return this.render.BreakLine(e.buffer,layoutLineCollection, startIndex, endIndex, WrapWidth);\r
342         }\r
343 \r
344         IList<LineToIndexTableData> LayoutLines_SpilitStringByChar(object sender, SpilitStringEventArgs e)\r
345         {\r
346             LineToIndexTable layoutLineCollection = (LineToIndexTable)sender;\r
347             int startIndex = e.index;\r
348             int endIndex = e.index + e.length - 1;\r
349             List<LineToIndexTableData> output = new List<LineToIndexTableData>();\r
350 \r
351             foreach (Tuple<int, int> range in this.Document.ForEachLines(startIndex, endIndex, 1000))\r
352             {\r
353                 int lineHeadIndex = range.Item1;\r
354                 int lineLength = range.Item2;\r
355                 char c = this.Document[lineHeadIndex + lineLength - 1];\r
356                 bool hasNewLine = c == Document.NewLine;\r
357                 output.Add(layoutLineCollection.CreateLineToIndexTableData(lineHeadIndex, lineLength, hasNewLine, null));\r
358             }\r
359 \r
360             if (output.Count > 0)\r
361                 output.Last().LineEnd = true;\r
362 \r
363             return output;\r
364         }\r
365     }\r
366 }\r