user32: Change the desktop colour and pattern to match win2k.
[wine] / dlls / mshtml / txtrange.c
1 /*
2  * Copyright 2006-2007 Jacek Caban for CodeWeavers
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
17  */
18
19 #include "config.h"
20
21 #include <stdarg.h>
22 #include <stdio.h>
23
24 #define COBJMACROS
25
26 #include "windef.h"
27 #include "winbase.h"
28 #include "winuser.h"
29 #include "winnls.h"
30 #include "ole2.h"
31
32 #include "wine/debug.h"
33 #include "wine/unicode.h"
34
35 #include "mshtml_private.h"
36
37 WINE_DEFAULT_DEBUG_CHANNEL(mshtml);
38
39 static const WCHAR brW[] = {'b','r',0};
40
41 typedef struct {
42     const IHTMLTxtRangeVtbl *lpHTMLTxtRangeVtbl;
43
44     LONG ref;
45
46     nsIDOMRange *nsrange;
47     HTMLDocument *doc;
48
49     struct list entry;
50 } HTMLTxtRange;
51
52 #define HTMLTXTRANGE(x)  ((IHTMLTxtRange*)  &(x)->lpHTMLTxtRangeVtbl)
53
54 typedef struct {
55     WCHAR *buf;
56     DWORD len;
57     DWORD size;
58 } wstrbuf_t;
59
60 typedef struct {
61     PRUint16 type;
62     nsIDOMNode *node;
63     PRUint32 off;
64     nsAString str;
65     const PRUnichar *p;
66 } dompos_t;
67
68 typedef enum {
69     RU_UNKNOWN,
70     RU_CHAR,
71     RU_WORD,
72     RU_SENTENCE,
73     RU_TEXTEDIT
74 } range_unit_t;
75
76 static HTMLTxtRange *get_range_object(HTMLDocument *doc, IHTMLTxtRange *iface)
77 {
78     HTMLTxtRange *iter;
79
80     LIST_FOR_EACH_ENTRY(iter, &doc->range_list, HTMLTxtRange, entry) {
81         if(HTMLTXTRANGE(iter) == iface)
82             return iter;
83     }
84
85     ERR("Could not find range in document\n");
86     return NULL;
87 }
88
89 static range_unit_t string_to_unit(LPCWSTR str)
90 {
91     static const WCHAR characterW[] =
92         {'c','h','a','r','a','c','t','e','r',0};
93     static const WCHAR wordW[] =
94         {'w','o','r','d',0};
95     static const WCHAR sentenceW[] =
96         {'s','e','n','t','e','n','c','e',0};
97     static const WCHAR texteditW[] =
98         {'t','e','x','t','e','d','i','t',0};
99
100     if(!strcmpiW(str, characterW))  return RU_CHAR;
101     if(!strcmpiW(str, wordW))       return RU_WORD;
102     if(!strcmpiW(str, sentenceW))   return RU_SENTENCE;
103     if(!strcmpiW(str, texteditW))   return RU_TEXTEDIT;
104
105     return RU_UNKNOWN;
106 }
107
108 static int string_to_nscmptype(LPCWSTR str)
109 {
110     static const WCHAR seW[] = {'S','t','a','r','t','T','o','E','n','d',0};
111     static const WCHAR ssW[] = {'S','t','a','r','t','T','o','S','t','a','r','t',0};
112     static const WCHAR esW[] = {'E','n','d','T','o','S','t','a','r','t',0};
113     static const WCHAR eeW[] = {'E','n','d','T','o','E','n','d',0};
114
115     if(!strcmpiW(str, seW))  return NS_START_TO_END;
116     if(!strcmpiW(str, ssW))  return NS_START_TO_START;
117     if(!strcmpiW(str, esW))  return NS_END_TO_START;
118     if(!strcmpiW(str, eeW))  return NS_END_TO_END;
119
120     return -1;
121 }
122
123 static PRUint16 get_node_type(nsIDOMNode *node)
124 {
125     PRUint16 type = 0;
126
127     if(node)
128         nsIDOMNode_GetNodeType(node, &type);
129
130     return type;
131 }
132
133 static BOOL is_br_node(nsIDOMNode *node)
134 {
135     nsIDOMElement *elem;
136     nsAString tag_str;
137     const PRUnichar *tag;
138     BOOL ret = FALSE;
139     nsresult nsres;
140
141     nsres = nsIDOMNode_QueryInterface(node, &IID_nsIDOMElement, (void**)&elem);
142     if(NS_FAILED(nsres))
143         return FALSE;
144
145     nsAString_Init(&tag_str, NULL);
146     nsIDOMElement_GetTagName(elem, &tag_str);
147     nsIDOMElement_Release(elem);
148     nsAString_GetData(&tag_str, &tag, 0);
149
150     if(!strcmpiW(tag, brW))
151         ret = TRUE;
152
153     nsAString_Finish(&tag_str);
154
155     return ret;
156 }
157
158 static inline void wstrbuf_init(wstrbuf_t *buf)
159 {
160     buf->len = 0;
161     buf->size = 16;
162     buf->buf = mshtml_alloc(buf->size * sizeof(WCHAR));
163     *buf->buf = 0;
164 }
165
166 static inline void wstrbuf_finish(wstrbuf_t *buf)
167 {
168     mshtml_free(buf->buf);
169 }
170
171 static void wstrbuf_append_len(wstrbuf_t *buf, LPCWSTR str, int len)
172 {
173     if(buf->len+len >= buf->size) {
174         buf->size = 2*buf->len+len;
175         buf->buf = mshtml_realloc(buf->buf, buf->size * sizeof(WCHAR));
176     }
177
178     memcpy(buf->buf+buf->len, str, len*sizeof(WCHAR));
179     buf->len += len;
180     buf->buf[buf->len] = 0;
181 }
182
183 static inline void wstrbuf_append(wstrbuf_t *buf, LPCWSTR str)
184 {
185     wstrbuf_append_len(buf, str, strlenW(str));
186 }
187
188 static void wstrbuf_append_nodetxt(wstrbuf_t *buf, LPCWSTR str, int len)
189 {
190     const WCHAR *s = str;
191     WCHAR *d;
192
193     TRACE("%s\n", debugstr_wn(str, len));
194
195     if(buf->len+len >= buf->size) {
196         buf->size = 2*buf->len+len;
197         buf->buf = mshtml_realloc(buf->buf, buf->size * sizeof(WCHAR));
198     }
199
200     if(buf->len && isspaceW(buf->buf[buf->len-1])) {
201         while(s < str+len && isspaceW(*s))
202             s++;
203     }
204
205     d = buf->buf+buf->len;
206     while(s < str+len) {
207         if(isspaceW(*s)) {
208             *d++ = ' ';
209             s++;
210             while(s < str+len && isspaceW(*s))
211                 s++;
212         }else {
213             *d++ = *s++;
214         }
215     }
216
217     buf->len = d - buf->buf;
218     *d = 0;
219 }
220
221 static void wstrbuf_append_node(wstrbuf_t *buf, nsIDOMNode *node)
222 {
223
224     switch(get_node_type(node)) {
225     case TEXT_NODE: {
226         nsIDOMText *nstext;
227         nsAString data_str;
228         const PRUnichar *data;
229
230         nsIDOMNode_QueryInterface(node, &IID_nsIDOMText, (void**)&nstext);
231
232         nsAString_Init(&data_str, NULL);
233         nsIDOMText_GetData(nstext, &data_str);
234         nsAString_GetData(&data_str, &data, NULL);
235         wstrbuf_append_nodetxt(buf, data, strlenW(data));
236         nsAString_Finish(&data_str);
237
238        nsIDOMText_Release(nstext);
239
240         break;
241     }
242     case ELEMENT_NODE:
243         if(is_br_node(node)) {
244             static const WCHAR endlW[] = {'\r','\n'};
245             wstrbuf_append_len(buf, endlW, 2);
246         }
247     }
248 }
249
250 static BOOL fill_nodestr(dompos_t *pos)
251 {
252     nsIDOMText *text;
253     nsresult nsres;
254
255     if(pos->type != TEXT_NODE)
256         return FALSE;
257
258     nsres = nsIDOMNode_QueryInterface(pos->node, &IID_nsIDOMText, (void**)&text);
259     if(NS_FAILED(nsres))
260         return FALSE;
261
262     nsAString_Init(&pos->str, NULL);
263     nsIDOMText_GetData(text, &pos->str);
264     nsIDOMText_Release(text);
265     nsAString_GetData(&pos->str, &pos->p, NULL);
266
267     if(pos->off == -1)
268         pos->off = *pos->p ? strlenW(pos->p)-1 : 0;
269
270     return TRUE;
271 }
272
273 static nsIDOMNode *next_node(nsIDOMNode *iter)
274 {
275     nsIDOMNode *ret, *tmp;
276     nsresult nsres;
277
278     if(!iter)
279         return NULL;
280
281     nsres = nsIDOMNode_GetFirstChild(iter, &ret);
282     if(NS_SUCCEEDED(nsres) && ret)
283         return ret;
284
285     nsIDOMNode_AddRef(iter);
286
287     do {
288         nsres = nsIDOMNode_GetNextSibling(iter, &ret);
289         if(NS_SUCCEEDED(nsres) && ret) {
290             nsIDOMNode_Release(iter);
291             return ret;
292         }
293
294         nsres = nsIDOMNode_GetParentNode(iter, &tmp);
295         nsIDOMNode_Release(iter);
296         iter = tmp;
297     }while(NS_SUCCEEDED(nsres) && iter);
298
299     return NULL;
300 }
301
302 static nsIDOMNode *prev_node(HTMLTxtRange *This, nsIDOMNode *iter)
303 {
304     nsIDOMNode *ret, *tmp;
305     nsresult nsres;
306
307     if(!iter) {
308         nsIDOMHTMLDocument *nshtmldoc;
309         nsIDOMHTMLElement *nselem;
310         nsIDOMDocument *nsdoc;
311
312         nsIWebNavigation_GetDocument(This->doc->nscontainer->navigation, &nsdoc);
313         nsIDOMDocument_QueryInterface(nsdoc, &IID_nsIDOMHTMLDocument, (void**)&nshtmldoc);
314         nsIDOMDocument_Release(nsdoc);
315         nsIDOMHTMLDocument_GetBody(nshtmldoc, &nselem);
316         nsIDOMHTMLDocument_Release(nshtmldoc);
317
318         nsIDOMElement_GetLastChild(nselem, &tmp);
319         if(!tmp)
320             return (nsIDOMNode*)nselem;
321
322         while(tmp) {
323             ret = tmp;
324             nsIDOMNode_GetLastChild(ret, &tmp);
325         }
326
327         nsIDOMElement_Release(nselem);
328
329         return ret;
330     }
331
332     nsres = nsIDOMNode_GetLastChild(iter, &ret);
333     if(NS_SUCCEEDED(nsres) && ret)
334         return ret;
335
336     nsIDOMNode_AddRef(iter);
337
338     do {
339         nsres = nsIDOMNode_GetPreviousSibling(iter, &ret);
340         if(NS_SUCCEEDED(nsres) && ret) {
341             nsIDOMNode_Release(iter);
342             return ret;
343         }
344
345         nsres = nsIDOMNode_GetParentNode(iter, &tmp);
346         nsIDOMNode_Release(iter);
347         iter = tmp;
348     }while(NS_SUCCEEDED(nsres) && iter);
349
350     return NULL;
351 }
352
353 static nsIDOMNode *get_child_node(nsIDOMNode *node, PRUint32 off)
354 {
355     nsIDOMNodeList *node_list;
356     nsIDOMNode *ret = NULL;
357
358     nsIDOMNode_GetChildNodes(node, &node_list);
359     nsIDOMNodeList_Item(node_list, off, &ret);
360     nsIDOMNodeList_Release(node_list);
361
362     return ret;
363 }
364
365 static void get_cur_pos(HTMLTxtRange *This, BOOL start, dompos_t *pos)
366 {
367     nsIDOMNode *node;
368     PRInt32 off;
369
370     pos->p = NULL;
371
372     if(!start) {
373         PRBool collapsed;
374         nsIDOMRange_GetCollapsed(This->nsrange, &collapsed);
375         start = collapsed;
376     }
377
378     if(start) {
379         nsIDOMRange_GetStartContainer(This->nsrange, &node);
380         nsIDOMRange_GetStartOffset(This->nsrange, &off);
381     }else {
382         nsIDOMRange_GetEndContainer(This->nsrange, &node);
383         nsIDOMRange_GetEndOffset(This->nsrange, &off);
384     }
385
386     pos->type = get_node_type(node);
387     if(pos->type == ELEMENT_NODE) {
388         if(start) {
389             pos->node = get_child_node(node, off);
390             pos->off = 0;
391         }else {
392             pos->node = off ? get_child_node(node, off-1) : prev_node(This, node);
393             pos->off = -1;
394         }
395
396         pos->type = get_node_type(pos->node);
397         nsIDOMNode_Release(node);
398     }else if(start) {
399         pos->node = node;
400         pos->off = off;
401     }else if(off) {
402         pos->node = node;
403         pos->off = off-1;
404     }else {
405         pos->node = prev_node(This, node);
406         pos->off = -1;
407         nsIDOMNode_Release(node);
408     }
409
410     if(pos->type == TEXT_NODE)
411         fill_nodestr(pos);
412 }
413
414 static void set_range_pos(HTMLTxtRange *This, BOOL start, dompos_t *pos)
415 {
416     nsresult nsres;
417
418     if(start) {
419         if(pos->type == TEXT_NODE)
420             nsres = nsIDOMRange_SetStart(This->nsrange, pos->node, pos->off);
421         else
422             nsres = nsIDOMRange_SetStartBefore(This->nsrange, pos->node);
423     }else {
424         if(pos->type == TEXT_NODE && pos->p[pos->off+1])
425             nsres = nsIDOMRange_SetEnd(This->nsrange, pos->node, pos->off+1);
426         else
427             nsres = nsIDOMRange_SetEndAfter(This->nsrange, pos->node);
428     }
429
430     if(NS_FAILED(nsres))
431         ERR("failed: %p %08x\n", pos->node, nsres);
432 }
433
434 static inline void dompos_release(dompos_t *pos)
435 {
436     if(pos->node)
437         nsIDOMNode_Release(pos->node);
438
439     if(pos->p)
440         nsAString_Finish(&pos->str);
441 }
442
443 static inline void dompos_addref(dompos_t *pos)
444 {
445     if(pos->node)
446         nsIDOMNode_AddRef(pos->node);
447
448     if(pos->type == TEXT_NODE)
449         fill_nodestr(pos);
450 }
451
452 static inline BOOL dompos_cmp(const dompos_t *pos1, const dompos_t *pos2)
453 {
454     return pos1->node == pos2->node && pos1->off == pos2->off;
455 }
456
457 static void range_to_string(HTMLTxtRange *This, wstrbuf_t *buf)
458 {
459     nsIDOMNode *iter, *tmp;
460     dompos_t start_pos, end_pos;
461     PRBool collapsed;
462
463     nsIDOMRange_GetCollapsed(This->nsrange, &collapsed);
464     if(collapsed) {
465         wstrbuf_finish(buf);
466         buf->buf = NULL;
467         buf->size = 0;
468         return;
469     }
470
471     get_cur_pos(This, FALSE, &end_pos);
472     get_cur_pos(This, TRUE, &start_pos);
473
474     if(start_pos.type == TEXT_NODE) {
475         if(start_pos.node == end_pos.node) {
476             wstrbuf_append_nodetxt(buf, start_pos.p+start_pos.off, end_pos.off-start_pos.off+1);
477             iter = start_pos.node;
478             nsIDOMNode_AddRef(iter);
479         }else {
480             wstrbuf_append_nodetxt(buf, start_pos.p+start_pos.off, strlenW(start_pos.p+start_pos.off));
481             iter = next_node(start_pos.node);
482         }
483     }else {
484         iter = start_pos.node;
485         nsIDOMNode_AddRef(iter);
486     }
487
488     while(iter != end_pos.node) {
489         wstrbuf_append_node(buf, iter);
490         tmp = next_node(iter);
491         nsIDOMNode_Release(iter);
492         iter = tmp;
493     }
494
495     nsIDOMNode_AddRef(end_pos.node);
496
497     if(start_pos.node != end_pos.node && !is_br_node(end_pos.node))
498         wstrbuf_append_nodetxt(buf, end_pos.p, end_pos.off+1);
499
500     nsIDOMNode_Release(iter);
501     dompos_release(&start_pos);
502     dompos_release(&end_pos);
503
504     if(buf->len) {
505         WCHAR *p;
506
507         for(p = buf->buf+buf->len-1; p >= buf->buf && isspaceW(*p); p--);
508
509         p = strchrW(p, '\r');
510         if(p)
511             *p = 0;
512     }
513 }
514
515 static WCHAR get_pos_char(const dompos_t *pos)
516 {
517     switch(pos->type) {
518     case TEXT_NODE:
519         return pos->p[pos->off];
520     case ELEMENT_NODE:
521         if(is_br_node(pos->node))
522             return '\n';
523     }
524
525     return 0;
526 }
527
528 static void end_space(const dompos_t *pos, dompos_t *new_pos)
529 {
530     const WCHAR *p;
531
532     *new_pos = *pos;
533     dompos_addref(new_pos);
534
535     if(pos->type != TEXT_NODE)
536         return;
537
538     p = new_pos->p+new_pos->off;
539
540     if(!*p || !isspace(*p))
541         return;
542
543     while(p[1] && isspace(p[1]))
544         p++;
545
546     new_pos->off = p - new_pos->p;
547 }
548
549 static WCHAR next_char(const dompos_t *pos, dompos_t *new_pos)
550 {
551     nsIDOMNode *iter, *tmp;
552     dompos_t last_space, tmp_pos;
553     const WCHAR *p;
554     WCHAR cspace = 0;
555
556     if(pos->type == TEXT_NODE && pos->off != -1 && pos->p[pos->off]) {
557         p = pos->p+pos->off;
558
559         if(isspace(*p))
560             while(isspaceW(*++p));
561         else
562             p++;
563
564         if(*p && isspaceW(*p)) {
565             cspace = ' ';
566             while(p[1] && isspaceW(p[1]))
567                 p++;
568         }
569
570         if(*p) {
571             *new_pos = *pos;
572             new_pos->off = p - pos->p;
573             dompos_addref(new_pos);
574
575             return cspace ? cspace : *p;
576         }else {
577             last_space = *pos;
578             last_space.off = p - pos->p;
579             dompos_addref(&last_space);
580         }
581     }
582
583     iter = next_node(pos->node);
584
585     while(iter) {
586         switch(get_node_type(iter)) {
587         case TEXT_NODE:
588             tmp_pos.node = iter;
589             tmp_pos.type = TEXT_NODE;
590             dompos_addref(&tmp_pos);
591
592             p = tmp_pos.p;
593
594             if(!*p) {
595                 dompos_release(&tmp_pos);
596                 break;
597             }else if(isspaceW(*p)) {
598                 if(cspace)
599                     dompos_release(&last_space);
600                 else
601                     cspace = ' ';
602
603                 while(p[1] && isspaceW(p[1]))
604                       p++;
605
606                 tmp_pos.off = p-tmp_pos.p;
607
608                 if(!p[1]) {
609                     last_space = tmp_pos;
610                     break;
611                 }
612
613                 *new_pos = tmp_pos;
614                 nsIDOMNode_Release(iter);
615                 return cspace;
616             }else if(cspace) {
617                 *new_pos = last_space;
618                 dompos_release(&tmp_pos);
619                 nsIDOMNode_Release(iter);
620
621                 return cspace;
622             }else if(*p) {
623                 tmp_pos.off = 0;
624                 *new_pos = tmp_pos;
625             }
626
627             nsIDOMNode_Release(iter);
628             return *p;
629
630         case ELEMENT_NODE:
631             if(!is_br_node(iter))
632                 break;
633
634             if(cspace)
635                 dompos_release(&last_space);
636             cspace = '\n';
637
638             nsIDOMNode_AddRef(iter);
639             last_space.node = iter;
640             last_space.type = ELEMENT_NODE;
641             last_space.off = 0;
642             last_space.p = NULL;
643         }
644
645         tmp = iter;
646         iter = next_node(iter);
647         nsIDOMNode_Release(tmp);
648     }
649
650     if(cspace) {
651         *new_pos = last_space;
652     }else {
653         *new_pos = *pos;
654         dompos_addref(new_pos);
655     }
656
657     return cspace;
658 }
659
660 static WCHAR prev_char(HTMLTxtRange *This, const dompos_t *pos, dompos_t *new_pos)
661 {
662     nsIDOMNode *iter, *tmp;
663     const WCHAR *p;
664     BOOL skip_space = FALSE;
665
666     if(pos->type == TEXT_NODE && isspaceW(pos->p[pos->off]))
667         skip_space = TRUE;
668
669     if(pos->type == TEXT_NODE && pos->off) {
670         p = pos->p+pos->off-1;
671
672         if(skip_space) {
673             while(p >= pos->p && isspace(*p))
674                 p--;
675         }
676
677         if(p >= pos->p) {
678             *new_pos = *pos;
679             new_pos->off = p-pos->p;
680             dompos_addref(new_pos);
681             return new_pos->p[new_pos->off];
682         }
683     }
684
685     iter = prev_node(This, pos->node);
686
687     while(iter) {
688         switch(get_node_type(iter)) {
689         case TEXT_NODE: {
690             dompos_t tmp_pos;
691
692             tmp_pos.node = iter;
693             tmp_pos.type = TEXT_NODE;
694             dompos_addref(&tmp_pos);
695
696             p = tmp_pos.p + strlenW(tmp_pos.p)-1;
697
698             if(skip_space) {
699                 while(p >= tmp_pos.p && isspaceW(*p))
700                     p--;
701             }
702
703             if(p < tmp_pos.p) {
704                 dompos_release(&tmp_pos);
705                 break;
706             }
707
708             tmp_pos.off = p-tmp_pos.p;
709             *new_pos = tmp_pos;
710             nsIDOMNode_Release(iter);
711             return *p;
712         }
713
714         case ELEMENT_NODE:
715             if(!is_br_node(iter))
716                 break;
717
718             if(skip_space) {
719                 skip_space = FALSE;
720                 break;
721             }
722
723             new_pos->node = iter;
724             new_pos->type = ELEMENT_NODE;
725             new_pos->off = 0;
726             new_pos->p = NULL;
727             return '\n';
728         }
729
730         tmp = iter;
731         iter = prev_node(This, iter);
732         nsIDOMNode_Release(tmp);
733     }
734
735     *new_pos = *pos;
736     dompos_addref(new_pos);
737     return 0;
738 }
739
740 static long move_next_chars(long cnt, const dompos_t *pos, BOOL col, const dompos_t *bound_pos,
741         BOOL *bounded, dompos_t *new_pos)
742 {
743     dompos_t iter, tmp;
744     long ret = 0;
745     WCHAR c;
746
747     if(bounded)
748         *bounded = FALSE;
749
750     if(col)
751         ret++;
752
753     if(ret >= cnt) {
754         end_space(pos, new_pos);
755         return ret;
756     }
757
758     c = next_char(pos, &iter);
759     ret++;
760
761     while(ret < cnt) {
762         tmp = iter;
763         c = next_char(&tmp, &iter);
764         dompos_release(&tmp);
765         if(!c)
766             break;
767
768         ret++;
769         if(bound_pos && dompos_cmp(&tmp, bound_pos)) {
770             *bounded = TRUE;
771             ret++;
772         }
773     }
774
775     *new_pos = iter;
776     return ret;
777 }
778
779 static long move_prev_chars(HTMLTxtRange *This, long cnt, const dompos_t *pos, BOOL end,
780         const dompos_t *bound_pos, BOOL *bounded, dompos_t *new_pos)
781 {
782     dompos_t iter, tmp;
783     long ret = 0;
784     WCHAR c;
785
786     if(bounded)
787         *bounded = FALSE;
788
789     c = prev_char(This, pos, &iter);
790     if(c)
791         ret++;
792
793     while(c && ret < cnt) {
794         tmp = iter;
795         c = prev_char(This, &tmp, &iter);
796         dompos_release(&tmp);
797         if(!c) {
798             if(end)
799                 ret++;
800             break;
801         }
802
803         ret++;
804
805         if(bound_pos && dompos_cmp(&iter, bound_pos)) {
806             *bounded = TRUE;
807             cnt--;
808         }
809     }
810
811     *new_pos = iter;
812     return bounded && *bounded ? ret+1 : ret;
813 }
814
815 static long find_prev_space(HTMLTxtRange *This, const dompos_t *pos, BOOL first_space, dompos_t *ret)
816 {
817     dompos_t iter, tmp;
818     WCHAR c;
819
820     c = prev_char(This, pos, &iter);
821     if(!c || (first_space && isspaceW(c))) {
822         *ret = iter;
823         return FALSE;
824     }
825
826     while(1) {
827         tmp = iter;
828         c = prev_char(This, &tmp, &iter);
829         if(!c || isspaceW(c)) {
830             dompos_release(&iter);
831             break;
832         }
833         dompos_release(&tmp);
834     }
835
836     *ret = tmp;
837     return TRUE;
838 }
839
840 static int find_word_end(const dompos_t *pos, dompos_t *ret)
841 {
842     dompos_t iter, tmp;
843     int cnt = 1;
844     WCHAR c;
845     c = get_pos_char(pos);
846     if(isspaceW(c)) {
847         *ret = *pos;
848         dompos_addref(ret);
849         return 0;
850     }
851
852     c = next_char(pos, &iter);
853     if(!c) {
854         *ret = iter;
855         return 0;
856     }
857     if(c == '\n') {
858         *ret = *pos;
859         dompos_addref(ret);
860         return 0;
861     }
862
863     while(c && !isspaceW(c)) {
864         tmp = iter;
865         c = next_char(&tmp, &iter);
866         if(c == '\n') {
867             dompos_release(&iter);
868             iter = tmp;
869         }else {
870             cnt++;
871             dompos_release(&tmp);
872         }
873     }
874
875     *ret = iter;
876     return cnt;
877 }
878
879 static long move_next_words(long cnt, const dompos_t *pos, dompos_t *new_pos)
880 {
881     dompos_t iter, tmp;
882     long ret = 0;
883     WCHAR c;
884
885     c = get_pos_char(pos);
886     if(isspaceW(c)) {
887         end_space(pos, &iter);
888         ret++;
889     }else {
890         c = next_char(pos, &iter);
891         if(c && isspaceW(c))
892             ret++;
893     }
894
895     while(c && ret < cnt) {
896         tmp = iter;
897         c = next_char(&tmp, &iter);
898         dompos_release(&tmp);
899         if(isspaceW(c))
900             ret++;
901     }
902
903     *new_pos = iter;
904     return ret;
905 }
906
907 static long move_prev_words(HTMLTxtRange *This, long cnt, const dompos_t *pos, dompos_t *new_pos)
908 {
909     dompos_t iter, tmp;
910     long ret = 0;
911
912     iter = *pos;
913     dompos_addref(&iter);
914
915     while(ret < cnt) {
916         if(!find_prev_space(This, &iter, FALSE, &tmp))
917             break;
918
919         dompos_release(&iter);
920         iter = tmp;
921         ret++;
922     }
923
924     *new_pos = iter;
925     return ret;
926 }
927
928 #define HTMLTXTRANGE_THIS(iface) DEFINE_THIS(HTMLTxtRange, HTMLTxtRange, iface)
929
930 static HRESULT WINAPI HTMLTxtRange_QueryInterface(IHTMLTxtRange *iface, REFIID riid, void **ppv)
931 {
932     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
933
934     *ppv = NULL;
935
936     if(IsEqualGUID(&IID_IUnknown, riid)) {
937         TRACE("(%p)->(IID_IUnknown %p)\n", This, ppv);
938         *ppv = HTMLTXTRANGE(This);
939     }else if(IsEqualGUID(&IID_IDispatch, riid)) {
940         TRACE("(%p)->(IID_IDispatch %p)\n", This, ppv);
941         *ppv = HTMLTXTRANGE(This);
942     }else if(IsEqualGUID(&IID_IHTMLTxtRange, riid)) {
943         TRACE("(%p)->(IID_IHTMLTxtRange %p)\n", This, ppv);
944         *ppv = HTMLTXTRANGE(This);
945     }
946
947     if(*ppv) {
948         IUnknown_AddRef((IUnknown*)*ppv);
949         return S_OK;
950     }
951
952     WARN("(%p)->(%s %p)\n", This, debugstr_guid(riid), ppv);
953     return E_NOINTERFACE;
954 }
955
956 static ULONG WINAPI HTMLTxtRange_AddRef(IHTMLTxtRange *iface)
957 {
958     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
959     LONG ref = InterlockedIncrement(&This->ref);
960
961     TRACE("(%p) ref=%d\n", This, ref);
962
963     return ref;
964 }
965
966 static ULONG WINAPI HTMLTxtRange_Release(IHTMLTxtRange *iface)
967 {
968     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
969     LONG ref = InterlockedDecrement(&This->ref);
970
971     TRACE("(%p) ref=%d\n", This, ref);
972
973     if(!ref) {
974         if(This->nsrange)
975             nsISelection_Release(This->nsrange);
976         if(This->doc)
977             list_remove(&This->entry);
978         mshtml_free(This);
979     }
980
981     return ref;
982 }
983
984 static HRESULT WINAPI HTMLTxtRange_GetTypeInfoCount(IHTMLTxtRange *iface, UINT *pctinfo)
985 {
986     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
987     FIXME("(%p)->(%p)\n", This, pctinfo);
988     return E_NOTIMPL;
989 }
990
991 static HRESULT WINAPI HTMLTxtRange_GetTypeInfo(IHTMLTxtRange *iface, UINT iTInfo,
992                                                LCID lcid, ITypeInfo **ppTInfo)
993 {
994     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
995     FIXME("(%p)->(%u %u %p)\n", This, iTInfo, lcid, ppTInfo);
996     return E_NOTIMPL;
997 }
998
999 static HRESULT WINAPI HTMLTxtRange_GetIDsOfNames(IHTMLTxtRange *iface, REFIID riid,
1000                                                  LPOLESTR *rgszNames, UINT cNames,
1001                                                  LCID lcid, DISPID *rgDispId)
1002 {
1003     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1004     FIXME("(%p)->(%s %p %u %u %p)\n", This, debugstr_guid(riid), rgszNames, cNames,
1005           lcid, rgDispId);
1006     return E_NOTIMPL;
1007 }
1008
1009 static HRESULT WINAPI HTMLTxtRange_Invoke(IHTMLTxtRange *iface, DISPID dispIdMember,
1010                             REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS *pDispParams,
1011                             VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr)
1012 {
1013     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1014     FIXME("(%p)->(%d %s %d %d %p %p %p %p)\n", This, dispIdMember, debugstr_guid(riid),
1015           lcid, wFlags, pDispParams, pVarResult, pExcepInfo, puArgErr);
1016     return E_NOTIMPL;
1017 }
1018
1019 static HRESULT WINAPI HTMLTxtRange_get_htmlText(IHTMLTxtRange *iface, BSTR *p)
1020 {
1021     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1022
1023     TRACE("(%p)->(%p)\n", This, p);
1024
1025     *p = NULL;
1026
1027     if(This->nsrange) {
1028         nsIDOMDocumentFragment *fragment;
1029         nsresult nsres;
1030
1031         nsres = nsIDOMRange_CloneContents(This->nsrange, &fragment);
1032         if(NS_SUCCEEDED(nsres)) {
1033             const PRUnichar *nstext;
1034             nsAString nsstr;
1035
1036             nsAString_Init(&nsstr, NULL);
1037             nsnode_to_nsstring((nsIDOMNode*)fragment, &nsstr);
1038             nsIDOMDocumentFragment_Release(fragment);
1039
1040             nsAString_GetData(&nsstr, &nstext, NULL);
1041             *p = SysAllocString(nstext);
1042
1043             nsAString_Finish(&nsstr);
1044         }
1045     }
1046
1047     if(!*p) {
1048         const WCHAR emptyW[] = {0};
1049         *p = SysAllocString(emptyW);
1050     }
1051
1052     TRACE("return %s\n", debugstr_w(*p));
1053     return S_OK;
1054 }
1055
1056 static HRESULT WINAPI HTMLTxtRange_put_text(IHTMLTxtRange *iface, BSTR v)
1057 {
1058     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1059     nsIDOMDocument *nsdoc;
1060     nsIDOMText *text_node;
1061     nsAString text_str;
1062     nsresult nsres;
1063
1064     TRACE("(%p)->(%s)\n", This, debugstr_w(v));
1065
1066     if(!This->doc)
1067         return MSHTML_E_NODOC;
1068
1069     nsres = nsIWebNavigation_GetDocument(This->doc->nscontainer->navigation, &nsdoc);
1070     if(NS_FAILED(nsres)) {
1071         ERR("GetDocument failed: %08x\n", nsres);
1072         return S_OK;
1073     }
1074
1075     nsAString_Init(&text_str, v);
1076     nsres = nsIDOMDocument_CreateTextNode(nsdoc, &text_str, &text_node);
1077     nsIDOMDocument_Release(nsdoc);
1078     nsAString_Finish(&text_str);
1079     if(NS_FAILED(nsres)) {
1080         ERR("CreateTextNode failed: %08x\n", nsres);
1081         return S_OK;
1082     }
1083     nsres = nsIDOMRange_DeleteContents(This->nsrange);
1084     if(NS_FAILED(nsres))
1085         ERR("DeleteContents failed: %08x\n", nsres);
1086
1087     nsres = nsIDOMRange_InsertNode(This->nsrange, (nsIDOMNode*)text_node);
1088     if(NS_FAILED(nsres))
1089         ERR("InsertNode failed: %08x\n", nsres);
1090
1091     nsres = nsIDOMRange_SetEndAfter(This->nsrange, (nsIDOMNode*)text_node);
1092     if(NS_FAILED(nsres))
1093         ERR("SetEndAfter failed: %08x\n", nsres);
1094
1095     return IHTMLTxtRange_collapse(HTMLTXTRANGE(This), VARIANT_FALSE);
1096 }
1097
1098 static HRESULT WINAPI HTMLTxtRange_get_text(IHTMLTxtRange *iface, BSTR *p)
1099 {
1100     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1101     wstrbuf_t buf;
1102
1103     TRACE("(%p)->(%p)\n", This, p);
1104
1105     *p = NULL;
1106     if(!This->nsrange)
1107         return S_OK;
1108
1109     wstrbuf_init(&buf);
1110     range_to_string(This, &buf);
1111     if(buf.buf)
1112         *p = SysAllocString(buf.buf);
1113     wstrbuf_finish(&buf);
1114
1115     TRACE("ret %s\n", debugstr_w(*p));
1116     return S_OK;
1117 }
1118
1119 static HRESULT WINAPI HTMLTxtRange_parentElement(IHTMLTxtRange *iface, IHTMLElement **parent)
1120 {
1121     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1122     nsIDOMNode *nsnode, *tmp;
1123     HTMLDOMNode *node;
1124
1125     TRACE("(%p)->(%p)\n", This, parent);
1126
1127     nsIDOMRange_GetCommonAncestorContainer(This->nsrange, &nsnode);
1128     while(nsnode && get_node_type(nsnode) != ELEMENT_NODE) {
1129         nsIDOMNode_GetParentNode(nsnode, &tmp);
1130         nsIDOMNode_Release(nsnode);
1131         nsnode = tmp;
1132     }
1133
1134     if(!nsnode) {
1135         *parent = NULL;
1136         return S_OK;
1137     }
1138
1139     node = get_node(This->doc, nsnode);
1140     nsIDOMNode_Release(nsnode);
1141
1142     return IHTMLDOMNode_QueryInterface(HTMLDOMNODE(node), &IID_IHTMLElement, (void**)parent);
1143 }
1144
1145 static HRESULT WINAPI HTMLTxtRange_duplicate(IHTMLTxtRange *iface, IHTMLTxtRange **Duplicate)
1146 {
1147     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1148     nsIDOMRange *nsrange = NULL;
1149
1150     TRACE("(%p)->(%p)\n", This, Duplicate);
1151
1152     nsIDOMRange_CloneRange(This->nsrange, &nsrange);
1153     *Duplicate = HTMLTxtRange_Create(This->doc, nsrange);
1154     nsIDOMRange_Release(nsrange);
1155
1156     return S_OK;
1157 }
1158
1159 static HRESULT WINAPI HTMLTxtRange_inRange(IHTMLTxtRange *iface, IHTMLTxtRange *Range,
1160         VARIANT_BOOL *InRange)
1161 {
1162     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1163     HTMLTxtRange *src_range;
1164     PRInt16 nsret = 0;
1165     nsresult nsres;
1166
1167     TRACE("(%p)->(%p %p)\n", This, Range, InRange);
1168
1169     *InRange = VARIANT_FALSE;
1170
1171     src_range = get_range_object(This->doc, Range);
1172     if(!src_range)
1173         return E_FAIL;
1174
1175     nsres = nsIDOMRange_CompareBoundaryPoints(This->nsrange, NS_START_TO_START,
1176             src_range->nsrange, &nsret);
1177     if(NS_SUCCEEDED(nsres) && nsret <= 0) {
1178         nsres = nsIDOMRange_CompareBoundaryPoints(This->nsrange, NS_END_TO_END,
1179                 src_range->nsrange, &nsret);
1180         if(NS_SUCCEEDED(nsres) && nsret >= 0)
1181             *InRange = VARIANT_TRUE;
1182     }
1183
1184     if(NS_FAILED(nsres))
1185         ERR("CompareBoundaryPoints failed: %08x\n", nsres);
1186
1187     return S_OK;
1188 }
1189
1190 static HRESULT WINAPI HTMLTxtRange_isEqual(IHTMLTxtRange *iface, IHTMLTxtRange *Range,
1191         VARIANT_BOOL *IsEqual)
1192 {
1193     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1194     HTMLTxtRange *src_range;
1195     PRInt16 nsret = 0;
1196     nsresult nsres;
1197
1198     TRACE("(%p)->(%p %p)\n", This, Range, IsEqual);
1199
1200     *IsEqual = VARIANT_FALSE;
1201
1202     src_range = get_range_object(This->doc, Range);
1203     if(!src_range)
1204         return E_FAIL;
1205
1206     nsres = nsIDOMRange_CompareBoundaryPoints(This->nsrange, NS_START_TO_START,
1207             src_range->nsrange, &nsret);
1208     if(NS_SUCCEEDED(nsres) && !nsret) {
1209         nsres = nsIDOMRange_CompareBoundaryPoints(This->nsrange, NS_END_TO_END,
1210                 src_range->nsrange, &nsret);
1211         if(NS_SUCCEEDED(nsres) && !nsret)
1212             *IsEqual = VARIANT_TRUE;
1213     }
1214
1215     if(NS_FAILED(nsres))
1216         ERR("CompareBoundaryPoints failed: %08x\n", nsres);
1217
1218     return S_OK;
1219 }
1220
1221 static HRESULT WINAPI HTMLTxtRange_scrollIntoView(IHTMLTxtRange *iface, VARIANT_BOOL fStart)
1222 {
1223     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1224     FIXME("(%p)->(%x)\n", This, fStart);
1225     return E_NOTIMPL;
1226 }
1227
1228 static HRESULT WINAPI HTMLTxtRange_collapse(IHTMLTxtRange *iface, VARIANT_BOOL Start)
1229 {
1230     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1231
1232     TRACE("(%p)->(%x)\n", This, Start);
1233
1234     nsIDOMRange_Collapse(This->nsrange, Start != VARIANT_FALSE);
1235     return S_OK;
1236 }
1237
1238 static HRESULT WINAPI HTMLTxtRange_expand(IHTMLTxtRange *iface, BSTR Unit, VARIANT_BOOL *Success)
1239 {
1240     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1241     range_unit_t unit;
1242
1243     TRACE("(%p)->(%s %p)\n", This, debugstr_w(Unit), Success);
1244
1245     unit = string_to_unit(Unit);
1246     if(unit == RU_UNKNOWN)
1247         return E_INVALIDARG;
1248
1249     *Success = VARIANT_FALSE;
1250
1251     switch(unit) {
1252     case RU_WORD: {
1253         dompos_t end_pos, start_pos, new_start_pos, new_end_pos;
1254         PRBool collapsed;
1255
1256         nsIDOMRange_GetCollapsed(This->nsrange, &collapsed);
1257
1258         get_cur_pos(This, TRUE, &start_pos);
1259         get_cur_pos(This, FALSE, &end_pos);
1260
1261         if(find_word_end(&end_pos, &new_end_pos) || collapsed) {
1262             set_range_pos(This, FALSE, &new_end_pos);
1263             *Success = VARIANT_TRUE;
1264         }
1265
1266         if(start_pos.type && (get_pos_char(&end_pos) || !dompos_cmp(&new_end_pos, &end_pos))) {
1267             if(find_prev_space(This, &start_pos, TRUE, &new_start_pos)) {
1268                 set_range_pos(This, TRUE, &new_start_pos);
1269                 *Success = VARIANT_TRUE;
1270             }
1271             dompos_release(&new_start_pos);
1272         }
1273
1274         dompos_release(&new_end_pos);
1275         dompos_release(&end_pos);
1276         dompos_release(&start_pos);
1277
1278         break;
1279     }
1280
1281     case RU_TEXTEDIT: {
1282         nsIDOMDocument *nsdoc;
1283         nsIDOMHTMLDocument *nshtmldoc;
1284         nsIDOMHTMLElement *nsbody = NULL;
1285         nsresult nsres;
1286
1287         nsres = nsIWebNavigation_GetDocument(This->doc->nscontainer->navigation, &nsdoc);
1288         if(NS_FAILED(nsres) || !nsdoc) {
1289             ERR("GetDocument failed: %08x\n", nsres);
1290             break;
1291         }
1292
1293         nsIDOMDocument_QueryInterface(nsdoc, &IID_nsIDOMHTMLDocument, (void**)&nshtmldoc);
1294         nsIDOMDocument_Release(nsdoc);
1295
1296         nsres = nsIDOMHTMLDocument_GetBody(nshtmldoc, &nsbody);
1297         nsIDOMHTMLDocument_Release(nshtmldoc);
1298         if(NS_FAILED(nsres) || !nsbody) {
1299             ERR("Could not get body: %08x\n", nsres);
1300             break;
1301         }
1302
1303         nsres = nsIDOMRange_SelectNodeContents(This->nsrange, (nsIDOMNode*)nsbody);
1304         nsIDOMHTMLElement_Release(nsbody);
1305         if(NS_FAILED(nsres)) {
1306             ERR("Collapse failed: %08x\n", nsres);
1307             break;
1308         }
1309
1310         *Success = VARIANT_TRUE;
1311         break;
1312     }
1313
1314     default:
1315         FIXME("Unimplemented unit %s\n", debugstr_w(Unit));
1316     }
1317
1318     return S_OK;
1319 }
1320
1321 static HRESULT WINAPI HTMLTxtRange_move(IHTMLTxtRange *iface, BSTR Unit,
1322         long Count, long *ActualCount)
1323 {
1324     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1325     range_unit_t unit;
1326
1327     TRACE("(%p)->(%s %ld %p)\n", This, debugstr_w(Unit), Count, ActualCount);
1328
1329     unit = string_to_unit(Unit);
1330     if(unit == RU_UNKNOWN)
1331         return E_INVALIDARG;
1332
1333     if(!Count) {
1334         *ActualCount = 0;
1335         return IHTMLTxtRange_collapse(HTMLTXTRANGE(This), TRUE);
1336     }
1337
1338     switch(unit) {
1339     case RU_CHAR: {
1340         dompos_t cur_pos, new_pos;
1341
1342         get_cur_pos(This, TRUE, &cur_pos);
1343
1344         if(Count > 0) {
1345             *ActualCount = move_next_chars(Count, &cur_pos, TRUE, NULL, NULL, &new_pos);
1346             set_range_pos(This, FALSE, &new_pos);
1347             dompos_release(&new_pos);
1348
1349             IHTMLTxtRange_collapse(HTMLTXTRANGE(This), FALSE);
1350         }else {
1351             *ActualCount = -move_prev_chars(This, -Count, &cur_pos, FALSE, NULL, NULL, &new_pos);
1352             set_range_pos(This, TRUE, &new_pos);
1353             IHTMLTxtRange_collapse(HTMLTXTRANGE(This), TRUE);
1354             dompos_release(&new_pos);
1355         }
1356
1357         dompos_release(&cur_pos);
1358         break;
1359     }
1360
1361     case RU_WORD: {
1362         dompos_t cur_pos, new_pos;
1363
1364         get_cur_pos(This, TRUE, &cur_pos);
1365
1366         if(Count > 0) {
1367             *ActualCount = move_next_words(Count, &cur_pos, &new_pos);
1368             set_range_pos(This, FALSE, &new_pos);
1369             dompos_release(&new_pos);
1370             IHTMLTxtRange_collapse(HTMLTXTRANGE(This), FALSE);
1371         }else {
1372             *ActualCount = -move_prev_words(This, -Count, &cur_pos, &new_pos);
1373             set_range_pos(This, TRUE, &new_pos);
1374             IHTMLTxtRange_collapse(HTMLTXTRANGE(This), TRUE);
1375             dompos_release(&new_pos);
1376         }
1377
1378         dompos_release(&cur_pos);
1379         break;
1380     }
1381
1382     default:
1383         FIXME("unimplemented unit %s\n", debugstr_w(Unit));
1384     }
1385
1386     TRACE("ret %ld\n", *ActualCount);
1387     return S_OK;
1388 }
1389
1390 static HRESULT WINAPI HTMLTxtRange_moveStart(IHTMLTxtRange *iface, BSTR Unit,
1391         long Count, long *ActualCount)
1392 {
1393     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1394     range_unit_t unit;
1395
1396     TRACE("(%p)->(%s %ld %p)\n", This, debugstr_w(Unit), Count, ActualCount);
1397
1398     unit = string_to_unit(Unit);
1399     if(unit == RU_UNKNOWN)
1400         return E_INVALIDARG;
1401
1402     if(!Count) {
1403         *ActualCount = 0;
1404         return S_OK;
1405     }
1406
1407     switch(unit) {
1408     case RU_CHAR: {
1409         dompos_t start_pos, end_pos, new_pos;
1410         PRBool collapsed;
1411
1412         get_cur_pos(This, TRUE, &start_pos);
1413         get_cur_pos(This, FALSE, &end_pos);
1414         nsIDOMRange_GetCollapsed(This->nsrange, &collapsed);
1415
1416         if(Count > 0) {
1417             BOOL bounded;
1418
1419             *ActualCount = move_next_chars(Count, &start_pos, collapsed, &end_pos, &bounded, &new_pos);
1420             set_range_pos(This, !bounded, &new_pos);
1421             if(bounded)
1422                 IHTMLTxtRange_collapse(HTMLTXTRANGE(This), FALSE);
1423         }else {
1424             *ActualCount = -move_prev_chars(This, -Count, &start_pos, FALSE, NULL, NULL, &new_pos);
1425             set_range_pos(This, TRUE, &new_pos);
1426         }
1427
1428         dompos_release(&start_pos);
1429         dompos_release(&end_pos);
1430         dompos_release(&new_pos);
1431         break;
1432     }
1433
1434     default:
1435         FIXME("unimplemented unit %s\n", debugstr_w(Unit));
1436     }
1437
1438     return S_OK;
1439 }
1440
1441 static HRESULT WINAPI HTMLTxtRange_moveEnd(IHTMLTxtRange *iface, BSTR Unit,
1442         long Count, long *ActualCount)
1443 {
1444     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1445     range_unit_t unit;
1446
1447     TRACE("(%p)->(%s %ld %p)\n", This, debugstr_w(Unit), Count, ActualCount);
1448
1449     unit = string_to_unit(Unit);
1450     if(unit == RU_UNKNOWN)
1451         return E_INVALIDARG;
1452
1453     if(!Count) {
1454         *ActualCount = 0;
1455         return S_OK;
1456     }
1457
1458     switch(unit) {
1459     case RU_CHAR: {
1460         dompos_t start_pos, end_pos, new_pos;
1461         PRBool collapsed;
1462
1463         get_cur_pos(This, TRUE, &start_pos);
1464         get_cur_pos(This, FALSE, &end_pos);
1465         nsIDOMRange_GetCollapsed(This->nsrange, &collapsed);
1466
1467         if(Count > 0) {
1468             *ActualCount = move_next_chars(Count, &end_pos, collapsed, NULL, NULL, &new_pos);
1469             set_range_pos(This, FALSE, &new_pos);
1470         }else {
1471             BOOL bounded;
1472
1473             *ActualCount = -move_prev_chars(This, -Count, &end_pos, TRUE, &start_pos, &bounded, &new_pos);
1474             set_range_pos(This, bounded, &new_pos);
1475             if(bounded)
1476                 IHTMLTxtRange_collapse(HTMLTXTRANGE(This), TRUE);
1477         }
1478
1479         dompos_release(&start_pos);
1480         dompos_release(&end_pos);
1481         dompos_release(&new_pos);
1482         break;
1483     }
1484
1485     default:
1486         FIXME("unimplemented unit %s\n", debugstr_w(Unit));
1487     }
1488
1489     return S_OK;
1490 }
1491
1492 static HRESULT WINAPI HTMLTxtRange_select(IHTMLTxtRange *iface)
1493 {
1494     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1495
1496     TRACE("(%p)\n", This);
1497
1498     if(This->doc->nscontainer) {
1499         nsIDOMWindow *dom_window = NULL;
1500         nsISelection *nsselection;
1501
1502         nsIWebBrowser_GetContentDOMWindow(This->doc->nscontainer->webbrowser, &dom_window);
1503         nsIDOMWindow_GetSelection(dom_window, &nsselection);
1504         nsIDOMWindow_Release(dom_window);
1505
1506         nsISelection_RemoveAllRanges(nsselection);
1507         nsISelection_AddRange(nsselection, This->nsrange);
1508
1509         nsISelection_Release(nsselection);
1510     }
1511
1512     return S_OK;
1513 }
1514
1515 static HRESULT WINAPI HTMLTxtRange_pasteHTML(IHTMLTxtRange *iface, BSTR html)
1516 {
1517     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1518     FIXME("(%p)->(%s)\n", This, debugstr_w(html));
1519     return E_NOTIMPL;
1520 }
1521
1522 static HRESULT WINAPI HTMLTxtRange_moveToElementText(IHTMLTxtRange *iface, IHTMLElement *element)
1523 {
1524     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1525     FIXME("(%p)->(%p)\n", This, element);
1526     return E_NOTIMPL;
1527 }
1528
1529 static HRESULT WINAPI HTMLTxtRange_setEndPoint(IHTMLTxtRange *iface, BSTR how,
1530         IHTMLTxtRange *SourceRange)
1531 {
1532     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1533     FIXME("(%p)->(%s %p)\n", This, debugstr_w(how), SourceRange);
1534     return E_NOTIMPL;
1535 }
1536
1537 static HRESULT WINAPI HTMLTxtRange_compareEndPoints(IHTMLTxtRange *iface, BSTR how,
1538         IHTMLTxtRange *SourceRange, long *ret)
1539 {
1540     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1541     HTMLTxtRange *src_range;
1542     PRInt16 nsret = 0;
1543     int nscmpt;
1544     nsresult nsres;
1545
1546     TRACE("(%p)->(%s %p %p)\n", This, debugstr_w(how), SourceRange, ret);
1547
1548     nscmpt = string_to_nscmptype(how);
1549     if(nscmpt == -1)
1550         return E_INVALIDARG;
1551
1552     src_range = get_range_object(This->doc, SourceRange);
1553     if(!src_range)
1554         return E_FAIL;
1555
1556     nsres = nsIDOMRange_CompareBoundaryPoints(This->nsrange, nscmpt, src_range->nsrange, &nsret);
1557     if(NS_FAILED(nsres))
1558         ERR("CompareBoundaryPoints failed: %08x\n", nsres);
1559
1560     *ret = nsret;
1561     return S_OK;
1562 }
1563
1564 static HRESULT WINAPI HTMLTxtRange_findText(IHTMLTxtRange *iface, BSTR String,
1565         long count, long Flags, VARIANT_BOOL *Success)
1566 {
1567     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1568     FIXME("(%p)->(%s %ld %08lx %p)\n", This, debugstr_w(String), count, Flags, Success);
1569     return E_NOTIMPL;
1570 }
1571
1572 static HRESULT WINAPI HTMLTxtRange_moveToPoint(IHTMLTxtRange *iface, long x, long y)
1573 {
1574     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1575     FIXME("(%p)->(%ld %ld)\n", This, x, y);
1576     return E_NOTIMPL;
1577 }
1578
1579 static HRESULT WINAPI HTMLTxtRange_getBookmark(IHTMLTxtRange *iface, BSTR *Bookmark)
1580 {
1581     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1582     FIXME("(%p)->(%p)\n", This, Bookmark);
1583     return E_NOTIMPL;
1584 }
1585
1586 static HRESULT WINAPI HTMLTxtRange_moveToBookmark(IHTMLTxtRange *iface, BSTR Bookmark,
1587         VARIANT_BOOL *Success)
1588 {
1589     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1590     FIXME("(%p)->(%s %p)\n", This, debugstr_w(Bookmark), Success);
1591     return E_NOTIMPL;
1592 }
1593
1594 static HRESULT WINAPI HTMLTxtRange_queryCommandSupported(IHTMLTxtRange *iface, BSTR cmdID,
1595         VARIANT_BOOL *pfRet)
1596 {
1597     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1598     FIXME("(%p)->(%s %p)\n", This, debugstr_w(cmdID), pfRet);
1599     return E_NOTIMPL;
1600 }
1601
1602 static HRESULT WINAPI HTMLTxtRange_queryCommandEnabled(IHTMLTxtRange *iface, BSTR cmdID,
1603         VARIANT_BOOL *pfRet)
1604 {
1605     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1606     FIXME("(%p)->(%s %p)\n", This, debugstr_w(cmdID), pfRet);
1607     return E_NOTIMPL;
1608 }
1609
1610 static HRESULT WINAPI HTMLTxtRange_queryCommandState(IHTMLTxtRange *iface, BSTR cmdID,
1611         VARIANT_BOOL *pfRet)
1612 {
1613     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1614     FIXME("(%p)->(%s %p)\n", This, debugstr_w(cmdID), pfRet);
1615     return E_NOTIMPL;
1616 }
1617
1618 static HRESULT WINAPI HTMLTxtRange_queryCommandIndeterm(IHTMLTxtRange *iface, BSTR cmdID,
1619         VARIANT_BOOL *pfRet)
1620 {
1621     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1622     FIXME("(%p)->(%s %p)\n", This, debugstr_w(cmdID), pfRet);
1623     return E_NOTIMPL;
1624 }
1625
1626 static HRESULT WINAPI HTMLTxtRange_queryCommandText(IHTMLTxtRange *iface, BSTR cmdID,
1627         BSTR *pcmdText)
1628 {
1629     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1630     FIXME("(%p)->(%s %p)\n", This, debugstr_w(cmdID), pcmdText);
1631     return E_NOTIMPL;
1632 }
1633
1634 static HRESULT WINAPI HTMLTxtRange_queryCommandValue(IHTMLTxtRange *iface, BSTR cmdID,
1635         VARIANT *pcmdValue)
1636 {
1637     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1638     FIXME("(%p)->(%s %p)\n", This, debugstr_w(cmdID), pcmdValue);
1639     return E_NOTIMPL;
1640 }
1641
1642 static HRESULT WINAPI HTMLTxtRange_execCommand(IHTMLTxtRange *iface, BSTR cmdID,
1643         VARIANT_BOOL showUI, VARIANT value, VARIANT_BOOL *pfRet)
1644 {
1645     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1646     FIXME("(%p)->(%s %x v %p)\n", This, debugstr_w(cmdID), showUI, pfRet);
1647     return E_NOTIMPL;
1648 }
1649
1650 static HRESULT WINAPI HTMLTxtRange_execCommandShowHelp(IHTMLTxtRange *iface, BSTR cmdID,
1651         VARIANT_BOOL *pfRet)
1652 {
1653     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1654     FIXME("(%p)->(%s %p)\n", This, debugstr_w(cmdID), pfRet);
1655     return E_NOTIMPL;
1656 }
1657
1658 #undef HTMLTXTRANGE_THIS
1659
1660 static const IHTMLTxtRangeVtbl HTMLTxtRangeVtbl = {
1661     HTMLTxtRange_QueryInterface,
1662     HTMLTxtRange_AddRef,
1663     HTMLTxtRange_Release,
1664     HTMLTxtRange_GetTypeInfoCount,
1665     HTMLTxtRange_GetTypeInfo,
1666     HTMLTxtRange_GetIDsOfNames,
1667     HTMLTxtRange_Invoke,
1668     HTMLTxtRange_get_htmlText,
1669     HTMLTxtRange_put_text,
1670     HTMLTxtRange_get_text,
1671     HTMLTxtRange_parentElement,
1672     HTMLTxtRange_duplicate,
1673     HTMLTxtRange_inRange,
1674     HTMLTxtRange_isEqual,
1675     HTMLTxtRange_scrollIntoView,
1676     HTMLTxtRange_collapse,
1677     HTMLTxtRange_expand,
1678     HTMLTxtRange_move,
1679     HTMLTxtRange_moveStart,
1680     HTMLTxtRange_moveEnd,
1681     HTMLTxtRange_select,
1682     HTMLTxtRange_pasteHTML,
1683     HTMLTxtRange_moveToElementText,
1684     HTMLTxtRange_setEndPoint,
1685     HTMLTxtRange_compareEndPoints,
1686     HTMLTxtRange_findText,
1687     HTMLTxtRange_moveToPoint,
1688     HTMLTxtRange_getBookmark,
1689     HTMLTxtRange_moveToBookmark,
1690     HTMLTxtRange_queryCommandSupported,
1691     HTMLTxtRange_queryCommandEnabled,
1692     HTMLTxtRange_queryCommandState,
1693     HTMLTxtRange_queryCommandIndeterm,
1694     HTMLTxtRange_queryCommandText,
1695     HTMLTxtRange_queryCommandValue,
1696     HTMLTxtRange_execCommand,
1697     HTMLTxtRange_execCommandShowHelp
1698 };
1699
1700 IHTMLTxtRange *HTMLTxtRange_Create(HTMLDocument *doc, nsIDOMRange *nsrange)
1701 {
1702     HTMLTxtRange *ret = mshtml_alloc(sizeof(HTMLTxtRange));
1703
1704     ret->lpHTMLTxtRangeVtbl = &HTMLTxtRangeVtbl;
1705     ret->ref = 1;
1706
1707     if(nsrange)
1708         nsIDOMRange_AddRef(nsrange);
1709     ret->nsrange = nsrange;
1710
1711     ret->doc = doc;
1712     list_add_head(&doc->range_list, &ret->entry);
1713
1714     return HTMLTXTRANGE(ret);
1715 }
1716
1717 void detach_ranges(HTMLDocument *This)
1718 {
1719     HTMLTxtRange *iter;
1720
1721     LIST_FOR_EACH_ENTRY(iter, &This->range_list, HTMLTxtRange, entry) {
1722         iter->doc = NULL;
1723     }
1724 }