sdk: const correctness
[sdk] / extras / html / HTMLView.ec
1 import "ecere"
2 import "lines"
3 import "htmlParser"
4
5 enum BlockType
6 {
7    HTML,
8    TEXT = 1,
9    IMAGE,
10    BR,
11    BODY,
12    FONT,
13    CENTER,
14    TABLE,
15    TR,
16    TD,
17    ANCHOR,
18    INPUT,
19    FORM,
20    TITLE,
21    HEAD
22 };
23
24 define LEFT_MARGIN = 10;
25 define RIGHT_MARGIN = 10;
26 define TOP_MARGIN = 10;
27 define BOTTOM_MARGIN = 10;
28
29 enum VerticalAlignment { middle, top, bottom };
30
31 enum HorizontalAlignment { none, left, middle, right };
32
33 define CELL_SPACING = 0;
34
35 class RenderFlags { bool lineW:1,width:1,render:1,minW:1; };
36
37 enum InputType { text, submit, checkbox, radio, hidden };
38
39 class ImageEntry : struct
40 {
41    ImageEntry prev, next;
42    uint size;
43    char * src;
44    OldList bitmapPtrs;
45    Bitmap bitmap;
46    bool missing;
47    char * referer;
48
49    ~ImageEntry()
50    {
51       delete bitmap;
52       delete referer;
53       delete src;
54       bitmapPtrs.Free(null);
55    }
56 };
57
58 class FontEntry : struct
59 {
60    FontEntry prev, next;
61    char * face;
62    float size;
63    uint attribs;
64    Font font;
65
66    ~FontEntry()
67    {
68       delete face;
69    }
70 };
71
72 class RequestLink : OldLink
73 {
74    bool processed;
75 }
76
77 class AlignedObject : struct
78 {
79    AlignedObject prev, next;
80    int w;
81    int untilY;
82 };
83
84 // Temporary solution
85 static HTMLView browserWindow;
86
87 class ObjectThread : Thread
88 {
89    bool objectThreadDead;
90    bool objectThreadTerminate;
91    Semaphore objectThreadSemaphore {};
92
93    uint Main()
94    {
95       objectThreadDead = false;
96       for(;!objectThreadTerminate;)
97       {
98          if(objectRequests.first)
99          {
100             RequestLink request;
101
102             //((GuiApplication)__thisModule).Lock();
103
104             for(;!objectThreadTerminate;)
105             {
106                OldLink bitmapPtr;
107                File file;
108                ImageEntry entry;
109                char path[MAX_LOCATION], referer[MAX_LOCATION];
110
111                objectsMutex.Wait();
112                for(request = objectRequests.first; request && request.processed; request = (RequestLink)request.next);
113
114                if(request)
115                   request.processed = true;
116                objectsMutex.Release();
117                if(!request) break;
118
119                entry = request.data;
120
121                strcpy(path, entry.src);
122                //strcpy(referer, browserWindow.location ? browserWindow.location : "");
123                strcpy(referer, entry.referer); //browserWindow.location ? browserWindow.location : "");
124
125                //((GuiApplication)__thisModule).Unlock();
126
127                if(path && strstr(path, "http://") == path)
128                {
129                   HTTPFile httpFile {};
130                   file = httpFile;
131                   incref file;
132                   //printf("Opening URL\n");
133
134                   //((GuiApplication)__thisModule).PauseNetworkEvents();
135                   if(httpFile.OpenURL(path, referer, null))
136                   {
137                      char extension[MAX_EXTENSION];
138                      entry.bitmap = Bitmap { alphaBlend = true };
139                      //printf("Loading File\n");
140                      entry.bitmap.LoadFromFile(file, GetExtension(path, extension), null);
141                      //printf("Done\n");
142                   }
143                   else
144                      entry.missing = true;
145                   //((GuiApplication)__thisModule).ResumeNetworkEvents();
146                }
147                else
148                {
149                   file = FileOpen(path, read);
150                   if(!file)
151                      entry.missing = true;
152                   else
153                   {
154                      char extension[MAX_EXTENSION];
155                      entry.bitmap = Bitmap { alphaBlend = true };
156                      entry.bitmap.LoadFromFile(file, GetExtension(path, extension), null);
157                      incref file;
158                   }
159                }
160                delete file;
161
162                objectsMutex.Wait();
163                objectRequests.Remove(request);
164                delete request;
165                objectsMutex.Release();
166
167                ((GuiApplication)__thisModule).Lock();
168                if(!objectThreadTerminate)
169                {
170                   Display display;
171                   Window rootWindow = browserWindow.rootWindow;
172                   if(rootWindow.is3D)
173                      display = rootWindow.parent.display;
174                   else
175                      display = rootWindow.display;
176
177                   if(display)
178                   {
179                      display.Lock(true);
180                      entry.bitmap.MakeDD(browserWindow.displaySystem);
181                      if(entry.bitmap && entry.bitmap.alphaBlend)
182                      {
183                         entry.bitmap.Convert(null, pixelFormat888, null);
184                         // entry.bitmap.displaySystem = displaySystem;
185                      }
186
187                      for(bitmapPtr = entry.bitmapPtrs.first; bitmapPtr; bitmapPtr = bitmapPtr.next)
188                      {
189                         *((Bitmap*) bitmapPtr.data) = entry.bitmap;
190                      }
191                      browserWindow.ComputeMinSizes();
192                      browserWindow.ComputeSizes();
193                      browserWindow.PositionForms();
194                      browserWindow.Update(null);
195
196                      // ((GuiApplication)__thisModule).UpdateDisplay();
197                      display.Unlock();
198                   }
199
200                   // TRIED MOVING THIS HERE BECAUSE OF printf("bug") in GuiApplication if(window.display.current)
201                   ((GuiApplication)__thisModule).UpdateDisplay();
202                }
203                ((GuiApplication)__thisModule).Unlock();
204             }
205             //((GuiApplication)__thisModule).Unlock();
206          }
207          else
208          {
209             //printf("Waiting for Object Thread Semaphore\n");
210             objectThreadSemaphore.Wait();
211          }
212       }
213       objectThreadDead = true;
214       return 0;
215    }
216 }
217
218 static ObjectThread objectThread1 { };
219 static ObjectThread objectThread2 { };
220 static ObjectThread objectThread3 { };
221 static ObjectThread objectThread4 { };
222 static Mutex objectsMutex {};
223 static OldList imageCache;
224 /*static */OldList fontCache;
225
226 static OldList objectRequests;
227
228 static void WriteBlock(File f, Block block)
229 {
230    static int indent = 0;
231    Block child;
232    int c;
233
234    for(c = 0; c<indent; c++)
235       f.Printf("   ");
236
237    switch(block.type)
238    {
239       case TEXT:
240          f.Printf("Text: %s\n", block.text);
241          break;
242       case IMAGE:
243          f.Printf("Image: %d %d %s\n", block.w, block.h, block.src);
244          break;
245       case BR:
246          f.Printf("Br\n");
247          break;
248       case BODY:
249          f.Printf("Body\n");
250          break;
251       case ANCHOR:
252          f.Printf("Anchor: %s\n", block.src);
253          break;
254       case FONT:
255          f.Printf("Font: %s, %d\n", block.face, block.size);
256          break;
257       case CENTER:
258          f.Printf("Center\n");
259          break;
260       case TABLE:
261          f.Printf("Table\n");
262          break;
263       case TR:
264          f.Printf("Tr\n");
265          break;
266       case TD:
267          f.Printf("Td\n");
268          break;
269    }
270
271    for(child = block.subBlocks.first; child; child = child.next)
272    {
273       indent ++;
274       WriteBlock(f, child);
275       indent --;
276    }
277 }
278
279 static void ComputeImageSize(Block block)
280 {
281    Block child;
282
283    if(block.type == IMAGE)
284    {
285       if(block.width)
286          block.w = block.width;
287       else
288          block.w = block.bitmap ? block.bitmap.width : 0;
289
290       if(block.height)
291          block.h = block.height;
292       else
293          block.h = block.bitmap ? block.bitmap.height : 0;
294    }
295    for(child = block.subBlocks.first; child; child = child.next)
296       ComputeImageSize(child);
297 }
298
299 // Resources Cache
300 Window sharedWindow
301 {
302    visible = false;
303
304    bool OnLoadGraphics()
305    {
306       ImageEntry entry;
307       FontEntry fEntry;
308       for(entry = imageCache.first; entry; entry = entry.next)
309       {
310          entry.bitmap.MakeDD(displaySystem);
311          if(entry.bitmap && entry.bitmap.alphaBlend)
312          {
313             entry.bitmap.Convert(null, pixelFormat888, null);
314             // entry.bitmap.displaySystem = displaySystem;
315          }
316       }
317       for(fEntry = fontCache.first; fEntry; fEntry = fEntry.next)
318       {
319          if(!fEntry.font)
320             fEntry.font = displaySystem.LoadFont(fEntry.face, fEntry.size, fEntry.attribs);
321       }
322       return true;
323    }
324
325    void OnUnloadGraphics()
326    {
327       ImageEntry entry;
328       FontEntry fontEntry;
329       while((entry = imageCache.first))
330       {
331          imageCache.Remove(entry);
332          delete entry;
333       }
334       while((fontEntry = fontCache.first))
335       {
336          displaySystem.UnloadFont(fontEntry.font);
337          fontCache.Remove(fontEntry);
338          delete fontEntry;
339       }
340    }
341 };
342
343 class HTMLView : Window
344 {
345    HTMLFile html {};
346    BitmapResource missing { "<:ecere>emblems/unreadable.png", window = sharedWindow /*this*/ };
347    char * location;
348    Block overLink, clickedLink;
349    Block overBlock;
350    int overPos;
351
352    void ComputeMinSizes()
353    {
354       if(html.body)
355       {
356          ComputeImageSize(html.body);
357
358          // Pre compute some stuff
359          {
360             Block block = html.body;
361             int textPos = 0;
362             int centered = 0;
363
364             Surface surface = display.GetSurface(0,0,null);
365             if(surface)
366             {
367                surface.TextFont(html.defaultFont.font.font);
368
369                while(block)
370                {
371                   Block nextBlock;
372                   int nextTextPos;
373                   int w;
374                   ComputeLine(surface, block, textPos, &nextBlock, &nextTextPos, &centered, &w, MAXINT, 0, RenderFlags { minW = true }, 0, null, null, null, true, 0, LEFT_MARGIN);
375                   block = nextBlock;
376                   textPos = nextTextPos;
377                }
378                delete surface;
379             }
380          }
381       }
382    }
383
384    void Clear(Block block)
385    {
386       Block b;
387
388       if(block.type != TABLE && block.type != IMAGE)
389       {
390          block.width = 0;
391          block.height = 0;
392       }
393       if(block.type == TABLE)
394       {
395          block.columns.Free(null);
396       }
397       if(block.type != IMAGE)
398       {
399          block.w = 0;
400          block.h = 0;
401          block.lineW = 0;
402          block.minW = 0;
403          block.rowSpan = block.span = 1;
404       }
405
406       for(b = block.subBlocks.first; b; b = b.next)
407       {
408          Clear(b);
409       }
410    }
411    void ComputeSizes()
412    {
413       int width = clientSize.w;
414       int height = clientSize.h;
415       Block block = html.body;
416       int totalWidth = 0, totalHeight = 0;
417       int y = TOP_MARGIN;
418       int textPos = 0;
419       int centered = 0;
420       Surface surface = display.GetSurface(0,0,null);
421
422       if(!initSize.w)
423          width = parent.clientSize.w;
424       if(!initSize.h)
425          height = parent.clientSize.h;
426
427       if(surface)
428       {
429          int maxH = height - BOTTOM_MARGIN;
430          int h = 0;
431
432          if(html.defaultFont.font)
433             surface.TextFont(html.defaultFont.font.font);
434          while(block)
435          {
436             Block nextBlock;
437             int nextTextPos;
438             int w, newH;
439             int maxW = width - (LEFT_MARGIN + RIGHT_MARGIN);
440             bool changeLine;
441
442             newH = ComputeLine(surface, block, textPos, &nextBlock, &nextTextPos, &centered, &w, maxW, maxH - y, RenderFlags { lineW = true, width = true }, 0, null, null, &changeLine, true, y, LEFT_MARGIN);
443             if(changeLine)
444             {
445                y += newH;
446                h = 0;
447             }
448             block = nextBlock;
449             textPos = nextTextPos;
450
451             totalWidth = Max(totalWidth, w);
452          }
453          delete surface;
454
455          totalHeight = y + BOTTOM_MARGIN;
456          totalWidth += LEFT_MARGIN + RIGHT_MARGIN;
457
458          SetScrollArea(totalWidth, totalHeight, false);
459          SetScrollArea(totalWidth, totalHeight, false);
460       }
461
462       if(!initSize.w || !initSize.h)
463          clientSize = Size {!initSize.w ? totalWidth : clientSize.w, !initSize.h ? totalHeight : clientSize.h };
464    }
465
466    void CreateForms(Block block)
467    {
468       Block child;
469
470       if(block.type == INPUT)
471       {
472          switch(block.inputType)
473          {
474             case submit:
475                block.window = Button { this, text = block.value, position = Point { 10, 50 }, id = (int64)block, NotifyClicked = ButtonClicked, isDefault = true };
476                if(block.size)
477                   block.window.size = { w = (int)(block.size * 8) };
478                if(block.src)
479                {
480                   ((Button)block.window).bitmap = { block.src };
481                   ((Button)block.window).bevel = false;
482                }
483                eInstance_IncRef(block.window);
484                block.window.Create();
485                block.window.cursor = ((GuiApplication)__thisModule).GetCursor(arrow);
486                //if(!html.defaultButton) html.defaultButton = block.window;
487                break;
488             case checkbox:
489                block.window = Button { this, isCheckbox = true, position = Point { 10, 100 }, id = (int64)block, NotifyClicked = ButtonClicked };
490                eInstance_IncRef(block.window);
491                block.window.Create();
492                block.window.cursor = ((GuiApplication)__thisModule).GetCursor(arrow);
493                break;
494             case radio:
495                block.window = Button { this, isRadio = true, position = Point { 10, 100 }, id = (int64)block, NotifyClicked = ButtonClicked };
496                eInstance_IncRef(block.window);
497                block.window.Create();
498                block.window.cursor = ((GuiApplication)__thisModule).GetCursor(arrow);
499                break;
500             case text:
501                block.window = EditBox { this, contents = block.value, size = { w = (int)(block.size * 8) }, position = Point { 10, 20 }, id = (int64)block };
502                if(!block.size)
503                {
504                   ((EditBox)block.window).size.w = block.parent.width;
505                }
506                eInstance_IncRef(block.window);
507                block.window.Create();
508                break;
509          }
510          /*if(!activeChild && block.window)
511             block.window.Activate();*/
512       }
513
514       for(child = block.subBlocks.last; child; child = child.prev)
515          CreateForms(child);
516    }
517
518    void LoadGraphics(Block block, void **previous)
519    {
520       Block child;
521
522       if(block.src && (block.type == IMAGE || block.type == TD || block.type == TABLE))
523       {
524          char path[MAX_LOCATION];
525          ImageEntry entry = null;
526
527          strcpy(path, location ? location : "");
528          if(location && path[strlen(path)-1] != '/')
529             PathCat(path, "..");
530          if(block.src[0] == '/' && block.src[1] == '/')
531          {
532             strcpy(path, "http:");
533             strcat(path, block.src);
534          }
535          else
536             PathCat(path, block.src);
537
538          if(!strstr(path, "File://"))
539          {
540             for(entry = imageCache.first; entry; entry = entry.next)
541                if(!strcmp(entry.src, path))
542                   break;
543          }
544
545          if(!entry)
546          {
547             entry = ImageEntry { src = CopyString(path), referer = CopyString(location) };
548             imageCache.Add(entry);
549          }
550
551          block.imageEntry = entry;
552          block.entryPtr = OldLink { data = &block.bitmap };
553          entry.bitmapPtrs.Add(block.entryPtr);
554
555          if(entry.bitmap)
556             block.bitmap = entry.bitmap;
557          else
558          {
559             if(path && (strstr(path, "http://") == path || strstr(path, "https://") == path))
560             {
561                RequestLink request;
562                objectsMutex.Wait();
563                for(request = objectRequests.first; request; request = (RequestLink)request.next)
564                {
565                   if(request.data == entry)
566                      break;
567                }
568                if(!request)
569                {
570                   request = RequestLink { data = entry };
571                   objectRequests.Insert(*previous, request);
572                   *previous = request;
573                   objectThread1.objectThreadSemaphore.Release();
574                   objectThread2.objectThreadSemaphore.Release();
575                   objectThread3.objectThreadSemaphore.Release();
576                   objectThread4.objectThreadSemaphore.Release();
577                }
578                else if(*previous != request)
579                {
580                   objectRequests.Move(request, *previous);
581                   *previous = request;
582                }
583                objectsMutex.Release();
584             }
585             else
586             {
587                // ADDED THIS TO LOAD LOCAL FILES RIGHT HERE...
588                OldLink bitmapPtr;
589                File file = FileOpen(path, read);
590                if(!file)
591                   entry.missing = true;
592                else
593                {
594                   char extension[MAX_EXTENSION];
595                   entry.bitmap = Bitmap { alphaBlend = true };
596                   entry.bitmap.LoadFromFile(file, GetExtension(path, extension), null);
597                   display.Lock(false);
598                   entry.bitmap.MakeDD(displaySystem);
599                   display.Unlock();
600                }
601                delete file;
602                for(bitmapPtr = entry.bitmapPtrs.first; bitmapPtr; bitmapPtr = bitmapPtr.next)
603                {
604                   *((Bitmap*) bitmapPtr.data) = entry.bitmap;
605                }
606                block.bitmap = entry.bitmap;
607             }
608          }
609          block.font = block.parent.font;
610          block.textColor = block.parent.textColor;
611       }
612       else if(block.type == FONT || block.type == ANCHOR)
613       {
614          FontEntry entry;
615          for(entry = fontCache.first; entry; entry = entry.next)
616          {
617             if(!strcmpi(entry.face, block.face) &&
618                entry.size == block.size &&
619                entry.attribs == block.attribs)
620             {
621                break;
622             }
623          }
624          if(!entry)
625          {
626             display.Lock(false);
627             entry = FontEntry { font = displaySystem.LoadFont(block.face, block.size, block.attribs), size = block.size, attribs = block.attribs, face = CopyString(block.face) };
628             fontCache.Add(entry);
629             display.Unlock();
630          }
631
632          if(entry)
633             block.font = entry;
634       }
635       else if(block.parent)
636       {
637          block.font = block.parent.font;
638          block.textColor = block.parent.textColor;
639       }
640       else
641       {
642          block.textColor = black;
643       }
644
645       for(child = block.subBlocks.first; child; child = child.next)
646          LoadGraphics(child, previous);
647    }
648
649    void NormalizeSelection(Block * startBlock, int * startSel, Block * endBlock, int * endSel)
650    {
651       bool selAfter = false;
652       Block b;
653       for(b = selBlock; b; b = GetNextBlock(b))
654       {
655          if(b != selBlock && b == textBlock)
656          {
657             selAfter = true;
658             break;
659          }
660       }
661
662       if(textBlock == selBlock)
663       {
664          *startSel = Min(selPosition, curPosition);
665          *endSel = Max(selPosition, curPosition);
666          *startBlock = *endBlock = textBlock;
667       }
668       else if(!selAfter)
669       {
670          *startBlock = textBlock;
671          *startSel = curPosition;
672          *endSel = selPosition;
673          *endBlock = selBlock;
674       }
675       else
676       {
677          *startBlock = selBlock;
678          *startSel = selPosition;
679          *endSel = curPosition;
680          *endBlock = textBlock;
681       }
682    }
683
684    void PositionForms()
685    {
686       Block block = html.body;
687       int y = TOP_MARGIN;
688       int textPos = 0;
689       int centered = 0;
690       int maxH = clientSize.h - BOTTOM_MARGIN;
691       OldList leftObjects { };
692       OldList rightObjects { };
693       AlignedObject object, nextObject;
694       int h = 0;
695
696       Surface surface = display.GetSurface(0,0,null);
697       if(surface)
698       {
699          Font font;
700          if(html.defaultFont.font)
701             surface.TextFont(html.defaultFont.font.font);
702
703          font = surface.font;
704          for(;block;)
705          {
706             Block nextBlock;
707             int nextTextPos;
708             int w, newH;
709             int left, right;
710             int x, maxW;
711             int thisLineCentered = centered;
712             bool changeLine;
713
714             left = LEFT_MARGIN;
715             right = clientSize.w - RIGHT_MARGIN;
716
717             for(object = leftObjects.last; object; object = nextObject)
718             {
719                nextObject = object.prev;
720                if(y < object.untilY || object.next)
721                   left += object.w;
722                else
723                   leftObjects.Delete(object);
724             }
725             for(object = rightObjects.last; object; object = nextObject)
726             {
727                nextObject = object.prev;
728                if(y < object.untilY || object.next)
729                   right -= object.w;
730                else
731                   rightObjects.Delete(object);
732             }
733             right = Max(left, right);
734             maxW = right - left;
735
736             newH = ComputeLine(surface, block, textPos, &nextBlock, &nextTextPos, &centered, &w, maxW, maxH - y, RenderFlags {}, y, &leftObjects, &rightObjects, &changeLine, true, 0 /*y*/, LEFT_MARGIN);
737
738             if(thisLineCentered)
739                x = Max(left,(left + right - w) / 2);
740             else
741                x = left;
742
743             surface.font = font;
744
745             PositionLine(this, surface, x - scroll.x, y - scroll.y,
746                maxW, newH, block, textPos, nextBlock, nextTextPos,
747                left - scroll.x, right - scroll.x);
748
749             if(changeLine)
750             {
751                y += newH;
752                h = 0;
753             }
754             block = nextBlock;
755             textPos = nextTextPos;
756          }
757          delete surface;
758       }
759    }
760
761    void Open(char * location, char * firstReferer)
762    {
763       HTTPFile f { /*reuseConnection = false*/ };
764       char referer[MAX_LOCATION] = "";
765       char relocation[MAX_LOCATION];
766       bool opened = false;
767
768       strcpy(relocation, location);
769
770       // PrintLn("\n\n\nOpening new location: ", location, "\n");
771
772       if(strstr(location, "http://") != location && strstr(location, "https://") != location)
773       {
774          if(!FileExists(location))
775          {
776             strcpy(relocation, "http://");
777             strcat(relocation, location);
778          }
779          else
780          {
781             delete f;
782             f = (HTTPFile)FileOpen(location, read);
783             if(f)
784                opened = true;
785          }
786       }
787       clickedLink = null;
788       overLink = null;
789       overBlock = null;
790
791       if(firstReferer)
792          strcpy(referer, firstReferer);
793
794       delete html;
795       html = HTMLFile {};
796
797       SetScrollArea(0,0,0);
798       Update(null);
799
800       while(!opened)
801       {
802          char path[MAX_LOCATION];
803
804          if(this.location)
805             delete this.location;
806          this.location = CopyString(relocation);
807          fileName = this.location;
808          ((GuiApplication)__thisModule).UpdateDisplay();
809
810          if(!(opened = f.OpenURL(this.location, referer, relocation)) && !strcmp(this.location, relocation))
811             break;
812
813          if(!opened)
814          {
815             //printf("Relocated to %s\n", relocation);
816          }
817          strcpy(referer, this.location);
818
819          // Fix up relocation relative paths
820          strcpy(path, this.location);
821          if(path[strlen(path)-1] != '/')
822             PathCat(path, "..");
823          if(relocation[0] == '/' && relocation[1] == '/')
824          {
825             strcpy(path, "http:");
826             strcat(path, relocation);
827          }
828          else
829             PathCat(path, relocation);
830          strcpy(relocation, path);
831       }
832       if(this.location)
833          delete this.location;
834       this.location = CopyString(relocation);
835       fileName = this.location;
836
837       if(opened)
838       {
839          bool isHTTP = eClass_IsDerived(f._class, class(HTTPFile));
840          void * previous = null;
841          bool isImage = false;
842          bool isPlain = isHTTP ? false : true;
843
844          // Handle known types
845          if(isHTTP && f.contentType)
846          {
847             if(strstr(f.contentType, "image/") == f.contentType)
848                isImage = true;
849             else if(strstr(f.contentType, "text/") == f.contentType && strnicmp(f.contentType + 5, "html", 4))
850                isPlain = true;
851          }
852          else
853          {
854             const String imageExt[] = { "jpg", "jpeg", "bmp", "pcx", "png", "gif" };
855             const String htmlExt[] = { "html", "htm", "php" };
856             const String textExt[] = { "c", "h", "ec", "eh", "epj", "cpp", "cxx", "cc", "hpp", "hxx", "hh", "m", "java", "cs", "py", "Makefile", "mk", "cf" };
857             char ext[MAX_EXTENSION];
858             int i;
859             GetExtension(fileName, ext);
860             for(i = 0; i < sizeof(imageExt) / sizeof(imageExt[0]); i++)
861                if(!strcmpi(ext, imageExt[i]))
862                {
863                   isImage = true;
864                   break;
865                }
866
867             for(i = 0; i < sizeof(htmlExt) / sizeof(htmlExt[0]); i++)
868                if(!strcmpi(ext, htmlExt[i]))
869                {
870                   isPlain = false;
871                   break;
872                }
873
874             for(i = 0; i < sizeof(textExt) / sizeof(textExt[0]); i++)
875                if(!strcmpi(ext, textExt[i]))
876                {
877                   isPlain = true;
878                   break;
879                }
880          }
881
882          if(isImage)
883          {
884             Block subBlock;
885             html.body = HTMLFile::AddBlock(html.block, BODY);
886
887             subBlock = HTMLFile::AddBlock(html.body, IMAGE);
888             subBlock.valign = bottom;
889             subBlock.halign = middle;
890             subBlock.src = CopyString(fileName);
891             html.defaultFont.type = FONT;
892             html.defaultFont.face = CopyString("Times New Roman");
893          }
894          else if(isPlain)
895          {
896             uint size;
897             TempFile tmp { };
898             Block subBlock;
899             char * text;
900             int len;
901             String cd = eClass_IsDerived(f._class, class(HTTPFile)) ? f.contentDisposition : null;
902
903             String tmpPath = PrintString("File://", (uintptr)tmp);
904             f.CopyTo(tmpPath);
905             size = tmp.GetSize();
906             tmp.Seek(0, start);
907
908             html.defaultFont.type = FONT;
909             html.defaultFont.face = CopyString("Courier New");
910
911             if(cd)
912             {
913                char fn[MAX_LOCATION];
914                while(GetKeyWordEx(&cd, fn, sizeof(fn), true, false))
915                {
916                   if(!strcmp(fn, "filename") && GetKeyWordEx(&cd, fn, sizeof(fn), true, true))
917                   {
918                      html.titleBlock = HTMLFile::AddBlock(html.block, TITLE);
919                      subBlock = HTMLFile::AddBlock(html.titleBlock, TEXT);
920                      subBlock.text = CopyString(fn);
921                      subBlock.textLen = strlen(fn);
922                   }
923                }
924             }
925
926             html.body = HTMLFile::AddBlock(html.block, BODY);
927             html.block = html.body;
928
929             text = new char[size + 1];
930             len = tmp.Read(text, 1, size);
931             text[len] = 0;
932
933             {
934                int c;
935                char ch;
936                int start = 0;
937                Block textBlock = HTMLFile::AddBlock(html.block, TEXT);
938
939                for(c = 0; ; c++)
940                {
941                   ch = text[c];
942                   if(ch == '\n' || ch == '\r' || !ch)
943                   {
944                      int len = c - start;
945                      textBlock.text = renew textBlock.text char[textBlock.textLen + 1 + len];
946                      memmove(textBlock.text + len, textBlock.text, textBlock.textLen + 1);
947                      memcpy(textBlock.text, text + start, len);
948                      textBlock.textLen += len;
949                      if(!ch) break;
950                      {
951                         Block block { type = BR, parent = textBlock.parent };
952                         Block newBlock { type = TEXT, parent = textBlock.parent };
953
954                         textBlock.parent.subBlocks.Insert(textBlock, block);
955                         textBlock.parent.subBlocks.Insert(block, newBlock);
956
957                         newBlock.textLen = 0;
958                         newBlock.text = new char[1];
959                         newBlock.text[0] = 0;
960
961                         textBlock = newBlock;
962                      }
963                      if(ch == '\r' && text[c+1] == '\n') c++;
964                      start = c + 1;
965                   }
966                }
967
968                html.block = html.block.parent;
969                delete text;
970             }
971             delete tmp;
972             delete tmpPath;
973
974          }
975          else
976          {
977             html.Parse(f);
978             if(html.baseHRef)
979             {
980                delete this.location;
981                this.location = CopyString(html.baseHRef);
982             }
983          }
984          LoadGraphics(html.defaultFont, &previous);
985          html.block.font = html.defaultFont.font;
986          LoadGraphics(html.block, &previous);
987          CreateForms(html.block);
988
989          ComputeMinSizes();
990          ComputeSizes();
991          PositionForms();
992          Update(null);
993
994          /*
995          {
996             File f = FileOpen("debug2.txt", write);
997             if(f)
998             {
999                WriteBlock(f, html.body);
1000                delete f;
1001             }
1002          }
1003          */
1004
1005       }
1006       ((GuiApplication)__thisModule.application).Unlock();
1007       // PrintLn("At position ", f.Tell(), " for ", fileName);
1008       delete f;
1009       ((GuiApplication)__thisModule.application).Lock();
1010       NotifyPageOpened(master);
1011    }
1012
1013    void OpenFile(File f, char * firstReferer)
1014    {
1015       char referer[MAX_LOCATION] = "";
1016       char relocation[MAX_LOCATION];
1017       bool opened = false;
1018
1019       clickedLink = null;
1020       overLink = null;
1021       overBlock = null;
1022
1023       if(firstReferer)
1024          strcpy(referer, firstReferer);
1025
1026       delete html;
1027       html = HTMLFile {};
1028
1029       SetScrollArea(0,0,0);
1030       Update(null);
1031
1032       opened = true;
1033       this.location = null;
1034       fileName = this.location;
1035
1036       if(opened)
1037       {
1038          void * previous = null;
1039          html.Parse(f);
1040          LoadGraphics(html.defaultFont, &previous);
1041          html.block.font = html.defaultFont.font;
1042          LoadGraphics(html.block, &previous);
1043          CreateForms(html.block);
1044
1045          ComputeMinSizes();
1046          ComputeSizes();
1047          PositionForms();
1048          /*
1049          {
1050             File f = FileOpen("debug2.txt", write);
1051             if(f)
1052             {
1053                WriteBlock(f, html.body);
1054                delete f;
1055             }
1056          }
1057          */
1058       }
1059       NotifyPageOpened(master);
1060    }
1061
1062    bool ScrollToAnchor(Block block, const char * anchor)
1063    {
1064       bool result = false;
1065       if(block.type == ANCHOR && block.anchor && !strcmpi(block.anchor, anchor))
1066       {
1067          SetScrollPosition(0, block.startY);
1068          result = true;
1069       }
1070       else
1071       {
1072          Block subBlock;
1073          for(subBlock = block.subBlocks.first; subBlock; subBlock = subBlock.next)
1074          {
1075             if((result = ScrollToAnchor(subBlock, anchor)))
1076                break;
1077          }
1078       }
1079       return result;
1080    }
1081
1082    bool GoToAnchor(const char * anchor)
1083    {
1084       return anchor ? ScrollToAnchor(html.block, anchor) : false;
1085    }
1086
1087    virtual bool Window::NotifyPageOpened();
1088
1089    void OnDestroy()
1090    {
1091       objectThread1.objectThreadTerminate = true;
1092       objectThread2.objectThreadTerminate = true;
1093       objectThread3.objectThreadTerminate = true;
1094       objectThread4.objectThreadTerminate = true;
1095       ((GuiApplication)__thisModule).Unlock();
1096       objectThread1.objectThreadSemaphore.Release();
1097       objectThread2.objectThreadSemaphore.Release();
1098       objectThread3.objectThreadSemaphore.Release();
1099       objectThread4.objectThreadSemaphore.Release();
1100
1101
1102       while(!objectThread1.objectThreadDead ||
1103             !objectThread2.objectThreadDead ||
1104             !objectThread3.objectThreadDead ||
1105             !objectThread4.objectThreadDead)
1106       {
1107          // ((GuiApplication)__thisModule).ProcessNetworkEvents();
1108          Sleep(0.01);
1109       }
1110
1111       //objectThread.Wait();
1112       objectRequests.Free(null);
1113
1114       ((GuiApplication)__thisModule).Lock();
1115    }
1116
1117    bool OnCreate()
1118    {
1119       browserWindow = this;
1120       objectThread1.objectThreadTerminate = false;
1121       objectThread2.objectThreadTerminate = false;
1122       objectThread3.objectThreadTerminate = false;
1123       objectThread4.objectThreadTerminate = false;
1124
1125       objectThread1.Create();
1126       objectThread2.Create();
1127       objectThread3.Create();
1128       objectThread4.Create();
1129
1130       /*
1131       if(location)
1132       {
1133          location = CopyString(location);
1134       }
1135       */
1136       return true;
1137    }
1138
1139    bool OnPostCreate()
1140    {
1141       if(location)
1142          Open(location, null);
1143       return true;
1144    }
1145
1146    void OnResize(int width, int height)
1147    {
1148       if(html.body)
1149       {
1150          ComputeMinSizes();
1151          ComputeSizes();
1152          PositionForms();
1153       }
1154    }
1155
1156    // For text selection
1157    Block textBlock, selBlock;
1158    int curPosition, selPosition;
1159    bool isSelected; // Persistent state changed by RenderLine
1160
1161    void OnRedraw(Surface surface)
1162    {
1163       Block block = html.body;
1164       int y = TOP_MARGIN;
1165       int textPos = 0;
1166       int centered = 0;
1167       int maxH = clientSize.h - BOTTOM_MARGIN;
1168       OldList leftObjects { };
1169       OldList rightObjects { };
1170       Font font;
1171
1172       AlignedObject object, nextObject;
1173       int h = 0;
1174
1175       surface.SetBackground(html.background);
1176       if(html.background.a < 255)
1177          surface.Area(0,0,clientSize.w,clientSize.h);
1178       else
1179          surface.Clear(colorBuffer);
1180       if(html.defaultFont.font) // TOFIX: Null! (No font set?)
1181          surface.TextFont(html.defaultFont.font.font);
1182       surface.SetForeground(html.defaultFont.textColor);
1183       isSelected = false;
1184
1185       for(;block;)
1186       {
1187          Block nextBlock;
1188          int nextTextPos;
1189          int w, newH;
1190          int left, right;
1191          int x, maxW;
1192          int thisLineCentered = centered;
1193          bool changeLine;
1194
1195          left = LEFT_MARGIN;
1196          right = clientSize.w - RIGHT_MARGIN;
1197
1198          for(object = leftObjects.last; object; object = nextObject)
1199          {
1200             nextObject = object.prev;
1201             if(y < object.untilY || object.next)
1202                left += object.w;
1203             else
1204                leftObjects.Delete(object);
1205          }
1206          for(object = rightObjects.last; object; object = nextObject)
1207          {
1208             nextObject = object.prev;
1209             if(y < object.untilY || object.next)
1210                right -= object.w;
1211             else
1212                rightObjects.Delete(object);
1213          }
1214          right = Max(left, right);
1215          maxW = right - left;
1216
1217          font = surface.font;
1218          newH = ComputeLine(surface, block, textPos, &nextBlock, &nextTextPos, &centered, &w, maxW, maxH - y, RenderFlags {}, y, &leftObjects, &rightObjects, &changeLine, false, 0, 0);
1219          surface.font = font;
1220          if(thisLineCentered)
1221             x = Max(left,(left + right - w) / 2);
1222          else
1223             x = left;
1224
1225          surface.TextFont(font);
1226
1227          //h = Max(h, newH);
1228          RenderLine(this, surface, x - scroll.x, y - scroll.y, maxW, newH, block, textPos, nextBlock, nextTextPos, left - scroll.x, right - scroll.x);
1229
1230          if(changeLine)
1231          {
1232             // y += h;
1233             y += newH;
1234             h = 0;
1235          }
1236          block = nextBlock;
1237          textPos = nextTextPos;
1238       }
1239       //y += h;
1240    }
1241
1242    void OnVScroll(ScrollBarAction action, int position, Key key)
1243    {
1244       Update(null);
1245    }
1246
1247    bool OnKeyHit(Key key, unichar character)
1248    {
1249       switch(key)
1250       {
1251          case shiftTab:
1252             CycleChildren(true, false, false, true);
1253             return false;
1254          case tab:
1255             CycleChildren(false, false, false, true);
1256             return false;
1257          case left: case right:
1258             horzScroll.OnKeyHit(key, character);
1259             break;
1260          case down: case up: case pageDown: case pageUp:
1261             vertScroll.OnKeyHit(key, character);
1262             break;
1263       }
1264       return true;
1265    }
1266
1267    bool OnKeyDown(Key key, unichar character)
1268    {
1269       switch(key)
1270       {
1271          case ctrlHome:
1272          {
1273             vertScroll.OnKeyDown(home, character);
1274             horzScroll.OnKeyDown(home, character);
1275             break;
1276          }
1277          case ctrlEnd:
1278          {
1279             vertScroll.OnKeyDown(end, character);
1280             horzScroll.OnKeyDown(end, character);
1281             break;
1282          }
1283       }
1284       return true;
1285    }
1286
1287    void OnHScroll(ScrollBarAction action, int position, Key key)
1288    {
1289       Update(null);
1290    }
1291
1292    bool PickHTML(int pickX, int pickY, Block* pickBlock, int * pickTextPos)
1293    {
1294       bool result = false;
1295       Block block = html.body;
1296       int y = TOP_MARGIN;
1297       int textPos = 0;
1298       int centered = 0;
1299       int maxH = clientSize.h - BOTTOM_MARGIN;
1300       OldList leftObjects { };
1301       OldList rightObjects { };
1302       AlignedObject object, nextObject;
1303       int h = 0;
1304
1305       Surface surface = display.GetSurface(0,0,null);
1306       if(surface)
1307       {
1308          if(html.defaultFont.font) // TOFIX: Null! (No font set?)
1309             surface.TextFont(html.defaultFont.font.font);
1310          for(;block;)
1311          {
1312             Block nextBlock;
1313             int nextTextPos;
1314             int w, newH;
1315             int left, right;
1316             int x, maxW;
1317             int thisLineCentered = centered;
1318             Font font = surface.font;
1319             bool changeLine;
1320
1321             left = LEFT_MARGIN;
1322             right = clientSize.w - RIGHT_MARGIN;
1323
1324             for(object = leftObjects.last; object; object = nextObject)
1325             {
1326                nextObject = object.prev;
1327                if(y < object.untilY || object.next)
1328                   left += object.w;
1329                else
1330                   leftObjects.Delete(object);
1331             }
1332             for(object = rightObjects.last; object; object = nextObject)
1333             {
1334                nextObject = object.prev;
1335                if(y < object.untilY || object.next)
1336                   right -= object.w;
1337                else
1338                   rightObjects.Delete(object);
1339             }
1340             right = Max(left, right);
1341             maxW = right - left;
1342
1343             newH = ComputeLine(surface, block, textPos, &nextBlock, &nextTextPos, &centered, &w, maxW, maxH - y, RenderFlags {}, y, &leftObjects, &rightObjects, &changeLine, false, 0, 0);
1344             if(thisLineCentered)
1345                x = Max(left,(left + right - w) / 2);
1346             else
1347                x = left;
1348
1349             surface.font = font;
1350
1351             if(PickLine(this, surface, x - scroll.x, y - scroll.y,
1352                maxW, newH, block, textPos, nextBlock, nextTextPos,
1353                left - scroll.x, right - scroll.x, pickX, pickY, pickBlock, pickTextPos))
1354             {
1355                result = true;
1356                break;
1357             }
1358
1359             if(changeLine)
1360             {
1361                y += newH;
1362                h = 0;
1363             }
1364             block = nextBlock;
1365             textPos = nextTextPos;
1366          }
1367          delete surface;
1368       }
1369
1370       for(object = leftObjects.last; object; object = nextObject)
1371       {
1372          nextObject = object.prev;
1373          leftObjects.Delete(object);
1374       }
1375       for(object = rightObjects.last; object; object = nextObject)
1376       {
1377          nextObject = object.prev;
1378          rightObjects.Delete(object);
1379       }
1380       return result;
1381    }
1382
1383    bool OnMouseMove(int x, int y, Modifiers mods)
1384    {
1385       Block pickBlock = null;
1386       Block linkBlock = null;
1387       int pickTextPos = 0;
1388
1389       if(!mods.isSideEffect)
1390       {
1391          PickHTML(x,y, &pickBlock, &pickTextPos);
1392
1393          if(pickBlock)
1394          {
1395             for(linkBlock = pickBlock; linkBlock; linkBlock = linkBlock.parent)
1396             {
1397                if(linkBlock.type == ANCHOR)
1398                   break;
1399             }
1400          }
1401          if(linkBlock)
1402          {
1403             if(linkBlock.href && strstr(linkBlock.href, "edit://") == linkBlock.href)
1404                cursor = ((GuiApplication)__thisModule).GetCursor(iBeam);
1405             else
1406                cursor = ((GuiApplication)__thisModule).GetCursor(hand);
1407             overLink = linkBlock;
1408          }
1409          else
1410          {
1411             if(pickBlock && pickBlock.type == TEXT)
1412             {
1413                cursor = ((GuiApplication)__thisModule).GetCursor(iBeam);
1414             }
1415             else
1416             {
1417                cursor = ((GuiApplication)__thisModule).GetCursor(arrow); //null);
1418             }
1419             overLink = null;
1420          }
1421          overBlock = pickBlock;
1422          overPos = pickTextPos;
1423       }
1424       return true;
1425    }
1426
1427    bool OnLeftButtonDown(int x, int y, Modifiers mods)
1428    {
1429       OnMouseMove(x, y, mods);
1430       if(overLink)
1431       {
1432          clickedLink = overLink;
1433
1434          Capture();
1435       }
1436       return true;
1437    }
1438
1439    virtual bool OnOpen(char * href)
1440    {
1441       char newLocation[MAX_LOCATION];
1442
1443       strcpy(newLocation, location ? location : "");
1444       if(newLocation[strlen(newLocation)-1] != '/')
1445          PathCat(newLocation, "..");
1446       if(href[0] == '/' && href[1] == '/')
1447       {
1448          strcpy(newLocation, "http:");
1449          strcat(newLocation, href);
1450       }
1451       else
1452          PathCat(newLocation, href);
1453
1454       Open(newLocation, null);
1455       return true;
1456    }
1457
1458    bool OnLeftButtonUp(int x, int y, Modifiers mods)
1459    {
1460       if(clickedLink)
1461       {
1462          ReleaseCapture();
1463          if(clickedLink == overLink && clickedLink.href)
1464          {
1465             if(OnOpen(clickedLink.href))
1466                Update(null);
1467          }
1468       }
1469       return true;
1470    }
1471
1472    void AddFormControls(char * location, Block block)
1473    {
1474       Block child;
1475       if(block.type == INPUT)
1476       {
1477          switch(block.inputType)
1478          {
1479             case radio:
1480                break;
1481             case text:
1482             {
1483                int c;
1484                int len;
1485                const char * text;
1486
1487                if(location[strlen(location)-1] != '?')
1488                {
1489                   strcat(location, "&");
1490                }
1491                strcat(location, block.name);
1492                strcat(location, "=");
1493
1494                len = strlen(location);
1495
1496                text = ((EditBox)block.window).contents;
1497
1498                for(c = 0; text[c]; c++)
1499                {
1500                   if(text[c] == ' ')
1501                      location[len++] = '+';
1502                   else
1503                   {
1504                      location[len++] = text[c];
1505                   }
1506                }
1507                location[len] = '\0';
1508                break;
1509             }
1510          }
1511       }
1512
1513       for(child = block.subBlocks.first; child; child = child.next)
1514       {
1515          AddFormControls(location, child);
1516       }
1517    }
1518
1519    bool ButtonClicked(Button button, int x, int y, Modifiers mods)
1520    {
1521       Block block = (Block)button.id;
1522       if(block.inputType == submit)
1523       {
1524          Block formBlock;
1525
1526          for(formBlock = block; formBlock; formBlock = formBlock.parent)
1527             if(formBlock.type == FORM)
1528                break;
1529
1530          if(formBlock && formBlock.action)
1531          {
1532             char newLocation[MAX_LOCATION];
1533
1534             strcpy(newLocation, location);
1535             if(newLocation[strlen(newLocation)-1] != '/')
1536                PathCat(newLocation, "..");
1537
1538             if(formBlock.action[0] == '/' && formBlock.action[1] == '/')
1539             {
1540                strcpy(newLocation, "http:");
1541                strcat(newLocation, formBlock.action);
1542             }
1543             else
1544                PathCat(newLocation, formBlock.action);
1545             strcat(newLocation, "?");
1546
1547             if(block.name)
1548             {
1549                strcat(newLocation, block.name);
1550                strcat(newLocation, "=");
1551             }
1552
1553             AddFormControls(newLocation, formBlock);
1554
1555             Open(newLocation, null);
1556             Update(null);
1557          }
1558       }
1559       return true;
1560    }
1561
1562    HTMLView()
1563    {
1564       //tabCycle = true;
1565       html.background = white;
1566    }
1567
1568    ~HTMLView()
1569    {
1570       delete this.location;
1571
1572       html.block.ClearEntries();
1573    }
1574
1575    property char * location
1576    {
1577       set
1578       {
1579          if(location)
1580             delete location;
1581          location = CopyString(value);
1582          if(created)
1583             Open(location, null);
1584       }
1585       get { return location; }
1586    }
1587
1588    property String title
1589    {
1590       get
1591       {
1592          String title = html.title;
1593          return title ? title : location;
1594       }
1595    }
1596 }