ide/Project: (#241) Seeing GCC warnings when building from IDE
[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(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
431          if(html.defaultFont.font)
432             surface.TextFont(html.defaultFont.font.font);
433          while(block)
434          {
435             Block nextBlock;
436             int nextTextPos;
437             int w, newH;
438             int maxW = width - (LEFT_MARGIN + RIGHT_MARGIN);
439             bool changeLine;
440
441             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);
442             if(changeLine)
443                y += newH;
444             block = nextBlock;
445             textPos = nextTextPos;
446
447             totalWidth = Max(totalWidth, w);
448          }
449          delete surface;
450
451          totalHeight = y + BOTTOM_MARGIN;
452          totalWidth += LEFT_MARGIN + RIGHT_MARGIN;
453
454          SetScrollArea(totalWidth, totalHeight, false);
455          SetScrollArea(totalWidth, totalHeight, false);
456       }
457
458       if(!initSize.w || !initSize.h)
459          clientSize = Size {!initSize.w ? totalWidth : clientSize.w, !initSize.h ? totalHeight : clientSize.h };
460    }
461
462    void CreateForms(Block block)
463    {
464       Block child;
465
466       if(block.type == INPUT)
467       {
468          switch(block.inputType)
469          {
470             case submit:
471                block.window = Button { this, text = block.value, position = Point { 10, 50 }, id = (int64)block, NotifyClicked = ButtonClicked, isDefault = true };
472                if(block.size)
473                   block.window.size = { w = (int)(block.size * 8) };
474                if(block.src)
475                {
476                   ((Button)block.window).bitmap = { block.src };
477                   ((Button)block.window).bevel = false;
478                }
479                eInstance_IncRef(block.window);
480                block.window.Create();
481                block.window.cursor = ((GuiApplication)__thisModule).GetCursor(arrow);
482                //if(!html.defaultButton) html.defaultButton = block.window;
483                break;
484             case checkbox:
485                block.window = Button { this, isCheckbox = true, position = Point { 10, 100 }, id = (int64)block, NotifyClicked = ButtonClicked };
486                eInstance_IncRef(block.window);
487                block.window.Create();
488                block.window.cursor = ((GuiApplication)__thisModule).GetCursor(arrow);
489                break;
490             case radio:
491                block.window = Button { this, isRadio = true, position = Point { 10, 100 }, id = (int64)block, NotifyClicked = ButtonClicked };
492                eInstance_IncRef(block.window);
493                block.window.Create();
494                block.window.cursor = ((GuiApplication)__thisModule).GetCursor(arrow);
495                break;
496             case text:
497                block.window = EditBox { this, contents = block.value, size = { w = (int)(block.size * 8) }, position = Point { 10, 20 }, id = (int64)block };
498                if(!block.size)
499                {
500                   ((EditBox)block.window).size.w = block.parent.width;
501                }
502                eInstance_IncRef(block.window);
503                block.window.Create();
504                break;
505          }
506          /*if(!activeChild && block.window)
507             block.window.Activate();*/
508       }
509
510       for(child = block.subBlocks.last; child; child = child.prev)
511          CreateForms(child);
512    }
513
514    void LoadGraphics(Block block, void **previous)
515    {
516       Block child;
517
518       if(block.src && (block.type == IMAGE || block.type == TD || block.type == TABLE))
519       {
520          char path[MAX_LOCATION];
521          ImageEntry entry = null;
522
523          strcpy(path, location ? location : "");
524          if(location && path[strlen(path)-1] != '/')
525             PathCat(path, "..");
526          if(block.src[0] == '/' && block.src[1] == '/')
527          {
528             strcpy(path, "http:");
529             strcat(path, block.src);
530          }
531          else
532             PathCat(path, block.src);
533
534          if(!strstr(path, "File://"))
535          {
536             for(entry = imageCache.first; entry; entry = entry.next)
537                if(!strcmp(entry.src, path))
538                   break;
539          }
540
541          if(!entry)
542          {
543             entry = ImageEntry { src = CopyString(path), referer = CopyString(location) };
544             imageCache.Add(entry);
545          }
546
547          block.imageEntry = entry;
548          block.entryPtr = OldLink { data = &block.bitmap };
549          entry.bitmapPtrs.Add(block.entryPtr);
550
551          if(entry.bitmap)
552             block.bitmap = entry.bitmap;
553          else
554          {
555             if(strstr(path, "http://") == path || strstr(path, "https://") == path)
556             {
557                RequestLink request;
558                objectsMutex.Wait();
559                for(request = objectRequests.first; request; request = (RequestLink)request.next)
560                {
561                   if(request.data == entry)
562                      break;
563                }
564                if(!request)
565                {
566                   request = RequestLink { data = entry };
567                   objectRequests.Insert(*previous, request);
568                   *previous = request;
569                   objectThread1.objectThreadSemaphore.Release();
570                   objectThread2.objectThreadSemaphore.Release();
571                   objectThread3.objectThreadSemaphore.Release();
572                   objectThread4.objectThreadSemaphore.Release();
573                }
574                else if(*previous != request)
575                {
576                   objectRequests.Move(request, *previous);
577                   *previous = request;
578                }
579                objectsMutex.Release();
580             }
581             else
582             {
583                // ADDED THIS TO LOAD LOCAL FILES RIGHT HERE...
584                OldLink bitmapPtr;
585                File file = FileOpen(path, read);
586                if(!file)
587                   entry.missing = true;
588                else
589                {
590                   char extension[MAX_EXTENSION];
591                   entry.bitmap = Bitmap { alphaBlend = true };
592                   entry.bitmap.LoadFromFile(file, GetExtension(path, extension), null);
593                   display.Lock(false);
594                   entry.bitmap.MakeDD(displaySystem);
595                   display.Unlock();
596                }
597                delete file;
598                for(bitmapPtr = entry.bitmapPtrs.first; bitmapPtr; bitmapPtr = bitmapPtr.next)
599                {
600                   *((Bitmap*) bitmapPtr.data) = entry.bitmap;
601                }
602                block.bitmap = entry.bitmap;
603             }
604          }
605          block.font = block.parent.font;
606          block.textColor = block.parent.textColor;
607       }
608       else if(block.type == FONT || block.type == ANCHOR)
609       {
610          FontEntry entry;
611          for(entry = fontCache.first; entry; entry = entry.next)
612          {
613             if(!strcmpi(entry.face, block.face) &&
614                entry.size == block.size &&
615                entry.attribs == block.attribs)
616             {
617                break;
618             }
619          }
620          if(!entry)
621          {
622             display.Lock(false);
623             entry = FontEntry { font = displaySystem.LoadFont(block.face, block.size, block.attribs), size = block.size, attribs = block.attribs, face = CopyString(block.face) };
624             fontCache.Add(entry);
625             display.Unlock();
626          }
627
628          if(entry)
629             block.font = entry;
630       }
631       else if(block.parent)
632       {
633          block.font = block.parent.font;
634          block.textColor = block.parent.textColor;
635       }
636       else
637       {
638          block.textColor = black;
639       }
640
641       for(child = block.subBlocks.first; child; child = child.next)
642          LoadGraphics(child, previous);
643    }
644
645    void NormalizeSelection(Block * startBlock, int * startSel, Block * endBlock, int * endSel)
646    {
647       bool selAfter = false;
648       Block b;
649       for(b = selBlock; b; b = GetNextBlock(b))
650       {
651          if(b != selBlock && b == textBlock)
652          {
653             selAfter = true;
654             break;
655          }
656       }
657
658       if(textBlock == selBlock)
659       {
660          *startSel = Min(selPosition, curPosition);
661          *endSel = Max(selPosition, curPosition);
662          *startBlock = *endBlock = textBlock;
663       }
664       else if(!selAfter)
665       {
666          *startBlock = textBlock;
667          *startSel = curPosition;
668          *endSel = selPosition;
669          *endBlock = selBlock;
670       }
671       else
672       {
673          *startBlock = selBlock;
674          *startSel = selPosition;
675          *endSel = curPosition;
676          *endBlock = textBlock;
677       }
678    }
679
680    void PositionForms()
681    {
682       Block block = html.body;
683       int y = TOP_MARGIN;
684       int textPos = 0;
685       int centered = 0;
686       int maxH = clientSize.h - BOTTOM_MARGIN;
687       OldList leftObjects { };
688       OldList rightObjects { };
689       AlignedObject object, nextObject;
690
691       Surface surface = display.GetSurface(0,0,null);
692       if(surface)
693       {
694          Font font;
695          if(html.defaultFont.font)
696             surface.TextFont(html.defaultFont.font.font);
697
698          font = surface.font;
699          for(;block;)
700          {
701             Block nextBlock;
702             int nextTextPos;
703             int w, newH;
704             int left, right;
705             int x, maxW;
706             int thisLineCentered = centered;
707             bool changeLine;
708
709             left = LEFT_MARGIN;
710             right = clientSize.w - RIGHT_MARGIN;
711
712             for(object = leftObjects.last; object; object = nextObject)
713             {
714                nextObject = object.prev;
715                if(y < object.untilY || object.next)
716                   left += object.w;
717                else
718                   leftObjects.Delete(object);
719             }
720             for(object = rightObjects.last; object; object = nextObject)
721             {
722                nextObject = object.prev;
723                if(y < object.untilY || object.next)
724                   right -= object.w;
725                else
726                   rightObjects.Delete(object);
727             }
728             right = Max(left, right);
729             maxW = right - left;
730
731             newH = ComputeLine(surface, block, textPos, &nextBlock, &nextTextPos, &centered, &w, maxW, maxH - y, RenderFlags {}, y, &leftObjects, &rightObjects, &changeLine, true, 0 /*y*/, LEFT_MARGIN);
732
733             if(thisLineCentered)
734                x = Max(left,(left + right - w) / 2);
735             else
736                x = left;
737
738             surface.font = font;
739
740             PositionLine(this, surface, x - scroll.x, y - scroll.y,
741                maxW, newH, block, textPos, nextBlock, nextTextPos,
742                left - scroll.x, right - scroll.x);
743
744             if(changeLine)
745                y += newH;
746             block = nextBlock;
747             textPos = nextTextPos;
748          }
749          delete surface;
750       }
751    }
752
753    void Open(char * location, char * firstReferer)
754    {
755       HTTPFile f { /*reuseConnection = false*/ };
756       char referer[MAX_LOCATION] = "";
757       char relocation[MAX_LOCATION];
758       bool opened = false;
759
760       strcpy(relocation, location);
761
762       // PrintLn("\n\n\nOpening new location: ", location, "\n");
763
764       if(strstr(location, "http://") != location && strstr(location, "https://") != location)
765       {
766          if(!FileExists(location))
767          {
768             strcpy(relocation, "http://");
769             strcat(relocation, location);
770          }
771          else
772          {
773             delete f;
774             f = (HTTPFile)FileOpen(location, read);
775             if(f)
776                opened = true;
777          }
778       }
779       clickedLink = null;
780       overLink = null;
781       overBlock = null;
782
783       if(firstReferer)
784          strcpy(referer, firstReferer);
785
786       delete html;
787       html = HTMLFile {};
788
789       SetScrollArea(0,0,0);
790       Update(null);
791
792       while(!opened)
793       {
794          char path[MAX_LOCATION];
795
796          if(this.location)
797             delete this.location;
798          this.location = CopyString(relocation);
799          fileName = this.location;
800          ((GuiApplication)__thisModule).UpdateDisplay();
801
802          if(!(opened = f.OpenURL(this.location, referer, relocation)) && !strcmp(this.location, relocation))
803             break;
804
805          if(!opened)
806          {
807             //printf("Relocated to %s\n", relocation);
808          }
809          strcpy(referer, this.location);
810
811          // Fix up relocation relative paths
812          strcpy(path, this.location);
813          if(path[strlen(path)-1] != '/')
814             PathCat(path, "..");
815          if(relocation[0] == '/' && relocation[1] == '/')
816          {
817             strcpy(path, "http:");
818             strcat(path, relocation);
819          }
820          else
821             PathCat(path, relocation);
822          strcpy(relocation, path);
823       }
824       if(this.location)
825          delete this.location;
826       this.location = CopyString(relocation);
827       fileName = this.location;
828
829       if(opened)
830       {
831          bool isHTTP = eClass_IsDerived(f._class, class(HTTPFile));
832          void * previous = null;
833          bool isImage = false;
834          bool isPlain = isHTTP ? false : true;
835
836          // Handle known types
837          if(isHTTP && f.contentType)
838          {
839             if(strstr(f.contentType, "image/") == f.contentType)
840                isImage = true;
841             else if(strstr(f.contentType, "text/") == f.contentType && strnicmp(f.contentType + 5, "html", 4))
842                isPlain = true;
843          }
844          else
845          {
846             const String imageExt[] = { "jpg", "jpeg", "bmp", "pcx", "png", "gif" };
847             const String htmlExt[] = { "html", "htm", "php" };
848             const String textExt[] = { "c", "h", "ec", "eh", "epj", "cpp", "cxx", "cc", "hpp", "hxx", "hh", "m", "java", "cs", "py", "Makefile", "mk", "cf" };
849             char ext[MAX_EXTENSION];
850             int i;
851             GetExtension(fileName, ext);
852             for(i = 0; i < sizeof(imageExt) / sizeof(imageExt[0]); i++)
853                if(!strcmpi(ext, imageExt[i]))
854                {
855                   isImage = true;
856                   break;
857                }
858
859             for(i = 0; i < sizeof(htmlExt) / sizeof(htmlExt[0]); i++)
860                if(!strcmpi(ext, htmlExt[i]))
861                {
862                   isPlain = false;
863                   break;
864                }
865
866             for(i = 0; i < sizeof(textExt) / sizeof(textExt[0]); i++)
867                if(!strcmpi(ext, textExt[i]))
868                {
869                   isPlain = true;
870                   break;
871                }
872          }
873
874          if(isImage)
875          {
876             Block subBlock;
877             html.body = HTMLFile::AddBlock(html.block, BODY);
878
879             subBlock = HTMLFile::AddBlock(html.body, IMAGE);
880             subBlock.valign = bottom;
881             subBlock.halign = middle;
882             subBlock.src = CopyString(fileName);
883             html.defaultFont.type = FONT;
884             html.defaultFont.face = CopyString("Times New Roman");
885          }
886          else if(isPlain)
887          {
888             uint size;
889             TempFile tmp { };
890             Block subBlock;
891             char * text;
892             int len;
893             String cd = eClass_IsDerived(f._class, class(HTTPFile)) ? f.contentDisposition : null;
894
895             String tmpPath = PrintString("File://", (uintptr)tmp);
896             f.CopyTo(tmpPath);
897             size = tmp.GetSize();
898             tmp.Seek(0, start);
899
900             html.defaultFont.type = FONT;
901             html.defaultFont.face = CopyString("Courier New");
902
903             if(cd)
904             {
905                char fn[MAX_LOCATION];
906                while(GetKeyWordEx(&cd, fn, sizeof(fn), true, false))
907                {
908                   if(!strcmp(fn, "filename") && GetKeyWordEx(&cd, fn, sizeof(fn), true, true))
909                   {
910                      html.titleBlock = HTMLFile::AddBlock(html.block, TITLE);
911                      subBlock = HTMLFile::AddBlock(html.titleBlock, TEXT);
912                      subBlock.text = CopyString(fn);
913                      subBlock.textLen = strlen(fn);
914                   }
915                }
916             }
917
918             html.body = HTMLFile::AddBlock(html.block, BODY);
919             html.block = html.body;
920
921             text = new char[size + 1];
922             len = tmp.Read(text, 1, size);
923             text[len] = 0;
924
925             {
926                int c;
927                char ch;
928                int start = 0;
929                Block textBlock = HTMLFile::AddBlock(html.block, TEXT);
930
931                for(c = 0; ; c++)
932                {
933                   ch = text[c];
934                   if(ch == '\n' || ch == '\r' || !ch)
935                   {
936                      int len = c - start;
937                      textBlock.text = renew textBlock.text char[textBlock.textLen + 1 + len];
938                      memmove(textBlock.text + len, textBlock.text, textBlock.textLen + 1);
939                      memcpy(textBlock.text, text + start, len);
940                      textBlock.textLen += len;
941                      if(!ch) break;
942                      {
943                         Block block { type = BR, parent = textBlock.parent };
944                         Block newBlock { type = TEXT, parent = textBlock.parent };
945
946                         textBlock.parent.subBlocks.Insert(textBlock, block);
947                         textBlock.parent.subBlocks.Insert(block, newBlock);
948
949                         newBlock.textLen = 0;
950                         newBlock.text = new char[1];
951                         newBlock.text[0] = 0;
952
953                         textBlock = newBlock;
954                      }
955                      if(ch == '\r' && text[c+1] == '\n') c++;
956                      start = c + 1;
957                   }
958                }
959
960                html.block = html.block.parent;
961                delete text;
962             }
963             delete tmp;
964             delete tmpPath;
965
966          }
967          else
968          {
969             html.Parse(f);
970             if(html.baseHRef)
971             {
972                delete this.location;
973                this.location = CopyString(html.baseHRef);
974             }
975          }
976          LoadGraphics(html.defaultFont, &previous);
977          html.block.font = html.defaultFont.font;
978          LoadGraphics(html.block, &previous);
979          CreateForms(html.block);
980
981          ComputeMinSizes();
982          ComputeSizes();
983          PositionForms();
984          Update(null);
985
986          /*
987          {
988             File f = FileOpen("debug2.txt", write);
989             if(f)
990             {
991                WriteBlock(f, html.body);
992                delete f;
993             }
994          }
995          */
996
997       }
998       ((GuiApplication)__thisModule.application).Unlock();
999       // PrintLn("At position ", f.Tell(), " for ", fileName);
1000       delete f;
1001       ((GuiApplication)__thisModule.application).Lock();
1002       NotifyPageOpened(master);
1003    }
1004
1005    void OpenFile(File f, char * firstReferer)
1006    {
1007       char referer[MAX_LOCATION] = "";
1008       bool opened = false;
1009
1010       clickedLink = null;
1011       overLink = null;
1012       overBlock = null;
1013
1014       if(firstReferer)
1015          strcpy(referer, firstReferer);
1016
1017       delete html;
1018       html = HTMLFile {};
1019
1020       SetScrollArea(0,0,0);
1021       Update(null);
1022
1023       opened = true;
1024       this.location = null;
1025       fileName = this.location;
1026
1027       if(opened)
1028       {
1029          void * previous = null;
1030          html.Parse(f);
1031          LoadGraphics(html.defaultFont, &previous);
1032          html.block.font = html.defaultFont.font;
1033          LoadGraphics(html.block, &previous);
1034          CreateForms(html.block);
1035
1036          ComputeMinSizes();
1037          ComputeSizes();
1038          PositionForms();
1039          /*
1040          {
1041             File f = FileOpen("debug2.txt", write);
1042             if(f)
1043             {
1044                WriteBlock(f, html.body);
1045                delete f;
1046             }
1047          }
1048          */
1049       }
1050       NotifyPageOpened(master);
1051    }
1052
1053    bool ScrollToAnchor(Block block, const char * anchor)
1054    {
1055       bool result = false;
1056       if(block.type == ANCHOR && block.anchor && !strcmpi(block.anchor, anchor))
1057       {
1058          SetScrollPosition(0, block.startY);
1059          result = true;
1060       }
1061       else
1062       {
1063          Block subBlock;
1064          for(subBlock = block.subBlocks.first; subBlock; subBlock = subBlock.next)
1065          {
1066             if((result = ScrollToAnchor(subBlock, anchor)))
1067                break;
1068          }
1069       }
1070       return result;
1071    }
1072
1073    bool GoToAnchor(const char * anchor)
1074    {
1075       return anchor ? ScrollToAnchor(html.block, anchor) : false;
1076    }
1077
1078    virtual bool Window::NotifyPageOpened();
1079
1080    void OnDestroy()
1081    {
1082       objectThread1.objectThreadTerminate = true;
1083       objectThread2.objectThreadTerminate = true;
1084       objectThread3.objectThreadTerminate = true;
1085       objectThread4.objectThreadTerminate = true;
1086       ((GuiApplication)__thisModule).Unlock();
1087       objectThread1.objectThreadSemaphore.Release();
1088       objectThread2.objectThreadSemaphore.Release();
1089       objectThread3.objectThreadSemaphore.Release();
1090       objectThread4.objectThreadSemaphore.Release();
1091
1092
1093       while(!objectThread1.objectThreadDead ||
1094             !objectThread2.objectThreadDead ||
1095             !objectThread3.objectThreadDead ||
1096             !objectThread4.objectThreadDead)
1097       {
1098          // ((GuiApplication)__thisModule).ProcessNetworkEvents();
1099          Sleep(0.01);
1100       }
1101
1102       //objectThread.Wait();
1103       objectRequests.Free(null);
1104
1105       ((GuiApplication)__thisModule).Lock();
1106    }
1107
1108    bool OnCreate()
1109    {
1110       browserWindow = this;
1111       objectThread1.objectThreadTerminate = false;
1112       objectThread2.objectThreadTerminate = false;
1113       objectThread3.objectThreadTerminate = false;
1114       objectThread4.objectThreadTerminate = false;
1115
1116       objectThread1.Create();
1117       objectThread2.Create();
1118       objectThread3.Create();
1119       objectThread4.Create();
1120
1121       /*
1122       if(location)
1123       {
1124          location = CopyString(location);
1125       }
1126       */
1127       return true;
1128    }
1129
1130    bool OnPostCreate()
1131    {
1132       if(location)
1133          Open(location, null);
1134       return true;
1135    }
1136
1137    void OnResize(int width, int height)
1138    {
1139       if(html.body)
1140       {
1141          ComputeMinSizes();
1142          ComputeSizes();
1143          PositionForms();
1144       }
1145    }
1146
1147    // For text selection
1148    Block textBlock, selBlock;
1149    int curPosition, selPosition;
1150    bool isSelected; // Persistent state changed by RenderLine
1151
1152    void OnRedraw(Surface surface)
1153    {
1154       Block block = html.body;
1155       int y = TOP_MARGIN;
1156       int textPos = 0;
1157       int centered = 0;
1158       int maxH = clientSize.h - BOTTOM_MARGIN;
1159       OldList leftObjects { };
1160       OldList rightObjects { };
1161       Font font;
1162
1163       AlignedObject object, nextObject;
1164
1165       surface.SetBackground(html.background);
1166       if(html.background.a < 255)
1167          surface.Area(0,0,clientSize.w,clientSize.h);
1168       else
1169          surface.Clear(colorBuffer);
1170       if(html.defaultFont.font) // TOFIX: Null! (No font set?)
1171          surface.TextFont(html.defaultFont.font.font);
1172       surface.SetForeground(html.defaultFont.textColor);
1173       isSelected = false;
1174
1175       for(;block;)
1176       {
1177          Block nextBlock;
1178          int nextTextPos;
1179          int w, newH;
1180          int left, right;
1181          int x, maxW;
1182          int thisLineCentered = centered;
1183          bool changeLine;
1184
1185          left = LEFT_MARGIN;
1186          right = clientSize.w - RIGHT_MARGIN;
1187
1188          for(object = leftObjects.last; object; object = nextObject)
1189          {
1190             nextObject = object.prev;
1191             if(y < object.untilY || object.next)
1192                left += object.w;
1193             else
1194                leftObjects.Delete(object);
1195          }
1196          for(object = rightObjects.last; object; object = nextObject)
1197          {
1198             nextObject = object.prev;
1199             if(y < object.untilY || object.next)
1200                right -= object.w;
1201             else
1202                rightObjects.Delete(object);
1203          }
1204          right = Max(left, right);
1205          maxW = right - left;
1206
1207          font = surface.font;
1208          newH = ComputeLine(surface, block, textPos, &nextBlock, &nextTextPos, &centered, &w, maxW, maxH - y, RenderFlags {}, y, &leftObjects, &rightObjects, &changeLine, false, 0, 0);
1209          surface.font = font;
1210          if(thisLineCentered)
1211             x = Max(left,(left + right - w) / 2);
1212          else
1213             x = left;
1214
1215          surface.TextFont(font);
1216
1217          RenderLine(this, surface, x - scroll.x, y - scroll.y, maxW, newH, block, textPos, nextBlock, nextTextPos, left - scroll.x, right - scroll.x);
1218
1219          if(changeLine)
1220             y += newH;
1221          block = nextBlock;
1222          textPos = nextTextPos;
1223       }
1224    }
1225
1226    void OnVScroll(ScrollBarAction action, int position, Key key)
1227    {
1228       Update(null);
1229    }
1230
1231    bool OnKeyHit(Key key, unichar character)
1232    {
1233       switch(key)
1234       {
1235          case shiftTab:
1236             CycleChildren(true, false, false, true);
1237             return false;
1238          case tab:
1239             CycleChildren(false, false, false, true);
1240             return false;
1241          case left: case right:
1242             horzScroll.OnKeyHit(key, character);
1243             break;
1244          case down: case up: case pageDown: case pageUp:
1245             vertScroll.OnKeyHit(key, character);
1246             break;
1247       }
1248       return true;
1249    }
1250
1251    bool OnKeyDown(Key key, unichar character)
1252    {
1253       switch(key)
1254       {
1255          case ctrlHome:
1256          {
1257             vertScroll.OnKeyDown(home, character);
1258             horzScroll.OnKeyDown(home, character);
1259             break;
1260          }
1261          case ctrlEnd:
1262          {
1263             vertScroll.OnKeyDown(end, character);
1264             horzScroll.OnKeyDown(end, character);
1265             break;
1266          }
1267       }
1268       return true;
1269    }
1270
1271    void OnHScroll(ScrollBarAction action, int position, Key key)
1272    {
1273       Update(null);
1274    }
1275
1276    bool PickHTML(int pickX, int pickY, Block* pickBlock, int * pickTextPos)
1277    {
1278       bool result = false;
1279       Block block = html.body;
1280       int y = TOP_MARGIN;
1281       int textPos = 0;
1282       int centered = 0;
1283       int maxH = clientSize.h - BOTTOM_MARGIN;
1284       OldList leftObjects { };
1285       OldList rightObjects { };
1286       AlignedObject object, nextObject;
1287
1288       Surface surface = display.GetSurface(0,0,null);
1289       if(surface)
1290       {
1291          if(html.defaultFont.font) // TOFIX: Null! (No font set?)
1292             surface.TextFont(html.defaultFont.font.font);
1293          for(;block;)
1294          {
1295             Block nextBlock;
1296             int nextTextPos;
1297             int w, newH;
1298             int left, right;
1299             int x, maxW;
1300             int thisLineCentered = centered;
1301             Font font = surface.font;
1302             bool changeLine;
1303
1304             left = LEFT_MARGIN;
1305             right = clientSize.w - RIGHT_MARGIN;
1306
1307             for(object = leftObjects.last; object; object = nextObject)
1308             {
1309                nextObject = object.prev;
1310                if(y < object.untilY || object.next)
1311                   left += object.w;
1312                else
1313                   leftObjects.Delete(object);
1314             }
1315             for(object = rightObjects.last; object; object = nextObject)
1316             {
1317                nextObject = object.prev;
1318                if(y < object.untilY || object.next)
1319                   right -= object.w;
1320                else
1321                   rightObjects.Delete(object);
1322             }
1323             right = Max(left, right);
1324             maxW = right - left;
1325
1326             newH = ComputeLine(surface, block, textPos, &nextBlock, &nextTextPos, &centered, &w, maxW, maxH - y, RenderFlags {}, y, &leftObjects, &rightObjects, &changeLine, false, 0, 0);
1327             if(thisLineCentered)
1328                x = Max(left,(left + right - w) / 2);
1329             else
1330                x = left;
1331
1332             surface.font = font;
1333
1334             if(PickLine(this, surface, x - scroll.x, y - scroll.y,
1335                maxW, newH, block, textPos, nextBlock, nextTextPos,
1336                left - scroll.x, right - scroll.x, pickX, pickY, pickBlock, pickTextPos))
1337             {
1338                result = true;
1339                break;
1340             }
1341
1342             if(changeLine)
1343                y += newH;
1344
1345             block = nextBlock;
1346             textPos = nextTextPos;
1347          }
1348          delete surface;
1349       }
1350
1351       for(object = leftObjects.last; object; object = nextObject)
1352       {
1353          nextObject = object.prev;
1354          leftObjects.Delete(object);
1355       }
1356       for(object = rightObjects.last; object; object = nextObject)
1357       {
1358          nextObject = object.prev;
1359          rightObjects.Delete(object);
1360       }
1361       return result;
1362    }
1363
1364    bool OnMouseMove(int x, int y, Modifiers mods)
1365    {
1366       Block pickBlock = null;
1367       Block linkBlock = null;
1368       int pickTextPos = 0;
1369
1370       if(!mods.isSideEffect)
1371       {
1372          PickHTML(x,y, &pickBlock, &pickTextPos);
1373
1374          if(pickBlock)
1375          {
1376             for(linkBlock = pickBlock; linkBlock; linkBlock = linkBlock.parent)
1377             {
1378                if(linkBlock.type == ANCHOR)
1379                   break;
1380             }
1381          }
1382          if(linkBlock)
1383          {
1384             if(linkBlock.href && strstr(linkBlock.href, "edit://") == linkBlock.href)
1385                cursor = ((GuiApplication)__thisModule).GetCursor(iBeam);
1386             else
1387                cursor = ((GuiApplication)__thisModule).GetCursor(hand);
1388             overLink = linkBlock;
1389          }
1390          else
1391          {
1392             if(pickBlock && pickBlock.type == TEXT)
1393             {
1394                cursor = ((GuiApplication)__thisModule).GetCursor(iBeam);
1395             }
1396             else
1397             {
1398                cursor = ((GuiApplication)__thisModule).GetCursor(arrow); //null);
1399             }
1400             overLink = null;
1401          }
1402          overBlock = pickBlock;
1403          overPos = pickTextPos;
1404       }
1405       return true;
1406    }
1407
1408    bool OnLeftButtonDown(int x, int y, Modifiers mods)
1409    {
1410       OnMouseMove(x, y, mods);
1411       if(overLink)
1412       {
1413          clickedLink = overLink;
1414
1415          Capture();
1416       }
1417       return true;
1418    }
1419
1420    virtual bool OnOpen(char * href)
1421    {
1422       char newLocation[MAX_LOCATION];
1423
1424       strcpy(newLocation, location ? location : "");
1425       if(newLocation[strlen(newLocation)-1] != '/')
1426          PathCat(newLocation, "..");
1427       if(href[0] == '/' && href[1] == '/')
1428       {
1429          strcpy(newLocation, "http:");
1430          strcat(newLocation, href);
1431       }
1432       else
1433          PathCat(newLocation, href);
1434
1435       Open(newLocation, null);
1436       return true;
1437    }
1438
1439    bool OnLeftButtonUp(int x, int y, Modifiers mods)
1440    {
1441       if(clickedLink)
1442       {
1443          ReleaseCapture();
1444          if(clickedLink == overLink && clickedLink.href)
1445          {
1446             if(OnOpen(clickedLink.href))
1447                Update(null);
1448          }
1449       }
1450       return true;
1451    }
1452
1453    void AddFormControls(char * location, Block block)
1454    {
1455       Block child;
1456       if(block.type == INPUT)
1457       {
1458          switch(block.inputType)
1459          {
1460             case radio:
1461                break;
1462             case text:
1463             {
1464                int c;
1465                int len;
1466                const char * text;
1467
1468                if(location[strlen(location)-1] != '?')
1469                {
1470                   strcat(location, "&");
1471                }
1472                strcat(location, block.name);
1473                strcat(location, "=");
1474
1475                len = strlen(location);
1476
1477                text = ((EditBox)block.window).contents;
1478
1479                for(c = 0; text[c]; c++)
1480                {
1481                   if(text[c] == ' ')
1482                      location[len++] = '+';
1483                   else
1484                   {
1485                      location[len++] = text[c];
1486                   }
1487                }
1488                location[len] = '\0';
1489                break;
1490             }
1491          }
1492       }
1493
1494       for(child = block.subBlocks.first; child; child = child.next)
1495       {
1496          AddFormControls(location, child);
1497       }
1498    }
1499
1500    bool ButtonClicked(Button button, int x, int y, Modifiers mods)
1501    {
1502       Block block = (Block)button.id;
1503       if(block.inputType == submit)
1504       {
1505          Block formBlock;
1506
1507          for(formBlock = block; formBlock; formBlock = formBlock.parent)
1508             if(formBlock.type == FORM)
1509                break;
1510
1511          if(formBlock && formBlock.action)
1512          {
1513             char newLocation[MAX_LOCATION];
1514
1515             strcpy(newLocation, location);
1516             if(newLocation[strlen(newLocation)-1] != '/')
1517                PathCat(newLocation, "..");
1518
1519             if(formBlock.action[0] == '/' && formBlock.action[1] == '/')
1520             {
1521                strcpy(newLocation, "http:");
1522                strcat(newLocation, formBlock.action);
1523             }
1524             else
1525                PathCat(newLocation, formBlock.action);
1526             strcat(newLocation, "?");
1527
1528             if(block.name)
1529             {
1530                strcat(newLocation, block.name);
1531                strcat(newLocation, "=");
1532             }
1533
1534             AddFormControls(newLocation, formBlock);
1535
1536             Open(newLocation, null);
1537             Update(null);
1538          }
1539       }
1540       return true;
1541    }
1542
1543    HTMLView()
1544    {
1545       //tabCycle = true;
1546       html.background = white;
1547    }
1548
1549    ~HTMLView()
1550    {
1551       delete this.location;
1552
1553       html.block.ClearEntries();
1554    }
1555
1556    property char * location
1557    {
1558       set
1559       {
1560          if(location)
1561             delete location;
1562          location = CopyString(value);
1563          if(created)
1564             Open(location, null);
1565       }
1566       get { return location; }
1567    }
1568
1569    property String title
1570    {
1571       get
1572       {
1573          String title = html.title;
1574          return title ? title : location;
1575       }
1576    }
1577 }