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