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;
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.PerformLayouted += new EventHandler((s, e) => { });
95             this.PageBoundChanged += new EventHandler((s, e) => { });
96         }
97
98         public Document Document
99         {
100             get
101             {
102                 return this._Document;
103             }
104             set
105             {
106                 if(this._Document != null)
107                 {
108                     this._Document.UpdateCalledAlways -= new DocumentUpdateEventHandler(doc_Update);
109                     this._Document.LineBreakChanged -= Document_LineBreakChanged;
110                     this._Document.StatusUpdate -= Document_StatusUpdate;
111                 }
112
113                 this._Document = value;
114
115                 this._Document.UpdateCalledAlways += new DocumentUpdateEventHandler(doc_Update);
116                 this._Document.LineBreakChanged += Document_LineBreakChanged;
117                 this._Document.StatusUpdate += Document_StatusUpdate;
118
119                 this.Document_StatusUpdate(this, null);
120             }
121         }
122
123         private void Document_StatusUpdate(object sender, EventArgs e)
124         {
125             if (this.render == null)
126                 return;
127             if (this.render.TabWidthChar != this.Document.TabStops)
128                 this.render.TabWidthChar = this.Document.TabStops;
129             if (this.render.RightToLeft != this.Document.RightToLeft)
130                 this.render.RightToLeft = this.Document.RightToLeft;
131             if (this.render.ShowFullSpace != this.Document.ShowFullSpace)
132                 this.render.ShowFullSpace = this.Document.ShowFullSpace;
133             if (this.render.ShowHalfSpace != this.Document.ShowHalfSpace)
134                 this.render.ShowHalfSpace = this.Document.ShowHalfSpace;
135             if (this.render.ShowTab != this.Document.ShowTab)
136                 this.render.ShowTab = this.Document.ShowTab;
137             if (this.render.ShowLineBreak != this.Document.ShowLineBreak)
138                 this.render.ShowLineBreak = this.Document.ShowLineBreak;
139             CalculateClipRect();
140             CalculateLineCountOnScreen();
141             this._LayoutLines.ClearLayoutCache();
142         }
143
144         private void Document_LineBreakChanged(object sender, EventArgs e)
145         {
146             if (this.Document.LineBreak != LineBreakMethod.None)
147                 this._LayoutLines.SpilitString = new SpilitStringEventHandler(LayoutLines_SpilitStringByPixelbase);
148             else
149                 this._LayoutLines.SpilitString = new SpilitStringEventHandler(LayoutLines_SpilitStringByChar);
150         }
151
152         protected LineToIndexTable _LayoutLines
153         {
154             get
155             {
156                 return this.Document.LayoutLines;
157             }
158         }
159
160         public event EventHandler SrcChanged;
161
162         public event EventHandler PerformLayouted;
163
164         public event EventHandler PageBoundChanged;
165
166         /// <summary>
167         /// テキストレンダラ―
168         /// </summary>
169         public ITextRender render
170         {
171             get;
172             set;
173         }
174
175         /// <summary>
176         /// 一ページの高さに収まる行数を返す
177         /// </summary>
178         public int LineCountOnScreen
179         {
180             get;
181             protected set;
182         }
183
184         /// <summary>
185         /// 折り返し時の右マージン
186         /// </summary>
187         public double LineBreakingMarginWidth
188         {
189             get;
190             protected set;
191         }
192
193         /// <summary>
194         /// 保持しているレイアウト行
195         /// </summary>
196         public LineToIndexTable LayoutLines
197         {
198             get { return this._LayoutLines; }
199         }
200
201         /// <summary>
202         /// 最も長い行の幅
203         /// </summary>
204         public double LongestWidth
205         {
206             get { return this._LongestWidth; }
207         }
208
209         public double LineNumberMargin
210         {
211             get
212             {
213                 return this.render.emSize.Width;
214             }
215         }
216
217         /// <summary>
218         /// シンタックスハイライター
219         /// </summary>
220         /// <remarks>差し替えた場合、再構築する必要があります</remarks>
221         public IHilighter Hilighter
222         {
223             get { return this._LayoutLines.Hilighter; }
224             set { this._LayoutLines.Hilighter = value; this._LayoutLines.ClearLayoutCache(); }
225         }
226
227         /// <summary>
228         /// すべてのレイアウト行を破棄し、再度レイアウトをやり直す
229         /// </summary>
230         public virtual void PerfomLayouts()
231         {
232             this.Document.FireUpdate(new DocumentUpdateEventArgs(UpdateType.Clear, -1, -1, -1));
233             this.Document.FireUpdate(new DocumentUpdateEventArgs(UpdateType.Replace, 0, 0, this.Document.Length));
234             CalculateLineCountOnScreen();
235             this.PerformLayouted(this, null);
236         }
237
238         /// <summary>
239         /// 余白を表す
240         /// </summary>
241         public Padding Padding
242         {
243             get {
244                 return this._Padding;
245             }
246             set {
247                 this._Padding = value;
248                 CalculateClipRect();
249                 CalculateLineCountOnScreen();
250                 if (this.Document.RightToLeft)
251                     this._LayoutLines.ClearLayoutCache();
252                 this.PageBoundChanged(this, null);
253             }
254         }
255
256         /// <summary>
257         /// ページ全体を表す領域
258         /// </summary>
259         public Rectangle PageBound
260         {
261             get { return this._Rect; }
262             set
263             {
264                 if (value.Width < 0 || value.Height < 0)
265                     throw new ArgumentOutOfRangeException("");
266                 this._Rect = value;
267                 CalculateClipRect();
268                 CalculateLineCountOnScreen();
269                 if (this.Document.RightToLeft)
270                     this._LayoutLines.ClearLayoutCache();
271                 this.PageBoundChanged(this, null);
272             }
273         }
274
275         /// <summary>
276         /// Draw()の対象となる領域の左上を表す
277         /// </summary>
278         public SrcPoint Src
279         {
280             get { return this.Document.Src; }
281             set { this.Document.Src = value; }
282         }
283
284         public virtual void Draw(Rectangle updateRect, bool force = false)
285         {
286             return;
287         }
288
289         public virtual bool TryScroll(double x, int row)
290         {
291             if (row < 0)
292                 return true;
293             if (row > this.LayoutLines.Count - 1)
294                 return true;
295             this.Document.Src = new SrcPoint(x, row, row * this.render.emSize.Height);
296             this.SrcChanged(this,null);
297             return false;
298         }
299
300         Point total = new Point();
301         double noti;
302         public virtual bool TryRelativeScroll(double delta_x, double delta_y, bool logical_scroll = true)
303         {
304             total.X += delta_x;
305             total.Y += delta_y;
306             noti += Math.Abs(delta_y);  //負の値が飛んでくる可能性がある
307
308             if (noti < this.render.emSize.Height && logical_scroll)
309                 return true;
310
311             noti = 0;
312
313             double totalHeight = this.LayoutLines.Count * this.render.emSize.Height;
314             if (total.Y > totalHeight)
315                 total.Y = totalHeight - this.render.TextArea.Height;
316             if (total.Y < 0)
317                 total.Y = 0;
318             if (total.X < 0)
319                 total.X = 0;
320
321             int row;
322             if (total.Y > 0)
323                 row = (int)(total.Y / this.render.emSize.Height + 1.0); //レタリング時にオフセットするために切り上げる
324             else
325                 row = 0;    //0の時だけは切り上げると困るので何もしない
326             if (row < 0)
327                 return true;
328             if (row > this.LayoutLines.Count - 1)
329                 return true;
330             this.Document.Src = new SrcPoint(total.X, row, total.Y);
331             this.SrcChanged(this, null);
332
333             return false;
334         }
335
336         public void Dispose()
337         {
338             this.Dispose(true);
339             GC.SuppressFinalize(this);
340         }
341
342         public virtual void CalculateLineCountOnScreen()
343         {
344         }
345
346         protected virtual void Dispose(bool disposing)
347         {
348             if (disposing)
349             {
350                 this.Document.UpdateCalledAlways -= new DocumentUpdateEventHandler(this.doc_Update);    //これをしないと複数のビューを作成した時に妙なエラーが発生する
351             }
352             this._LayoutLines.Clear();
353         }
354
355         protected virtual void CalculateClipRect()
356         {
357         }
358
359
360         protected virtual void OnSrcChanged(EventArgs e)
361         {
362             EventHandler handler = this.SrcChanged;
363             if (handler != null)
364                 this.SrcChanged(this, e);
365         }
366
367         protected virtual void OnPerformLayoutedChanged(EventArgs e)
368         {
369             EventHandler handler = this.PerformLayouted;
370             if (handler != null)
371                 this.PerformLayouted(this, e);
372         }
373
374         protected virtual void OnPageBoundChanged(EventArgs e)
375         {
376             EventHandler handler = this.PageBoundChanged;
377             if (handler != null)
378                 this.PageBoundChanged(this, e);
379         }
380
381         void render_ChangedRightToLeft(object sender, EventArgs e)
382         {
383             this.Document.Src = new SrcPoint(0, this.Document.Src.Row, this.Src.Y);
384         }
385
386         void render_ChangedRenderResource(object sender, ChangedRenderRsourceEventArgs e)
387         {
388             this._LayoutLines.ClearLayoutCache();
389             if (e.type == ResourceType.Font)
390             {
391                 this.CalculateClipRect();
392                 this.CalculateLineCountOnScreen();
393             }
394         }
395
396         void doc_Update(object sender, DocumentUpdateEventArgs e)
397         {
398             switch (e.type)
399             {
400                 case UpdateType.Clear:
401                     this._LongestWidth = 0;
402                     break;
403             }
404         }
405
406         IList<LineToIndexTableData> LayoutLines_SpilitStringByPixelbase(object sender, SpilitStringEventArgs e)
407         {
408             double WrapWidth;
409             if (this.Document.LineBreak == LineBreakMethod.PageBound)
410                 WrapWidth = this.render.TextArea.Width - LineBreakingMarginWidth;  //余白を残さないと欠ける
411             else
412                 WrapWidth = this.render.emSize.Width * this.Document.LineBreakCharCount;
413
414             if (WrapWidth < 0 && this.Document.LineBreak != LineBreakMethod.None)
415                 throw new InvalidOperationException();
416
417             int startIndex = e.index;
418             int endIndex = e.index + e.length - 1;
419
420             LineToIndexTable layoutLineCollection = (LineToIndexTable)sender;
421
422             return this.render.BreakLine(e.buffer,layoutLineCollection, startIndex, endIndex, WrapWidth);
423         }
424
425         IList<LineToIndexTableData> LayoutLines_SpilitStringByChar(object sender, SpilitStringEventArgs e)
426         {
427             return this.Document.CreateLineList(e.index, e.length, Document.MaximumLineLength);
428         }
429     }
430 }