f763f161cee91341827ec8d073ff90586f67b4f0
[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          surface.TextFont(html.defaultFont.font.font);
383          while(block)
384          {
385             Block nextBlock;
386             int nextTextPos;
387             int w, newH;
388             int maxW = width - (LEFT_MARGIN + RIGHT_MARGIN);
389             bool changeLine;
390
391             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);
392             if(changeLine)
393             {
394                y += newH;
395                h = 0;
396             }
397             block = nextBlock;
398             textPos = nextTextPos;
399
400             totalWidth = Max(totalWidth, w);
401          }
402          delete surface;
403
404          totalHeight = y + BOTTOM_MARGIN;
405          totalWidth += LEFT_MARGIN + RIGHT_MARGIN;
406
407          SetScrollArea(totalWidth, totalHeight, false);
408          SetScrollArea(totalWidth, totalHeight, false);
409       }
410       
411       if(!initSize.w || !initSize.h)
412          clientSize = Size {!initSize.w ? totalWidth : clientSize.w, !initSize.h ? totalHeight : clientSize.h };
413    }
414
415    void CreateForms(Block block)
416    {
417       Block child;
418
419       if(block.type == INPUT)
420       {
421          switch(block.inputType)
422          {
423             case submit:
424                block.window = Button { this, text = block.value, position = Point { 10, 50 }, id = (int64)block, NotifyClicked = ButtonClicked, isDefault = true };
425                eInstance_IncRef(block.window);
426                block.window.Create();
427                block.window.cursor = ((GuiApplication)__thisModule).GetCursor(arrow);
428                //if(!html.defaultButton) html.defaultButton = block.window;
429                break;
430             case radio:
431                block.window = Button { this, isRadio = true, position = Point { 10, 100 }, id = (int64)block, NotifyClicked = ButtonClicked };
432                eInstance_IncRef(block.window);
433                block.window.Create();
434                block.window.cursor = ((GuiApplication)__thisModule).GetCursor(arrow);
435                break;
436             case text:
437                block.window = EditBox { this, position = Point { 10, 20 }, id = (int64)block };
438                eInstance_IncRef(block.window);
439                block.window.Create();
440                break;
441          }
442          /*if(!activeChild && block.window)
443             block.window.Activate();*/
444       }
445
446       for(child = block.subBlocks.last; child; child = child.prev)
447          CreateForms(child);
448    }
449
450    void LoadGraphics(Block block, void **previous)
451    {
452       Block child;
453
454       if(block.src && (block.type == IMAGE || block.type == TD || block.type == TABLE))
455       {
456          char path[MAX_LOCATION];
457          ImageEntry entry;
458
459          strcpy(path, location ? location : "");
460          if(location && path[strlen(path)-1] != '/')
461             PathCat(path, "..");
462          if(block.src[0] == '/' && block.src[1] == '/')
463          {
464             strcpy(path, "http:");
465             strcat(path, block.src);
466          }
467          else
468             PathCat(path, block.src);
469
470          for(entry = imageCache.first; entry; entry = entry.next)
471             if(!strcmp(entry.src, path))
472                break;
473
474          if(!entry)
475          {
476             entry = ImageEntry { src = CopyString(path), referer = CopyString(location) };
477             imageCache.Add(entry);
478          }
479
480          block.imageEntry = entry;
481          block.entryPtr = OldLink { data = &block.bitmap };
482          entry.bitmapPtrs.Add(block.entryPtr);
483
484          if(entry.bitmap)
485             block.bitmap = entry.bitmap;
486          else
487          {
488             if(path && strstr(path, "http://") == path)
489             {
490                RequestLink request;
491                objectsMutex.Wait();
492                for(request = objectRequests.first; request; request = (RequestLink)request.next)
493                {
494                   if(request.data == entry) 
495                      break;
496                }
497                if(!request)
498                {
499                   request = RequestLink { data = entry };
500                   objectRequests.Insert(*previous, request);
501                   *previous = request;
502                   objectThread1.objectThreadSemaphore.Release();
503                   objectThread2.objectThreadSemaphore.Release();
504                   objectThread3.objectThreadSemaphore.Release();
505                   objectThread4.objectThreadSemaphore.Release();
506                }
507                else if(*previous != request)
508                {
509                   objectRequests.Move(request, *previous);
510                   *previous = request;
511                }
512                objectsMutex.Release();
513             }
514             else
515             {
516                // ADDED THIS TO LOAD LOCAL FILES RIGHT HERE...
517                OldLink bitmapPtr;
518                File file = FileOpen(path, read);
519                if(!file)
520                   entry.missing = true;
521                else
522                {
523                   char extension[MAX_EXTENSION];
524                   entry.bitmap = Bitmap { alphaBlend = true };
525                   entry.bitmap.LoadFromFile(file, GetExtension(path, extension), null);
526                }
527                delete file;
528                for(bitmapPtr = entry.bitmapPtrs.first; bitmapPtr; bitmapPtr = bitmapPtr.next)
529                {
530                   *((Bitmap*) bitmapPtr.data) = entry.bitmap;
531                }
532                block.bitmap = entry.bitmap;
533             }
534          }
535          block.font = block.parent.font;
536       }
537       else if(block.type == FONT || block.type == ANCHOR)
538       {
539          FontEntry entry;
540          for(entry = fontCache.first; entry; entry = entry.next)
541          {
542             if(!strcmpi(entry.face, block.face) &&
543                entry.size == block.size &&
544                entry.attribs == block.attribs)
545             {
546                break;
547             }
548          }
549          if(!entry)
550          {
551             display.Lock(false);
552             entry = FontEntry { /*font = displaySystem.LoadFont(block.face, block.size, block.attribs), */size = block.size, attribs = block.attribs, face = CopyString(block.face) };
553             fontCache.Add(entry);
554             display.Unlock();
555          }
556
557          if(entry)
558          {
559             // ?
560             block.font = entry;
561          }
562       }
563       else if(block.parent)
564          block.font = block.parent.font;
565
566       for(child = block.subBlocks.first; child; child = child.next)
567          LoadGraphics(child, previous);
568    }
569
570    bool OnLoadGraphics()
571    {
572       ImageEntry entry;
573       FontEntry fEntry;
574       for(entry = imageCache.first; entry; entry = entry.next)
575       {
576          entry.bitmap.MakeDD(displaySystem);
577          if(entry.bitmap && entry.bitmap.alphaBlend)
578          {
579             entry.bitmap.Convert(null, pixelFormat888, null);
580             // entry.bitmap.displaySystem = displaySystem;
581          }
582       }
583       for(fEntry = fontCache.first; fEntry; fEntry = fEntry.next)
584          fEntry.font = browserWindow.displaySystem.LoadFont(fEntry.face, fEntry.size, fEntry.attribs);
585       return true;
586    }
587    
588    void OnUnloadGraphics()
589    {
590       FontEntry entry;
591       for(entry = fontCache.first; entry; entry = entry.next)
592       {
593          browserWindow.displaySystem.UnloadFont(entry.font);
594          entry.font = null;
595       }
596    }
597
598    void NormalizeSelection(Block * startBlock, int * startSel, Block * endBlock, int * endSel)
599    {
600       bool selAfter = false;
601       Block b;
602       for(b = selBlock; b; b = GetNextBlock(b))
603       {
604          if(b != selBlock && b == textBlock)
605          {
606             selAfter = true;
607             break;
608          }
609       }
610
611       if(textBlock == selBlock)
612       {
613          *startSel = Min(selPosition, curPosition);
614          *endSel = Max(selPosition, curPosition);
615          *startBlock = *endBlock = textBlock;
616       }
617       else if(!selAfter)
618       {
619          *startBlock = textBlock;
620          *startSel = curPosition;
621          *endSel = selPosition;
622          *endBlock = selBlock;
623       }
624       else
625       {
626          *startBlock = selBlock;
627          *startSel = selPosition;
628          *endSel = curPosition;
629          *endBlock = textBlock;
630       }
631    }
632
633    void PositionForms()
634    {
635       Block block = html.body;
636       int y = TOP_MARGIN;
637       int textPos = 0;
638       int centered = 0;
639       int maxH = clientSize.h - BOTTOM_MARGIN;
640       OldList leftObjects { };
641       OldList rightObjects { };
642       AlignedObject object, nextObject;
643       int h = 0;
644
645       Surface surface = display.GetSurface(0,0,null);
646       if(surface)
647       {
648          surface.TextFont(html.defaultFont.font.font);
649          for(;block;)
650          {
651             Block nextBlock;
652             int nextTextPos;
653             int w, newH;
654             int left, right;
655             int x, maxW;
656             int thisLineCentered = centered;
657             Font font = surface.GetFont();
658             bool changeLine;
659
660             left = LEFT_MARGIN;
661             right = clientSize.w - RIGHT_MARGIN;
662
663             for(object = leftObjects.last; object; object = nextObject)
664             {
665                nextObject = object.prev;
666                if(y < object.untilY || object.next)
667                   left += object.w;
668                else
669                   leftObjects.Delete(object);
670             }
671             for(object = rightObjects.last; object; object = nextObject)
672             {
673                nextObject = object.prev;
674                if(y < object.untilY || object.next)
675                   right -= object.w;
676                else
677                   rightObjects.Delete(object);
678             }
679             right = Max(left, right);
680             maxW = right - left;
681
682             newH = ComputeLine(surface, block, textPos, &nextBlock, &nextTextPos, &centered, &w, maxW, maxH - y, RenderFlags {}, y, &leftObjects, &rightObjects, &changeLine, true, 0 /*y*/, LEFT_MARGIN);
683             if(thisLineCentered)
684                x = Max(left,(left + right - w) / 2);
685             else
686                x = left;
687
688             surface.TextFont(font);
689
690             PositionLine(this, surface, x - scroll.x, y - scroll.y,
691                maxW, newH, block, textPos, nextBlock, nextTextPos, 
692                left - scroll.x, right - scroll.x);
693
694             if(changeLine)
695             {
696                y += newH;
697                h = 0;
698             }
699             block = nextBlock;
700             textPos = nextTextPos;
701          }
702          delete surface;
703       }
704    }
705
706    void Open(char * location, char * firstReferer)
707    {
708       HTTPFile f {};
709       char referer[MAX_LOCATION] = "";
710       char relocation[MAX_LOCATION];
711       bool opened = false;
712
713       strcpy(relocation, location);
714
715       if(strstr(location, "http://") != location)
716       {
717          if(!FileExists(location))
718          {
719             strcpy(relocation, "http://");
720             strcat(relocation, location);
721          }
722          else
723          {
724             delete f;
725             f = (HTTPFile)FileOpen(location, read);
726             if(f)
727                opened = true;
728          }
729       }
730       clickedLink = null;
731       overLink = null;
732       overBlock = null;
733
734       if(firstReferer)
735          strcpy(referer, firstReferer);
736
737       delete html;
738       html = HTMLFile {};
739
740       SetScrollArea(0,0,0);
741       Update(null);
742
743       while(!opened)
744       {
745          char path[MAX_LOCATION];
746          
747          if(this.location)
748             delete this.location;
749          this.location = CopyString(relocation);
750          fileName = this.location;
751          ((GuiApplication)__thisModule).UpdateDisplay();
752
753          if(!(opened = f.OpenURL(this.location, referer, relocation)) && !strcmp(this.location, relocation))
754             break;
755
756          if(!opened)
757          {
758             //printf("Relocated to %s\n", relocation);
759          }
760          strcpy(referer, this.location);
761
762          // Fix up relocation relative paths
763          strcpy(path, this.location);
764          if(path[strlen(path)-1] != '/')
765             PathCat(path, "..");
766          if(relocation[0] == '/' && relocation[1] == '/')
767          {
768             strcpy(path, "http:");
769             strcat(path, relocation);
770          }
771          else
772             PathCat(path, relocation);
773          strcpy(relocation, path);
774       }
775       if(this.location)
776          delete this.location;
777       this.location = CopyString(relocation);
778       fileName = this.location;
779
780       if(opened)
781       {
782          void * previous = null;
783          html.Parse(f);
784          LoadGraphics(html.defaultFont, &previous);
785          LoadGraphics(html.block, &previous);
786          CreateForms(html.block);
787
788          ComputeMinSizes();
789          ComputeSizes();
790          PositionForms();
791          Update(null);
792
793          /*
794          {
795             File f = FileOpen("debug2.txt", write);
796             if(f)
797             {
798                WriteBlock(f, html.body);
799                delete f;
800             }
801          }
802          */
803          
804       }
805       delete f;
806       NotifyPageOpened(master);
807    }
808
809    void OpenFile(File f, char * firstReferer)
810    {
811       char referer[MAX_LOCATION] = "";
812       char relocation[MAX_LOCATION];
813       bool opened = false;
814
815       clickedLink = null;
816       overLink = null;
817       overBlock = null;
818
819       if(firstReferer)
820          strcpy(referer, firstReferer);
821
822       delete html;
823       html = HTMLFile {};
824
825       SetScrollArea(0,0,0);
826       Update(null);
827
828       opened = true;
829       this.location = null;
830       fileName = this.location;
831
832       if(opened)
833       {
834          void * previous = null;
835          html.Parse(f);
836          LoadGraphics(html.defaultFont, &previous);
837          LoadGraphics(html.block, &previous);
838          CreateForms(html.block);
839
840          ComputeMinSizes();
841          ComputeSizes();
842          PositionForms();
843          /*
844          {
845             File f = FileOpen("debug2.txt", write);
846             if(f)
847             {
848                WriteBlock(f, html.body);
849                delete f;
850             }
851          }
852          */         
853       }
854       NotifyPageOpened(master);
855    }
856
857    bool ScrollToAnchor(Block block, char * anchor)
858    {
859       bool result = false;
860       if(block.type == ANCHOR && block.anchor && !strcmpi(block.anchor, anchor))
861       {
862          SetScrollPosition(0, block.startY);
863          result = true;
864       }
865       else
866       {
867          Block subBlock;
868          for(subBlock = block.subBlocks.first; subBlock; subBlock = subBlock.next)
869          {
870             if((result = ScrollToAnchor(subBlock, anchor)))
871                break;
872          }
873       }
874       return result;
875    }
876
877    bool GoToAnchor(char * anchor)
878    {
879       return anchor ? ScrollToAnchor(html.block, anchor) : false;
880    }
881
882    virtual bool Window::NotifyPageOpened();
883
884    void OnDestroy()
885    {
886       objectThread1.objectThreadTerminate = true;
887       objectThread2.objectThreadTerminate = true;
888       objectThread3.objectThreadTerminate = true;
889       objectThread4.objectThreadTerminate = true;
890       ((GuiApplication)__thisModule).Unlock();
891       objectThread1.objectThreadSemaphore.Release();
892       objectThread2.objectThreadSemaphore.Release();
893       objectThread3.objectThreadSemaphore.Release();
894       objectThread4.objectThreadSemaphore.Release();
895
896       
897       while(!objectThread1.objectThreadDead || 
898             !objectThread2.objectThreadDead ||
899             !objectThread3.objectThreadDead ||
900             !objectThread4.objectThreadDead)
901       {
902          // ((GuiApplication)__thisModule).ProcessNetworkEvents();
903          Sleep(0.01);
904       }
905
906       //objectThread.Wait();
907       objectRequests.Free(null);
908
909       ((GuiApplication)__thisModule).Lock();
910    }
911
912    bool OnCreate()
913    {
914       browserWindow = this;
915       objectThread1.objectThreadTerminate = false;
916       objectThread2.objectThreadTerminate = false;
917       objectThread3.objectThreadTerminate = false;
918       objectThread4.objectThreadTerminate = false;
919
920       objectThread1.Create();
921       objectThread2.Create();
922       objectThread3.Create();
923       objectThread4.Create();
924
925       /*
926       if(location)
927       {
928          location = CopyString(location);
929       }
930       */
931       return true;
932    }
933
934    bool OnPostCreate()
935    {
936       if(location)
937          Open(location, null);
938       return true;
939    }
940
941    void OnResize(int width, int height)
942    {
943       if(html.body)
944       {
945          ComputeMinSizes();
946          ComputeSizes();
947          PositionForms();
948       }
949    }
950
951    // For text selection
952    Block textBlock, selBlock;
953    int curPosition, selPosition;
954    bool isSelected; // Persistent state changed by RenderLine
955
956    void OnRedraw(Surface surface)
957    {
958       Block block = html.body;
959       int y = TOP_MARGIN;
960       int textPos = 0;
961       int centered = 0;
962       int maxH = clientSize.h - BOTTOM_MARGIN;
963       OldList leftObjects { };
964       OldList rightObjects { };
965
966       AlignedObject object, nextObject;
967       int h = 0;
968
969       surface.SetBackground(html.background);
970       if(html.background.a < 255)
971          surface.Area(0,0,clientSize.w,clientSize.h);
972       else
973          surface.Clear(colorBuffer);
974       if(html.defaultFont.font) // TOFIX: Null! (No font set?)
975          surface.TextFont(html.defaultFont.font.font);
976       surface.SetForeground(html.defaultFont.textColor);
977       isSelected = false;
978
979       for(;block;)
980       {
981          Block nextBlock;
982          int nextTextPos;
983          int w, newH;
984          int left, right;
985          int x, maxW;
986          int thisLineCentered = centered;
987          Font font = surface.GetFont();
988          bool changeLine;
989
990          left = LEFT_MARGIN;
991          right = clientSize.w - RIGHT_MARGIN;
992
993          for(object = leftObjects.last; object; object = nextObject)
994          {
995             nextObject = object.prev;
996             if(y < object.untilY || object.next)
997                left += object.w;
998             else
999                leftObjects.Delete(object);
1000          }
1001          for(object = rightObjects.last; object; object = nextObject)
1002          {
1003             nextObject = object.prev;
1004             if(y < object.untilY || object.next)
1005                right -= object.w;
1006             else
1007                rightObjects.Delete(object);
1008          }
1009          right = Max(left, right);
1010          maxW = right - left;
1011
1012          newH = ComputeLine(surface, block, textPos, &nextBlock, &nextTextPos, &centered, &w, maxW, maxH - y, RenderFlags {}, y, &leftObjects, &rightObjects, &changeLine, false, 0, 0);
1013          if(thisLineCentered)
1014             x = Max(left,(left + right - w) / 2);
1015          else
1016             x = left;
1017
1018          surface.TextFont(font);
1019
1020          //h = Max(h, newH);
1021          RenderLine(this, surface, x - scroll.x, y - scroll.y, maxW, newH, block, textPos, nextBlock, nextTextPos, left - scroll.x, right - scroll.x);
1022
1023          if(changeLine)
1024          {
1025             // y += h;
1026             y += newH;
1027             h = 0;
1028          }
1029          block = nextBlock;
1030          textPos = nextTextPos;
1031       }
1032       //y += h;
1033    }
1034
1035    void OnVScroll(ScrollBarAction action, int position, Key key)
1036    {
1037       Update(null);
1038    }
1039
1040    bool OnKeyHit(Key key, unichar character)
1041    {
1042       switch(key)
1043       {
1044          case shiftTab:
1045             CycleChildren(true, false, false, true);
1046             return false;
1047          case tab:
1048             CycleChildren(false, false, false, true);
1049             return false;
1050          case left: case right: 
1051             horzScroll.OnKeyHit(key, character);
1052             break;
1053          case down: case up: case pageDown: case pageUp:
1054             vertScroll.OnKeyHit(key, character);
1055             break;
1056       }   
1057       return true;
1058    }
1059
1060    bool OnKeyDown(Key key, unichar character)
1061    {
1062       switch(key)
1063       {
1064          case ctrlHome:
1065          {
1066             vertScroll.OnKeyDown(home, character);
1067             horzScroll.OnKeyDown(home, character);
1068             break;
1069          }
1070          case ctrlEnd:
1071          {
1072             vertScroll.OnKeyDown(end, character);
1073             horzScroll.OnKeyDown(end, character);
1074             break;
1075          }
1076       }   
1077       return true;
1078    }
1079
1080    void OnHScroll(ScrollBarAction action, int position, Key key)
1081    {
1082       Update(null);
1083    }
1084
1085    bool PickHTML(int pickX, int pickY, Block* pickBlock, int * pickTextPos)
1086    {
1087       bool result = false;
1088       Block block = html.body;
1089       int y = TOP_MARGIN;
1090       int textPos = 0;
1091       int centered = 0;
1092       int maxH = clientSize.h - BOTTOM_MARGIN;
1093       OldList leftObjects { };
1094       OldList rightObjects { };
1095       AlignedObject object, nextObject;
1096       int h = 0;
1097
1098       Surface surface = display.GetSurface(0,0,null);
1099       if(surface)
1100       {
1101          if(html.defaultFont.font) // TOFIX: Null! (No font set?)
1102             surface.TextFont(html.defaultFont.font.font);
1103          for(;block;)
1104          {
1105             Block nextBlock;
1106             int nextTextPos;
1107             int w, newH;
1108             int left, right;
1109             int x, maxW;
1110             int thisLineCentered = centered;
1111             Font font = surface.GetFont();
1112             bool changeLine;
1113
1114             left = LEFT_MARGIN;
1115             right = clientSize.w - RIGHT_MARGIN;
1116
1117             for(object = leftObjects.last; object; object = nextObject)
1118             {
1119                nextObject = object.prev;
1120                if(y < object.untilY || object.next)
1121                   left += object.w;
1122                else
1123                   leftObjects.Delete(object);
1124             }
1125             for(object = rightObjects.last; object; object = nextObject)
1126             {
1127                nextObject = object.prev;
1128                if(y < object.untilY || object.next)
1129                   right -= object.w;
1130                else
1131                   rightObjects.Delete(object);
1132             }
1133             right = Max(left, right);
1134             maxW = right - left;
1135
1136             newH = ComputeLine(surface, block, textPos, &nextBlock, &nextTextPos, &centered, &w, maxW, maxH - y, RenderFlags {}, y, &leftObjects, &rightObjects, &changeLine, false, 0, 0);
1137             if(thisLineCentered)
1138                x = Max(left,(left + right - w) / 2);
1139             else
1140                x = left;
1141
1142             surface.TextFont(font);
1143
1144             if(PickLine(this, surface, x - scroll.x, y - scroll.y, 
1145                maxW, newH, block, textPos, nextBlock, nextTextPos, 
1146                left - scroll.x, right - scroll.x, pickX, pickY, pickBlock, pickTextPos))
1147             {
1148                result = true;
1149                break;         
1150             }
1151
1152             if(changeLine)
1153             {
1154                y += newH;
1155                h = 0;
1156             }
1157             block = nextBlock;
1158             textPos = nextTextPos;
1159          }
1160          delete surface;
1161       }
1162       return result;
1163    }
1164
1165    bool OnMouseMove(int x, int y, Modifiers mods)
1166    {
1167       Block pickBlock = null;
1168       Block linkBlock = null;
1169       int pickTextPos = 0;
1170
1171       if(!mods.isSideEffect)
1172       {
1173          PickHTML(x,y, &pickBlock, &pickTextPos);
1174
1175          if(pickBlock)
1176          {
1177             for(linkBlock = pickBlock; linkBlock; linkBlock = linkBlock.parent)
1178             {
1179                if(linkBlock.type == ANCHOR)
1180                   break;
1181             }
1182          }
1183          if(linkBlock)
1184          {
1185             if(strstr(linkBlock.href, "edit://") == linkBlock.href)
1186                cursor = ((GuiApplication)__thisModule).GetCursor(iBeam);
1187             else
1188                cursor = ((GuiApplication)__thisModule).GetCursor(hand);
1189             overLink = linkBlock;
1190          }
1191          else
1192          {
1193             if(pickBlock && pickBlock.type == TEXT)
1194             {
1195                cursor = ((GuiApplication)__thisModule).GetCursor(iBeam);
1196             }
1197             else
1198             {
1199                cursor = ((GuiApplication)__thisModule).GetCursor(arrow); //null);
1200             }
1201             overLink = null;
1202          }
1203          overBlock = pickBlock;
1204          overPos = pickTextPos;
1205       }
1206       return true;
1207    }
1208
1209    bool OnLeftButtonDown(int x, int y, Modifiers mods)
1210    {
1211       OnMouseMove(x, y, mods);
1212       if(overLink)
1213       {
1214          clickedLink = overLink;
1215
1216          Capture();
1217       }
1218       return true;
1219    }
1220
1221    virtual bool OnOpen(char * href)
1222    {
1223       char newLocation[MAX_LOCATION];
1224
1225       strcpy(newLocation, location ? location : "");
1226       if(newLocation[strlen(newLocation)-1] != '/')
1227          PathCat(newLocation, "..");
1228       if(href[0] == '/' && href[1] == '/')
1229       {
1230          strcpy(newLocation, "http:");
1231          strcat(newLocation, href);
1232       }
1233       else
1234          PathCat(newLocation, href);
1235
1236       Open(newLocation, null);
1237       return true;
1238    }
1239    
1240    bool OnLeftButtonUp(int x, int y, Modifiers mods)
1241    {
1242       if(clickedLink)
1243       {
1244          ReleaseCapture();
1245          if(clickedLink == overLink && clickedLink.href)
1246          {
1247             if(OnOpen(clickedLink.href))
1248                Update(null);
1249          }
1250       }
1251       return true;
1252    }
1253
1254    void AddFormControls(char * location, Block block)
1255    {
1256       Block child;
1257       if(block.type == INPUT)
1258       {
1259          switch(block.inputType)
1260          {
1261             case radio:
1262                break;
1263             case text:
1264             {
1265                int c;
1266                int len;
1267                char * text;
1268
1269                if(location[strlen(location)-1] != '?') 
1270                {
1271                   strcat(location, "&");
1272                }
1273                strcat(location, block.name);
1274                strcat(location, "=");
1275
1276                len = strlen(location);
1277
1278                text = ((EditBox)block.window).contents;
1279
1280                for(c = 0; text[c]; c++)
1281                {
1282                   if(text[c] == ' ')
1283                      location[len++] = '+';
1284                   else
1285                   {
1286                      location[len++] = text[c];
1287                   }
1288                }
1289                location[len] = '\0';
1290                break;
1291             }
1292          }
1293       }
1294
1295       for(child = block.subBlocks.first; child; child = child.next)
1296       {
1297          AddFormControls(location, child);
1298       }
1299    }
1300
1301    bool ButtonClicked(Button button, int x, int y, Modifiers mods)
1302    {
1303       Block block = (Block)button.id;
1304       if(block.inputType == submit)
1305       {
1306          Block formBlock;
1307
1308          for(formBlock = block; formBlock; formBlock = formBlock.parent)
1309             if(formBlock.type == FORM)
1310                break;
1311
1312          if(formBlock && formBlock.action)
1313          {
1314             char newLocation[MAX_LOCATION];
1315
1316             strcpy(newLocation, location);
1317             if(newLocation[strlen(newLocation)-1] != '/')
1318                PathCat(newLocation, "..");
1319
1320             if(formBlock.action[0] == '/' && formBlock.action[1] == '/')
1321             {
1322                strcpy(newLocation, "http:");
1323                strcat(newLocation, formBlock.action);
1324             }
1325             else
1326                PathCat(newLocation, formBlock.action);
1327             strcat(newLocation, "?");
1328
1329             if(block.name)
1330             {
1331                strcat(newLocation, block.name);
1332                strcat(newLocation, "=");
1333             }
1334
1335             AddFormControls(newLocation, formBlock);
1336
1337             Open(newLocation, null);
1338             Update(null);
1339          }
1340       }
1341       return true;
1342    }
1343
1344    HTMLView()
1345    {
1346       //tabCycle = true;
1347       html.background = white;
1348    }
1349
1350    ~HTMLView()
1351    {
1352       delete this.location;
1353
1354       {
1355          ImageEntry entry;
1356          FontEntry fontEntry;
1357          while((entry = imageCache.first))
1358          {
1359             imageCache.Remove(entry);
1360             delete entry;
1361          }
1362          while((fontEntry = fontCache.first))
1363          {
1364             fontCache.Remove(fontEntry);
1365             delete fontEntry;
1366          }
1367          html.block.ClearEntries();
1368       }
1369    }
1370
1371    property char * location
1372    {
1373       set
1374       {
1375          if(location)
1376             delete location;
1377          location = CopyString(value);
1378          if(created)
1379             Open(location, null);
1380       }
1381       get { return location; }
1382    }
1383 }