├── xmltab.png
├── README.md
├── xml.h
├── LICENSE
├── xml.c
└── main.c
/xmltab.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/little-brother/xmltab-wlx/HEAD/xmltab.png
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | **xmltab-wlx** is a [Total Commander](https://www.ghisler.com/) plugin to view XML files.
2 |
3 | |[**Download the latest version**](https://github.com/little-brother/xmltab-wlx/releases/latest/download/xmltab.zip)|
4 | |-------------------------------------------------------------------------------------------|
5 |
6 | 
7 |
8 | ### Features
9 | * Mixed tree and grid view
10 | * Column filters
11 | * Sort data by column click
12 | * Beautifier and highlighting
13 | * Supports ANSI, UTF8 and UTF16
14 |
15 | If you have any problems, comments or suggestions, check [Wiki](https://github.com/little-brother/xmltab-wlx/wiki), create [issue](https://github.com/little-brother/xmltab-wlx/issues) or just let me know lb.im@ya.ru.
--------------------------------------------------------------------------------
/xml.h:
--------------------------------------------------------------------------------
1 | // Based on https://github.com/markusfisch/libxml
2 | #ifndef _xml_h_
3 | #define _xml_h_
4 |
5 | typedef struct xml_attribute {
6 | /* Argument name */
7 | char *key;
8 |
9 | /* Argument value */
10 | char *value;
11 |
12 | /* Pointer to next argument. May be NULL. */
13 | struct xml_attribute *next;
14 |
15 | /* Pointer to prev argument. May be NULL. */
16 | struct xml_attribute *prev;
17 | } xml_attribute;
18 |
19 | typedef struct xml_element {
20 | /* The tag name if this is a tag element or NULL if this
21 | * element represents character data. */
22 | char *key;
23 |
24 | /* Character data segment of parent XML element or NULL if
25 | * this is a tag element. "key" and "value" are exclusive
26 | * because character data is treated as "nameless" child
27 | * element. So each xml_element can have either a "key"
28 | * or a "value" but not both. */
29 | char *value;
30 |
31 | /* Parent element. May be NULL. */
32 | struct xml_element *parent;
33 |
34 | /* First child element. May be NULL. */
35 | struct xml_element *first_child;
36 |
37 | /* Last child element. May be NULL. */
38 | struct xml_element *last_child;
39 |
40 | /* Pointer to next sibling. May be NULL. */
41 | struct xml_element *next;
42 |
43 | /* Pointer to prev sibling. May be NULL. */
44 | struct xml_element *prev;
45 |
46 | /* Added: child count, XML offset and XML length */
47 | int child_count;
48 | int offset;
49 | int length;
50 |
51 | /* First and last attribute. Both may be NULL. */
52 | xml_attribute *first_attribute, *last_attribute;
53 |
54 | int attribute_count;
55 | void* userdata;
56 | } xml_element;
57 |
58 | struct xml_element* xml_parse(const char *);
59 | void xml_free(struct xml_element *);
60 |
61 | struct xml_element* xml_find_element(struct xml_element*, const char*);
62 | struct xml_attribute* xml_find_attribute(struct xml_element*, const char*);
63 |
64 | char* xml_content(struct xml_element *);
65 | char* xml_path(struct xml_element *);
66 |
67 | #endif
68 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 2, June 1991
3 |
4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
6 | Everyone is permitted to copy and distribute verbatim copies
7 | of this license document, but changing it is not allowed.
8 |
9 | Preamble
10 |
11 | The licenses for most software are designed to take away your
12 | freedom to share and change it. By contrast, the GNU General Public
13 | License is intended to guarantee your freedom to share and change free
14 | software--to make sure the software is free for all its users. This
15 | General Public License applies to most of the Free Software
16 | Foundation's software and to any other program whose authors commit to
17 | using it. (Some other Free Software Foundation software is covered by
18 | the GNU Lesser General Public License instead.) You can apply it to
19 | your programs, too.
20 |
21 | When we speak of free software, we are referring to freedom, not
22 | price. Our General Public Licenses are designed to make sure that you
23 | have the freedom to distribute copies of free software (and charge for
24 | this service if you wish), that you receive source code or can get it
25 | if you want it, that you can change the software or use pieces of it
26 | in new free programs; and that you know you can do these things.
27 |
28 | To protect your rights, we need to make restrictions that forbid
29 | anyone to deny you these rights or to ask you to surrender the rights.
30 | These restrictions translate to certain responsibilities for you if you
31 | distribute copies of the software, or if you modify it.
32 |
33 | For example, if you distribute copies of such a program, whether
34 | gratis or for a fee, you must give the recipients all the rights that
35 | you have. You must make sure that they, too, receive or can get the
36 | source code. And you must show them these terms so they know their
37 | rights.
38 |
39 | We protect your rights with two steps: (1) copyright the software, and
40 | (2) offer you this license which gives you legal permission to copy,
41 | distribute and/or modify the software.
42 |
43 | Also, for each author's protection and ours, we want to make certain
44 | that everyone understands that there is no warranty for this free
45 | software. If the software is modified by someone else and passed on, we
46 | want its recipients to know that what they have is not the original, so
47 | that any problems introduced by others will not reflect on the original
48 | authors' reputations.
49 |
50 | Finally, any free program is threatened constantly by software
51 | patents. We wish to avoid the danger that redistributors of a free
52 | program will individually obtain patent licenses, in effect making the
53 | program proprietary. To prevent this, we have made it clear that any
54 | patent must be licensed for everyone's free use or not licensed at all.
55 |
56 | The precise terms and conditions for copying, distribution and
57 | modification follow.
58 |
59 | GNU GENERAL PUBLIC LICENSE
60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
61 |
62 | 0. This License applies to any program or other work which contains
63 | a notice placed by the copyright holder saying it may be distributed
64 | under the terms of this General Public License. The "Program", below,
65 | refers to any such program or work, and a "work based on the Program"
66 | means either the Program or any derivative work under copyright law:
67 | that is to say, a work containing the Program or a portion of it,
68 | either verbatim or with modifications and/or translated into another
69 | language. (Hereinafter, translation is included without limitation in
70 | the term "modification".) Each licensee is addressed as "you".
71 |
72 | Activities other than copying, distribution and modification are not
73 | covered by this License; they are outside its scope. The act of
74 | running the Program is not restricted, and the output from the Program
75 | is covered only if its contents constitute a work based on the
76 | Program (independent of having been made by running the Program).
77 | Whether that is true depends on what the Program does.
78 |
79 | 1. You may copy and distribute verbatim copies of the Program's
80 | source code as you receive it, in any medium, provided that you
81 | conspicuously and appropriately publish on each copy an appropriate
82 | copyright notice and disclaimer of warranty; keep intact all the
83 | notices that refer to this License and to the absence of any warranty;
84 | and give any other recipients of the Program a copy of this License
85 | along with the Program.
86 |
87 | You may charge a fee for the physical act of transferring a copy, and
88 | you may at your option offer warranty protection in exchange for a fee.
89 |
90 | 2. You may modify your copy or copies of the Program or any portion
91 | of it, thus forming a work based on the Program, and copy and
92 | distribute such modifications or work under the terms of Section 1
93 | above, provided that you also meet all of these conditions:
94 |
95 | a) You must cause the modified files to carry prominent notices
96 | stating that you changed the files and the date of any change.
97 |
98 | b) You must cause any work that you distribute or publish, that in
99 | whole or in part contains or is derived from the Program or any
100 | part thereof, to be licensed as a whole at no charge to all third
101 | parties under the terms of this License.
102 |
103 | c) If the modified program normally reads commands interactively
104 | when run, you must cause it, when started running for such
105 | interactive use in the most ordinary way, to print or display an
106 | announcement including an appropriate copyright notice and a
107 | notice that there is no warranty (or else, saying that you provide
108 | a warranty) and that users may redistribute the program under
109 | these conditions, and telling the user how to view a copy of this
110 | License. (Exception: if the Program itself is interactive but
111 | does not normally print such an announcement, your work based on
112 | the Program is not required to print an announcement.)
113 |
114 | These requirements apply to the modified work as a whole. If
115 | identifiable sections of that work are not derived from the Program,
116 | and can be reasonably considered independent and separate works in
117 | themselves, then this License, and its terms, do not apply to those
118 | sections when you distribute them as separate works. But when you
119 | distribute the same sections as part of a whole which is a work based
120 | on the Program, the distribution of the whole must be on the terms of
121 | this License, whose permissions for other licensees extend to the
122 | entire whole, and thus to each and every part regardless of who wrote it.
123 |
124 | Thus, it is not the intent of this section to claim rights or contest
125 | your rights to work written entirely by you; rather, the intent is to
126 | exercise the right to control the distribution of derivative or
127 | collective works based on the Program.
128 |
129 | In addition, mere aggregation of another work not based on the Program
130 | with the Program (or with a work based on the Program) on a volume of
131 | a storage or distribution medium does not bring the other work under
132 | the scope of this License.
133 |
134 | 3. You may copy and distribute the Program (or a work based on it,
135 | under Section 2) in object code or executable form under the terms of
136 | Sections 1 and 2 above provided that you also do one of the following:
137 |
138 | a) Accompany it with the complete corresponding machine-readable
139 | source code, which must be distributed under the terms of Sections
140 | 1 and 2 above on a medium customarily used for software interchange; or,
141 |
142 | b) Accompany it with a written offer, valid for at least three
143 | years, to give any third party, for a charge no more than your
144 | cost of physically performing source distribution, a complete
145 | machine-readable copy of the corresponding source code, to be
146 | distributed under the terms of Sections 1 and 2 above on a medium
147 | customarily used for software interchange; or,
148 |
149 | c) Accompany it with the information you received as to the offer
150 | to distribute corresponding source code. (This alternative is
151 | allowed only for noncommercial distribution and only if you
152 | received the program in object code or executable form with such
153 | an offer, in accord with Subsection b above.)
154 |
155 | The source code for a work means the preferred form of the work for
156 | making modifications to it. For an executable work, complete source
157 | code means all the source code for all modules it contains, plus any
158 | associated interface definition files, plus the scripts used to
159 | control compilation and installation of the executable. However, as a
160 | special exception, the source code distributed need not include
161 | anything that is normally distributed (in either source or binary
162 | form) with the major components (compiler, kernel, and so on) of the
163 | operating system on which the executable runs, unless that component
164 | itself accompanies the executable.
165 |
166 | If distribution of executable or object code is made by offering
167 | access to copy from a designated place, then offering equivalent
168 | access to copy the source code from the same place counts as
169 | distribution of the source code, even though third parties are not
170 | compelled to copy the source along with the object code.
171 |
172 | 4. You may not copy, modify, sublicense, or distribute the Program
173 | except as expressly provided under this License. Any attempt
174 | otherwise to copy, modify, sublicense or distribute the Program is
175 | void, and will automatically terminate your rights under this License.
176 | However, parties who have received copies, or rights, from you under
177 | this License will not have their licenses terminated so long as such
178 | parties remain in full compliance.
179 |
180 | 5. You are not required to accept this License, since you have not
181 | signed it. However, nothing else grants you permission to modify or
182 | distribute the Program or its derivative works. These actions are
183 | prohibited by law if you do not accept this License. Therefore, by
184 | modifying or distributing the Program (or any work based on the
185 | Program), you indicate your acceptance of this License to do so, and
186 | all its terms and conditions for copying, distributing or modifying
187 | the Program or works based on it.
188 |
189 | 6. Each time you redistribute the Program (or any work based on the
190 | Program), the recipient automatically receives a license from the
191 | original licensor to copy, distribute or modify the Program subject to
192 | these terms and conditions. You may not impose any further
193 | restrictions on the recipients' exercise of the rights granted herein.
194 | You are not responsible for enforcing compliance by third parties to
195 | this License.
196 |
197 | 7. If, as a consequence of a court judgment or allegation of patent
198 | infringement or for any other reason (not limited to patent issues),
199 | conditions are imposed on you (whether by court order, agreement or
200 | otherwise) that contradict the conditions of this License, they do not
201 | excuse you from the conditions of this License. If you cannot
202 | distribute so as to satisfy simultaneously your obligations under this
203 | License and any other pertinent obligations, then as a consequence you
204 | may not distribute the Program at all. For example, if a patent
205 | license would not permit royalty-free redistribution of the Program by
206 | all those who receive copies directly or indirectly through you, then
207 | the only way you could satisfy both it and this License would be to
208 | refrain entirely from distribution of the Program.
209 |
210 | If any portion of this section is held invalid or unenforceable under
211 | any particular circumstance, the balance of the section is intended to
212 | apply and the section as a whole is intended to apply in other
213 | circumstances.
214 |
215 | It is not the purpose of this section to induce you to infringe any
216 | patents or other property right claims or to contest validity of any
217 | such claims; this section has the sole purpose of protecting the
218 | integrity of the free software distribution system, which is
219 | implemented by public license practices. Many people have made
220 | generous contributions to the wide range of software distributed
221 | through that system in reliance on consistent application of that
222 | system; it is up to the author/donor to decide if he or she is willing
223 | to distribute software through any other system and a licensee cannot
224 | impose that choice.
225 |
226 | This section is intended to make thoroughly clear what is believed to
227 | be a consequence of the rest of this License.
228 |
229 | 8. If the distribution and/or use of the Program is restricted in
230 | certain countries either by patents or by copyrighted interfaces, the
231 | original copyright holder who places the Program under this License
232 | may add an explicit geographical distribution limitation excluding
233 | those countries, so that distribution is permitted only in or among
234 | countries not thus excluded. In such case, this License incorporates
235 | the limitation as if written in the body of this License.
236 |
237 | 9. The Free Software Foundation may publish revised and/or new versions
238 | of the General Public License from time to time. Such new versions will
239 | be similar in spirit to the present version, but may differ in detail to
240 | address new problems or concerns.
241 |
242 | Each version is given a distinguishing version number. If the Program
243 | specifies a version number of this License which applies to it and "any
244 | later version", you have the option of following the terms and conditions
245 | either of that version or of any later version published by the Free
246 | Software Foundation. If the Program does not specify a version number of
247 | this License, you may choose any version ever published by the Free Software
248 | Foundation.
249 |
250 | 10. If you wish to incorporate parts of the Program into other free
251 | programs whose distribution conditions are different, write to the author
252 | to ask for permission. For software which is copyrighted by the Free
253 | Software Foundation, write to the Free Software Foundation; we sometimes
254 | make exceptions for this. Our decision will be guided by the two goals
255 | of preserving the free status of all derivatives of our free software and
256 | of promoting the sharing and reuse of software generally.
257 |
258 | NO WARRANTY
259 |
260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
268 | REPAIR OR CORRECTION.
269 |
270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
278 | POSSIBILITY OF SUCH DAMAGES.
279 |
280 | END OF TERMS AND CONDITIONS
281 |
282 | How to Apply These Terms to Your New Programs
283 |
284 | If you develop a new program, and you want it to be of the greatest
285 | possible use to the public, the best way to achieve this is to make it
286 | free software which everyone can redistribute and change under these terms.
287 |
288 | To do so, attach the following notices to the program. It is safest
289 | to attach them to the start of each source file to most effectively
290 | convey the exclusion of warranty; and each file should have at least
291 | the "copyright" line and a pointer to where the full notice is found.
292 |
293 |
294 | Copyright (C)
295 |
296 | This program is free software; you can redistribute it and/or modify
297 | it under the terms of the GNU General Public License as published by
298 | the Free Software Foundation; either version 2 of the License, or
299 | (at your option) any later version.
300 |
301 | This program is distributed in the hope that it will be useful,
302 | but WITHOUT ANY WARRANTY; without even the implied warranty of
303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
304 | GNU General Public License for more details.
305 |
306 | You should have received a copy of the GNU General Public License along
307 | with this program; if not, write to the Free Software Foundation, Inc.,
308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
309 |
310 | Also add information on how to contact you by electronic and paper mail.
311 |
312 | If the program is interactive, make it output a short notice like this
313 | when it starts in an interactive mode:
314 |
315 | Gnomovision version 69, Copyright (C) year name of author
316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
317 | This is free software, and you are welcome to redistribute it
318 | under certain conditions; type `show c' for details.
319 |
320 | The hypothetical commands `show w' and `show c' should show the appropriate
321 | parts of the General Public License. Of course, the commands you use may
322 | be called something other than `show w' and `show c'; they could even be
323 | mouse-clicks or menu items--whatever suits your program.
324 |
325 | You should also get your employer (if you work as a programmer) or your
326 | school, if any, to sign a "copyright disclaimer" for the program, if
327 | necessary. Here is a sample; alter the names:
328 |
329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program
330 | `Gnomovision' (which makes passes at compilers) written by James Hacker.
331 |
332 | , 1 April 1989
333 | Ty Coon, President of Vice
334 |
335 | This General Public License does not permit incorporating your program into
336 | proprietary programs. If your program is a subroutine library, you may
337 | consider it more useful to permit linking proprietary applications with the
338 | library. If this is what you want to do, use the GNU Lesser General
339 | Public License instead of this License.
--------------------------------------------------------------------------------
/xml.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 |
5 | #include "xml.h"
6 |
7 | #define WHITESPACE " \t\r\n"
8 |
9 | #define TAG_ELEMENT_OPEN 1
10 | #define TAG_ELEMENT_CLOSE 2
11 | #define TAG_PI 3
12 | #define TAG_TYPE 4
13 | #define TAG_COMMENT 5
14 | #define TAG_CDATA 6
15 |
16 | typedef struct xml_state {
17 | /* the root element */
18 | struct xml_element *root;
19 |
20 | /* internal state variables */
21 | struct xml_element *current;
22 | struct xml_tag_pattern *tag;
23 | size_t length;
24 | size_t cursor;
25 | int empty;
26 | const char* data;
27 | const char *(*parser)(struct xml_state *, const char *);
28 | } xml_state;
29 |
30 | struct xml_tag_pattern {
31 | int type;
32 | const char *open;
33 | size_t open_len;
34 | const char *close;
35 | } xml_tag_patterns[] = {
36 | {TAG_ELEMENT_OPEN, "<", 1, ">"},
37 | {TAG_ELEMENT_CLOSE, "", 2, ">"},
38 | {TAG_PI, "", 2, "?>"},
39 | {TAG_TYPE, ""},
40 | {TAG_COMMENT, ""},
41 | {TAG_CDATA, ""},
42 | {0, 0, 0, 0}
43 | };
44 |
45 | /*****************************************************************************
46 | * STRING OPERATIONS
47 | ****************************************************************************/
48 |
49 | /**
50 | * Return length of quoted string respecting escaped characters
51 | *
52 | * @param s - first character after quote
53 | * @param q - quote character
54 | */
55 | static size_t xml_quotedspn(const char *s, char q) {
56 | const char *first = s;
57 | // zero-length attribute e.g. a = ""
58 | if (*s == q)
59 | return 0;
60 |
61 | while (*++s) {
62 | if (*s == '\\') {
63 | ++s;
64 | continue;
65 | }
66 |
67 | if (*s == q) {
68 | return s - first;
69 | }
70 | }
71 |
72 | return 0;
73 | }
74 |
75 | #ifdef WIN32
76 | #include
77 | /**
78 | * Copies n bytes of given string
79 | *
80 | * @param s - string to copy
81 | * @param n - number of bytes
82 | */
83 | static char *strndup(const char *s, size_t n) {
84 | if (!s || !n){
85 | errno = EINVAL;
86 | return NULL;
87 | }
88 |
89 | char *r;
90 | r = calloc(n + 1, sizeof(*r));
91 | if (!r) return NULL;
92 |
93 | if ( r != memcpy(r, s, n)) {
94 | free(r);
95 | return NULL;
96 | }
97 | return r;
98 | }
99 | #endif
100 |
101 | /**
102 | * Append string
103 | *
104 | * @param dest - address of string to append to
105 | * @param dest_len - address of length of string
106 | * @param src - string to append
107 | * @param src_len - length of string to append
108 | */
109 | static char *xml_string_append(
110 | char **dest,
111 | size_t *dest_len,
112 | const char *src,
113 | size_t src_len) {
114 | char *n;
115 |
116 | if (src_len < 1) {
117 | return *dest;
118 | }
119 |
120 | if (!*dest) {
121 | if ((*dest = strndup(src, src_len))) {
122 | *dest_len += src_len;
123 | }
124 | } else if ((n = realloc(*dest, *dest_len + src_len + 1))) {
125 | *dest = n;
126 | n += *dest_len;
127 |
128 | *n = 0;
129 | strncat(n, src, src_len);
130 |
131 | *dest_len += src_len;
132 | }
133 |
134 | return *dest;
135 | }
136 |
137 | /*****************************************************************************
138 | * CREATING AND MODIFYING ELEMENTS
139 | ****************************************************************************/
140 |
141 | /**
142 | * Add child to parent element
143 | *
144 | * @param p - parent element
145 | * @param c - child element
146 | */
147 | static void xml_element_add(
148 | struct xml_element *p,
149 | struct xml_element *c) {
150 | c->parent = p;
151 |
152 | if (p->first_child) {
153 | c->prev = p->last_child;
154 | p->last_child->next = c;
155 | p->last_child = c;
156 | } else {
157 | p->first_child = p->last_child = c;
158 | }
159 |
160 | ++p->child_count;
161 | }
162 |
163 | /**
164 | * Create a new element
165 | *
166 | * @param parent - parent element
167 | */
168 | static struct xml_element *xml_element_create(struct xml_element *parent, int offset) {
169 | struct xml_element *e;
170 |
171 | if (!(e = calloc(1, sizeof(struct xml_element)))) {
172 | return NULL;
173 | }
174 | e->offset = offset;
175 |
176 | if (parent) {
177 | xml_element_add(parent, e);
178 | }
179 |
180 | return e;
181 | }
182 |
183 | /*****************************************************************************
184 | * CREATING AND MODIFYING ATTRIBUTES
185 | ****************************************************************************/
186 |
187 | /**
188 | * Add attribute to element
189 | *
190 | * @param e - element
191 | * @param a - attribute
192 | */
193 | static void xml_attribute_add(
194 | struct xml_element *e,
195 | struct xml_attribute *a) {
196 | if (e->first_attribute) {
197 | a->prev = e->last_attribute;
198 | e->last_attribute->next = a;
199 | e->last_attribute = a;
200 | } else {
201 | e->first_attribute = e->last_attribute = a;
202 | }
203 |
204 | ++e->attribute_count;
205 | }
206 |
207 | /**
208 | * Create a new attribute
209 | *
210 | * @param parent - parent element
211 | */
212 | static struct xml_attribute *xml_attribute_create(
213 | struct xml_element *parent) {
214 | struct xml_attribute *a;
215 |
216 | if (!(a = calloc(1, sizeof(struct xml_attribute)))) {
217 | return NULL;
218 | }
219 |
220 | if (parent) {
221 | xml_attribute_add(parent, a);
222 | }
223 |
224 | return a;
225 | }
226 |
227 | /*****************************************************************************
228 | * APPENDING KEY/VALUE
229 | ****************************************************************************/
230 |
231 | /**
232 | * Append character data to current element
233 | *
234 | * @param st - state
235 | * @param d - begin of data
236 | * @param l - length of data
237 | */
238 | static int xml_value_append(struct xml_state *st, const char *d, size_t l) {
239 | int pos = d - st->data;
240 | if (!st->length &&
241 | !(st->current = xml_element_create(st->current, pos))) {
242 | return -1;
243 | }
244 |
245 | if (!xml_string_append(
246 | &st->current->value,
247 | &st->length,
248 | d,
249 | l)) {
250 | return -1;
251 | }
252 |
253 | return 0;
254 | }
255 |
256 | /**
257 | * Append tag data to current element
258 | *
259 | * @param st - state
260 | * @param d - begin of data
261 | * @param l - length of data
262 | */
263 | static int xml_key_append(struct xml_state *st, const char *d, size_t l) {
264 | if (!st->current) {
265 | return -1;
266 | }
267 |
268 | /* ignore data for closing elements */
269 | if (st->tag->type == TAG_ELEMENT_CLOSE) {
270 | return 0;
271 | }
272 |
273 | /* append start of pattern for special tag types */
274 | if (!st->length &&
275 | st->tag->open_len > 1 &&
276 | !xml_string_append(
277 | &st->current->key,
278 | &st->length,
279 | st->tag->open + 1,
280 | st->tag->open_len - 1)) {
281 | return -1;
282 | }
283 |
284 | if (!xml_string_append(
285 | &st->current->key,
286 | &st->length,
287 | d,
288 | l)) {
289 | return -1;
290 | }
291 |
292 | return 0;
293 | }
294 |
295 | /*****************************************************************************
296 | * PARSING ATTRIBUTES
297 | ****************************************************************************/
298 |
299 | /**
300 | * Parse attributes
301 | *
302 | * @param e - element
303 | * @param from - first character after tag name
304 | */
305 | static int xml_parse_attributes(struct xml_element *e, char *from) {
306 | while (*from) {
307 | struct xml_attribute *a;
308 | size_t p;
309 | char *key = NULL;
310 | char *value = NULL;
311 | size_t key_len = 0;
312 | size_t value_len = 0;
313 |
314 | /* skip leading white space */
315 | from += strspn(from, WHITESPACE);
316 |
317 | /* search for first character that is not part of a name */
318 | p = strcspn(from, "="WHITESPACE);
319 |
320 | if (p < 1) {
321 | break;
322 | }
323 |
324 | key = from;
325 | key_len = p;
326 |
327 | /* move after argument name */
328 | from += p;
329 |
330 | /* skip white space before next control character */
331 | from += strspn(from, WHITESPACE);
332 |
333 | if (*from == '=') {
334 | char q = 0;
335 |
336 | /* move after '=' and skip leading white space */
337 | ++from;
338 | from += strspn(from, WHITESPACE);
339 |
340 | if (!*from) {
341 | break;
342 | } else if (*from == '\'') {
343 | p = xml_quotedspn(++from, (q = '\''));
344 | } else if (*from == '"') {
345 | p = xml_quotedspn(++from, (q = '"'));
346 | } else {
347 | /* argument data is unqouted */
348 | p = strcspn(from, WHITESPACE);
349 | }
350 |
351 | value = from;
352 | value_len = p;
353 |
354 | from += p;
355 |
356 | if (*from == q) {
357 | ++from;
358 | }
359 | }
360 |
361 | if (!(a = xml_attribute_create(e))) {
362 | return -1;
363 | }
364 |
365 | if (key) {
366 | *(key + key_len) = 0;
367 | a->key = key;
368 | }
369 |
370 | if (value) {
371 | *(value + value_len) = 0;
372 | a->value = value;
373 | }
374 | }
375 |
376 | return 0;
377 | }
378 |
379 | /*****************************************************************************
380 | * PARSING ELEMENTS
381 | ****************************************************************************/
382 |
383 | /* forward declarations */
384 | static const char *xml_parse_content(struct xml_state *, const char *);
385 |
386 | /**
387 | * Close element
388 | *
389 | * @param st - state
390 | */
391 | static void xml_close_element(struct xml_state *st, const char* d) {
392 | st->current->length = d - st->data - st->current->offset;
393 | if (st->current->length > 0 && ((d - 1)[0] != '>'))
394 | st->current->length--;
395 | st->current = st->current->parent;
396 | }
397 |
398 | /**
399 | * Close tag
400 | *
401 | * @param st - state
402 | */
403 | static void xml_close_tag(struct xml_state *st) {
404 | st->tag = NULL;
405 | st->length = 0;
406 | st->cursor = 0;
407 | st->empty = 0;
408 | st->parser = xml_parse_content;
409 | }
410 |
411 | /**
412 | * Parse end of tag name for empty element marker
413 | *
414 | * @param st - state
415 | */
416 | static void xml_check_empty(struct xml_state *st) {
417 | char *p = st->current->key + strlen(st->current->key) - 1;
418 |
419 | for (; p >= st->current->key; --p) {
420 | /* ignore trailing white space; this isn't required
421 | * by the spec but probably better to have */
422 | if (strchr(WHITESPACE, *p)) {
423 | continue;
424 | }
425 |
426 | if (*p == '/') {
427 | *p = 0;
428 | st->empty = 1;
429 | }
430 |
431 | break;
432 | }
433 | }
434 |
435 | /**
436 | * Parse tag name
437 | *
438 | * @param st - state
439 | */
440 | static int xml_parse_tag_name(struct xml_state *st) {
441 | char *p = st->current->key;
442 |
443 | xml_check_empty(st);
444 | p += strcspn(p, WHITESPACE);
445 |
446 | if (!*p) {
447 | /* key ends with the name */
448 | return 0;
449 | }
450 |
451 | /* terminate name and skip further white space */
452 | *p++ = 0;
453 | p += strspn(p, WHITESPACE);
454 |
455 | if (*p && xml_parse_attributes(st->current, p)) {
456 | return -1;
457 | }
458 |
459 | return 0;
460 | }
461 |
462 | /**
463 | * Parse tag until terminating pattern
464 | *
465 | * @param st - state
466 | * @param d - XML data
467 | */
468 | static const char *xml_parse_tag_body(struct xml_state *st, const char *d) {
469 | while (*d) {
470 | const char *m = NULL;
471 |
472 | if (!st->cursor) {
473 | /* find first character of terminating pattern */
474 | //m = strchr(d, st->tag->close[0]);
475 |
476 | // Fix special chars e.g > inside attributes
477 | // https://forum.wincmd.ru/viewpost.php?p=135766
478 | int i = 0;
479 | int q = 0;
480 | while (d[i]) {
481 | if (d[i] == st->tag->close[0] && !q) {
482 | m = d + i;
483 | break;
484 | }
485 | q = (q + (d[i] == '"')) % 2;
486 | i++;
487 | }
488 | } else {
489 | /* find next character of terminating pattern */
490 | for (;;) {
491 | if (st->tag->close[st->cursor] == *d) {
492 | m = d;
493 | }
494 |
495 | /* break if next character does match or cursor
496 | * is at the first character */
497 | if (m || !st->cursor) {
498 | break;
499 | }
500 |
501 | /* append leading part of pattern since it
502 | * is data if d doesn't match */
503 | if (xml_key_append(st, st->tag->close, st->cursor)) {
504 | return NULL;
505 | }
506 |
507 | /* if cursor has already moved into a pattern
508 | * but d doesn't match, d may still match the
509 | * beginning of a pattern */
510 | st->cursor = 0;
511 | }
512 |
513 | /* start over if no match was found and cursor
514 | * is at first character of the pattern */
515 | if (!m && !st->cursor) {
516 | continue;
517 | }
518 | }
519 |
520 | if (m) {
521 | /* put data until this match into tag name */
522 | if (!st->cursor && xml_key_append(st, d, m - d)) {
523 | return NULL;
524 | }
525 |
526 | d = ++m;
527 | ++st->cursor;
528 |
529 | /* terminating pattern complete */
530 | if (!st->tag->close[st->cursor]) {
531 | /* append termination pattern for special tag types */
532 | if (st->cursor > 1 &&
533 | !xml_string_append(
534 | &st->current->key,
535 | &st->length,
536 | st->tag->close,
537 | st->cursor - 1)) {
538 | return NULL;
539 | }
540 |
541 | if (st->tag->type == TAG_ELEMENT_OPEN &&
542 | xml_parse_tag_name(st)) {
543 | return NULL;
544 | }
545 |
546 | if (st->tag->type != TAG_ELEMENT_OPEN ||
547 | st->empty) {
548 | xml_close_element(st, d);
549 | }
550 |
551 | xml_close_tag(st);
552 | break;
553 | }
554 | } else {
555 | /* append all the rest */
556 | size_t l = strlen(d);
557 |
558 | if (xml_key_append(st, d, l)) {
559 | return NULL;
560 | }
561 |
562 | /* move to end of data */
563 | d += l;
564 |
565 | break;
566 | }
567 | }
568 |
569 | return d;
570 | }
571 |
572 | /**
573 | * Parse beginning of a tag to determine its type (and terminating pattern)
574 | *
575 | * @param st - state
576 | * @param d - XML data
577 | */
578 | static const char *xml_parse_tag_opening(struct xml_state *st,
579 | const char *d) {
580 | for (; *d; ++d) {
581 | struct xml_tag_pattern *p = xml_tag_patterns;
582 |
583 | /* check character against all opening patterns */
584 | for (;;) {
585 | for (; p->type; ++p) {
586 | if (p->open_len > st->cursor &&
587 | p->open[st->cursor] == *d) {
588 | break;
589 | }
590 | }
591 |
592 | /* break if pattern is found or cursor is at
593 | * first character */
594 | if (p->type || !st->cursor) {
595 | break;
596 | }
597 |
598 | /* if cursor has already moved into a pattern but
599 | * d doesn't match, d may still match the beginning
600 | * of a pattern */
601 | st->cursor = 0;
602 | }
603 |
604 | /* if character doesn't match any pattern anymore
605 | * take the latest matching pattern */
606 | if (!p->type) {
607 | if (!st->tag) {
608 | return NULL;
609 | }
610 |
611 | if (st->length > 0) {
612 | xml_close_element(st, d);
613 | }
614 |
615 | st->length = 0;
616 | st->cursor = 0;
617 | st->parser = xml_parse_tag_body;
618 |
619 | /* create child element */
620 | int offset = d - st->data - st->tag->open_len;
621 | if (st->tag->type != TAG_ELEMENT_CLOSE &&
622 | !(st->current = xml_element_create(st->current, offset))) {
623 | return NULL;
624 | }
625 |
626 | break;
627 | }
628 |
629 | ++st->cursor;
630 | st->tag = p;
631 | }
632 |
633 | return d;
634 | }
635 |
636 | /**
637 | * Parse character data of active element
638 | *
639 | * @param st - state
640 | * @param d - XML data
641 | */
642 | static const char *xml_parse_content(struct xml_state *st, const char *d) {
643 | const char *end = d + strcspn(d, "<");
644 |
645 | if (end > d && xml_value_append(st, d, end - d)) {
646 | return NULL;
647 | }
648 |
649 | if (*end == '<') {
650 | st->parser = xml_parse_tag_opening;
651 | }
652 |
653 | return end;
654 | }
655 |
656 | /**
657 | * Parse (next) chunk of a XML document
658 | *
659 | * @param st - parsing status
660 | * @param d - XML chunk
661 | */
662 | int xml_parse_chunk(struct xml_state *st, const char *d) {
663 | if (!d) {
664 | return -1;
665 | }
666 |
667 | if (!st->root) {
668 | st->current = st->root = xml_element_create(NULL, 0);
669 | st->root->offset = 0;
670 | st->root->length = strlen(d);
671 | }
672 |
673 | if (!st->parser) {
674 | xml_close_tag(st);
675 | }
676 |
677 | while (*d) {
678 | if (!(d = st->parser(st, d))) {
679 | return -1;
680 | }
681 | }
682 |
683 | return 0;
684 | }
685 |
686 | /**
687 | * Parse XML document
688 | *
689 | * @param data - XML string
690 | */
691 | struct xml_element *xml_parse(const char *data) {
692 | struct xml_state st;
693 |
694 | memset(&st, 0, sizeof(st));
695 | st.data = data;
696 |
697 | if (!xml_parse_chunk(&st, data) && st.root) {
698 | return st.root;
699 | }
700 |
701 | return NULL;
702 | }
703 |
704 | /*****************************************************************************
705 | * FREE MEMORY
706 | ****************************************************************************/
707 |
708 | /**
709 | * Free XML element tree
710 | *
711 | * @param e - root element
712 | */
713 | void xml_free(struct xml_element *e) {
714 | struct xml_element *c, *n;
715 |
716 | if (!e) {
717 | return;
718 | }
719 |
720 | for (c = e->first_child; c; c = n) {
721 | n = c->next;
722 | xml_free(c);
723 | }
724 |
725 | /* free attributes */
726 | {
727 | struct xml_attribute *a, *na;
728 |
729 | for (a = e->first_attribute; a; a = na) {
730 | na = a->next;
731 |
732 | /* don't free key/value because they're
733 | * just pointers into e->key */
734 | free(a);
735 | }
736 | }
737 |
738 | free(e->key);
739 | free(e->value);
740 | free(e);
741 | }
742 |
743 | /*****************************************************************************
744 | * CHILD ELEMENT LOCATION
745 | ****************************************************************************/
746 |
747 | /**
748 | * Returns child element by key
749 | *
750 | * @param e - first attribute
751 | * @param key - attribute key
752 | */
753 | struct xml_element *xml_find_element(
754 | struct xml_element *e,
755 | const char *key) {
756 | xml_element* s = e ? e->first_child : NULL;
757 | for (; s; s = s->next) {
758 | if (!s->key || !key){
759 | continue;
760 | }
761 | if (!strcasecmp(s->key, key)) {
762 | return s;
763 | }
764 | }
765 |
766 | return NULL;
767 | }
768 |
769 | /*****************************************************************************
770 | * ATTRIBUTE LOCATION
771 | ****************************************************************************/
772 |
773 | /**
774 | * Returns attribute by key
775 | *
776 | * @param a - first attribute
777 | * @param key - attribute key
778 | */
779 | struct xml_attribute *xml_find_attribute(
780 | struct xml_element *e,
781 | const char *key) {
782 | xml_attribute* a = e ? e->first_attribute : NULL;
783 | for (; a; a = a->next) {
784 | if (!a->key || !key){
785 | continue;
786 | }
787 | if (!strcasecmp(a->key, key)) {
788 | return a;
789 | }
790 | }
791 |
792 | return NULL;
793 | }
794 |
795 | /*****************************************************************************
796 | * CONTENT CONCATENATION
797 | ****************************************************************************/
798 |
799 | /**
800 | * Calculate size of all child values
801 | *
802 | * @param e - element
803 | */
804 | static size_t xml_content_len(struct xml_element *e) {
805 | size_t s = 0;
806 |
807 | for (e = e->first_child; e; e = e->next) {
808 | if (e->value) {
809 | s += strlen(e->value) + (e->next != NULL); // space
810 | } else {
811 | s += xml_content_len(e);
812 | }
813 | }
814 |
815 | return s;
816 | }
817 |
818 | /**
819 | * Copy elements into pre-calculated buffer
820 | *
821 | * @param e - element
822 | * @param t - target
823 | */
824 | static void xml_content_cpy(struct xml_element *e, char **t) {
825 | for (e = e->first_child; e; e = e->next) {
826 | if (e->value) {
827 | int start = strspn(e->value, WHITESPACE);
828 | int len = strlen(e->value + start);
829 | while (len > 0 && strchr(WHITESPACE, (e->value + start)[len - start - 1]))
830 | len--;
831 |
832 | // trim value + space
833 | if (len) {
834 | strncpy(*t, e->value + start, len);
835 | *t += len;
836 | if (e->next) {
837 | **t = ' ';
838 | *t += 1;
839 | }
840 | }
841 | } else {
842 | xml_content_cpy(e, t);
843 | }
844 | }
845 | }
846 |
847 | /**
848 | * Return concatenated content of element and all of its children
849 | *
850 | * @param e - element
851 | */
852 | char *xml_content(struct xml_element *e) {
853 | size_t l;
854 | char *s;
855 | char *t;
856 |
857 | if (!e ||
858 | (l = xml_content_len(e)) < 1 ||
859 | !(s = calloc(l + 1, sizeof(char)))) {
860 | return NULL;
861 | }
862 |
863 | t = s;
864 | xml_content_cpy(e, &t);
865 |
866 | return s;
867 | }
868 |
869 | int xml_path_no (struct xml_element *e) {
870 | int no = 1;
871 | xml_element* n = e->prev;
872 | while (n) {
873 | no += n && n->key ? strcmp(e->key, n->key) == 0 : 0;
874 | n = n->prev;
875 | }
876 |
877 | int isUnique = no == 1;
878 | n = e->next;
879 | while (n && isUnique) {
880 | isUnique = n && n->key ? strcmp(e->key, n->key) != 0 : 1;
881 | n = n->next;
882 | }
883 |
884 | return isUnique ? 0 : no;
885 | }
886 |
887 | char* xml_path (struct xml_element *e) {
888 | int len = 1000;
889 | char* res = calloc(len, sizeof(char));
890 |
891 | if (!e->key) {
892 | char* xpath = xml_path(e->parent);
893 | res = realloc(res, strlen(xpath) + 64);
894 | sprintf(res, "%s/text()", xpath);
895 | return res;
896 | }
897 |
898 | do {
899 | int no = xml_path_no(e);
900 |
901 | if (e->key) {
902 | if (strlen(e->key) + strlen(res) < 100) {
903 | len += 1000;
904 | res = realloc(res, len);
905 | }
906 |
907 | char* buf = calloc(strlen(res) + strlen(e->key) + 64, sizeof(char));
908 | if (no)
909 | sprintf(buf, "/%s[%i]%s", e->key, no, res);
910 | else
911 | sprintf(buf, "/%s%s", e->key, res);
912 |
913 | strcpy(res, buf);
914 | free(buf);
915 | }
916 |
917 | e = e->parent;
918 | } while (e);
919 |
920 | return res;
921 | }
922 |
--------------------------------------------------------------------------------
/main.c:
--------------------------------------------------------------------------------
1 | #define UNICODE
2 | #define _UNICODE
3 |
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 | #include
11 | #include
12 | #include
13 | #include
14 | #include
15 | #include
16 | #include "xml.h"
17 |
18 | #define LVS_EX_AUTOSIZECOLUMNS 0x10000000
19 |
20 | #define WMU_UPDATE_GRID WM_USER + 1
21 | #define WMU_UPDATE_RESULTSET WM_USER + 2
22 | #define WMU_UPDATE_FILTER_SIZE WM_USER + 3
23 | #define WMU_SET_HEADER_FILTERS WM_USER + 4
24 | #define WMU_AUTO_COLUMN_SIZE WM_USER + 5
25 | #define WMU_SET_CURRENT_CELL WM_USER + 6
26 | #define WMU_RESET_CACHE WM_USER + 7
27 | #define WMU_SET_FONT WM_USER + 8
28 | #define WMU_SET_THEME WM_USER + 9
29 | #define WMU_UPDATE_TEXT WM_USER + 10
30 | #define WMU_UPDATE_HIGHLIGHT WM_USER + 11
31 | #define WMU_SWITCH_TAB WM_USER + 12
32 | #define WMU_HIDE_COLUMN WM_USER + 13
33 | #define WMU_SHOW_COLUMNS WM_USER + 14
34 | #define WMU_SORT_COLUMN WM_USER + 15
35 | #define WMU_HOT_KEYS WM_USER + 16
36 | #define WMU_HOT_CHARS WM_USER + 17
37 |
38 | #define IDC_MAIN 100
39 | #define IDC_TREE 101
40 | #define IDC_TAB 102
41 | #define IDC_GRID 103
42 | #define IDC_TEXT 104
43 | #define IDC_STATUSBAR 105
44 | #define IDC_HEADER_EDIT 1000
45 |
46 | #define IDM_COPY_CELL 5000
47 | #define IDM_COPY_ROWS 5001
48 | #define IDM_COPY_COLUMN 5002
49 | #define IDM_FILTER_ROW 5003
50 | #define IDM_DARK_THEME 5004
51 | #define IDM_COPY_TEXT 5010
52 | #define IDM_SELECTALL 5011
53 | #define IDM_FORMAT 5012
54 | #define IDM_LOCATE 5013
55 | #define IDM_COPY_XPATH 5020
56 | #define IDM_HIDE_COLUMN 5021
57 | #define IDM_SHOW_SAME 5022
58 |
59 | #define SB_VERSION 0
60 | #define SB_CODEPAGE 1
61 | #define SB_RESERVED 2
62 | #define SB_MODE 3
63 | #define SB_ROW_COUNT 4
64 | #define SB_CURRENT_CELL 5
65 | #define SB_AUXILIARY 6
66 |
67 | #define SPLITTER_WIDTH 5
68 | #define MAX_LENGTH 4096
69 | #define MAX_COLUMN_LENGTH 2000
70 | #define APP_NAME TEXT("xmltab")
71 | #define APP_VERSION TEXT("1.0.8")
72 | #define LOADING TEXT("Loading...")
73 | #define WHITESPACE " \t\r\n"
74 |
75 | #define XML_TEXT "#TEXT"
76 | #define XML_COMMENT "#COMMENT"
77 | #define XML_CDATA "#CDATA"
78 |
79 | #define CP_UTF16LE 1200
80 | #define CP_UTF16BE 1201
81 |
82 | #define LCS_FINDFIRST 1
83 | #define LCS_MATCHCASE 2
84 | #define LCS_WHOLEWORDS 4
85 | #define LCS_BACKWARDS 8
86 |
87 | typedef struct {
88 | int size;
89 | DWORD PluginInterfaceVersionLow;
90 | DWORD PluginInterfaceVersionHi;
91 | char DefaultIniName[MAX_PATH];
92 | } ListDefaultParamStruct;
93 |
94 | static TCHAR iniPath[MAX_PATH] = {0};
95 |
96 | LRESULT CALLBACK cbNewMain (HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
97 | LRESULT CALLBACK cbHotKey(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
98 | LRESULT CALLBACK cbNewHeader(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
99 | LRESULT CALLBACK cbNewFilterEdit(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
100 | LRESULT CALLBACK cbNewTab(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
101 | LRESULT CALLBACK cbNewText(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
102 |
103 | HTREEITEM addNode(HWND hTreeWnd, HTREEITEM hParentItem, xml_element* val);
104 | void showNode(HWND hTreeWnd, xml_element* node);
105 | void highlightText(HWND hWnd, TCHAR* text);
106 | char* formatXML(const char* data);
107 | HWND getMainWindow(HWND hWnd);
108 | void setStoredValue(TCHAR* name, int value);
109 | int getStoredValue(TCHAR* name, int defValue);
110 | TCHAR* getStoredString(TCHAR* name, TCHAR* defValue);
111 | int CALLBACK cbEnumTabStopChildren (HWND hWnd, LPARAM lParam);
112 | TCHAR* utf8to16(const char* in);
113 | char* utf16to8(const TCHAR* in);
114 | int findString(TCHAR* text, TCHAR* word, BOOL isMatchCase, BOOL isWholeWords);
115 | int findString8(char* text, char* word, BOOL isMatchCase, BOOL isWholeWords);
116 | BOOL hasString (const TCHAR* str, const TCHAR* sub, BOOL isCaseSensitive);
117 | TCHAR* extractUrl(TCHAR* data);
118 | int detectCodePage(const unsigned char *data);
119 | void setClipboardText(const TCHAR* text);
120 | BOOL isNumber(const TCHAR* val);
121 | BOOL isEmpty (const char* s);
122 | BOOL isUtf8(const char * string);
123 | void mergeSort(int indexes[], void* data, int l, int r, BOOL isBackward, BOOL isNums);
124 | HTREEITEM TreeView_AddItem (HWND hTreeWnd, TCHAR* caption, HTREEITEM parent, LPARAM lParam);
125 | int TreeView_GetItemText(HWND hTreeWnd, HTREEITEM hItem, TCHAR* buf, int maxLen);
126 | LPARAM TreeView_GetItemParam(HWND hTreeWnd, HTREEITEM hItem);
127 | int TreeView_SetItemText(HWND hTreeWnd, HTREEITEM hItem, TCHAR* text);
128 | int ListView_AddColumn(HWND hListWnd, TCHAR* colName);
129 | int Header_GetItemText(HWND hWnd, int i, TCHAR* pszText, int cchTextMax);
130 | void Menu_SetItemState(HMENU hMenu, UINT wID, UINT fState);
131 |
132 | BOOL APIENTRY DllMain (HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
133 | if (ul_reason_for_call == DLL_PROCESS_ATTACH && iniPath[0] == 0) {
134 | TCHAR path[MAX_PATH + 1] = {0};
135 | GetModuleFileName(hModule, path, MAX_PATH);
136 | TCHAR* dot = _tcsrchr(path, TEXT('.'));
137 | _tcsncpy(dot, TEXT(".ini"), 5);
138 | if (_taccess(path, 0) == 0)
139 | _tcscpy(iniPath, path);
140 | }
141 | return TRUE;
142 | }
143 |
144 | void __stdcall ListGetDetectString(char* DetectString, int maxlen) {
145 | TCHAR* detectString16 = getStoredString(TEXT("detect-string"), TEXT("MULTIMEDIA & ext=\"XML\""));
146 | char* detectString8 = utf16to8(detectString16);
147 | snprintf(DetectString, maxlen, detectString8);
148 | free(detectString16);
149 | free(detectString8);
150 | }
151 |
152 | void __stdcall ListSetDefaultParams(ListDefaultParamStruct* dps) {
153 | if (iniPath[0] == 0) {
154 | DWORD size = MultiByteToWideChar(CP_ACP, 0, dps->DefaultIniName, -1, NULL, 0);
155 | MultiByteToWideChar(CP_ACP, 0, dps->DefaultIniName, -1, iniPath, size);
156 | }
157 | }
158 |
159 | int __stdcall ListSearchTextW(HWND hWnd, TCHAR* searchString, int searchParameter) {
160 | HWND hTabWnd = GetDlgItem(hWnd, IDC_TAB);
161 | HWND hTreeWnd = GetDlgItem(hWnd, IDC_TREE);
162 |
163 | BOOL isFindFirst = searchParameter & LCS_FINDFIRST;
164 | BOOL isBackward = searchParameter & LCS_BACKWARDS;
165 | BOOL isMatchCase = searchParameter & LCS_MATCHCASE;
166 | BOOL isWholeWords = searchParameter & LCS_WHOLEWORDS;
167 |
168 | if (GetFocus() == hTreeWnd) {
169 | if (isBackward) {
170 | MessageBox(hWnd, TEXT("Backward search is unsupported"), NULL, MB_OK);
171 | return 0;
172 | }
173 |
174 | xml_element* xml = (xml_element*)GetProp(hWnd, TEXT("XML"));
175 | HTREEITEM hItem = TreeView_GetSelection(hTreeWnd);
176 | xml_element* from = (xml_element*)TreeView_GetItemParam(hTreeWnd, hItem);
177 |
178 | char* searchString8 = utf16to8(searchString);
179 | char* xml8 = (char*)GetProp(hWnd, TEXT("DATA"));
180 | int offset[] = {
181 | from->offset + strlen(from->key ? from->key : ""),
182 | from->offset + from->length
183 | };
184 | int pos[] = {
185 | offset[0] + findString8(xml8 + offset[0], searchString8, isMatchCase, isWholeWords),
186 | offset[1] + findString8(xml8 + offset[1], searchString8, isMatchCase, isWholeWords)
187 | };
188 | free(searchString8);
189 |
190 | if (pos[0] == offset[0] - 1)
191 | return 0;
192 |
193 | xml_element* node = 0;
194 | for (int i = 0; i < 2; i++) {
195 | node = xml;
196 | xml_element* prev = 0;
197 | while (node && prev != node) {
198 | prev = node;
199 |
200 | xml_element* next = node->next;
201 | while (next && (next->offset < pos[i])) {
202 | if(next->key)
203 | node = next;
204 |
205 | next = next->next;
206 | }
207 |
208 | xml_element* subnode = node->first_child;
209 | while (subnode && (subnode->offset < pos[i])) {
210 | if (subnode->key)
211 | node = subnode;
212 |
213 | subnode = subnode->next;
214 | }
215 | }
216 |
217 | if (node != from)
218 | break;
219 | }
220 |
221 | showNode(hTreeWnd, node);
222 | TreeView_SelectItem(hTreeWnd, (HTREEITEM)(node ? node->userdata : 0));
223 | } else if (TabCtrl_GetCurSel(hTabWnd) == 1) {
224 | HWND hTextWnd = GetDlgItem(hTabWnd, IDC_TEXT);
225 | DWORD len = _tcslen(searchString);
226 | DWORD spos = 0;
227 | SendMessage(hTextWnd, EM_GETSEL, 0, (LPARAM)&spos);
228 | int mode = 0;
229 | FINDTEXTEXW ft = {{spos, -1}, searchString, {0, 0}};
230 | if (searchParameter & LCS_MATCHCASE)
231 | mode |= FR_MATCHCASE;
232 | if (searchParameter & LCS_WHOLEWORDS)
233 | mode |= FR_WHOLEWORD;
234 | if (!(searchParameter & LCS_BACKWARDS))
235 | mode |= FR_DOWN;
236 | else
237 | ft.chrg.cpMin = ft.chrg.cpMin > len ? ft.chrg.cpMin - len : ft.chrg.cpMin;
238 |
239 | int pos = SendMessage(hTextWnd, EM_FINDTEXTEXW, mode, (LPARAM)&ft);
240 | if (pos != -1)
241 | SendMessage(hTextWnd, EM_SETSEL, pos, pos + len);
242 | else
243 | MessageBeep(0);
244 | SetFocus(hTextWnd);
245 | } else {
246 | HWND hGridWnd = GetDlgItem(hTabWnd, IDC_GRID);
247 | HWND hStatusWnd = GetDlgItem(hWnd, IDC_STATUSBAR);
248 |
249 | TCHAR*** cache = (TCHAR***)GetProp(hWnd, TEXT("CACHE"));
250 | int* resultset = (int*)GetProp(hWnd, TEXT("RESULTSET"));
251 | int rowCount = *(int*)GetProp(hWnd, TEXT("ROWCOUNT"));
252 | int colCount = Header_GetItemCount(ListView_GetHeader(hGridWnd));
253 | if (!resultset || rowCount == 0)
254 | return 0;
255 |
256 | if (isFindFirst) {
257 | *(int*)GetProp(hWnd, TEXT("CURRENTCOLNO")) = 0;
258 | *(int*)GetProp(hWnd, TEXT("SEARCHCELLPOS")) = 0;
259 | *(int*)GetProp(hWnd, TEXT("CURRENTROWNO")) = isBackward ? rowCount - 1 : 0;
260 | }
261 |
262 | int rowNo = *(int*)GetProp(hWnd, TEXT("CURRENTROWNO"));
263 | int colNo = *(int*)GetProp(hWnd, TEXT("CURRENTCOLNO"));
264 | int *pStartPos = (int*)GetProp(hWnd, TEXT("SEARCHCELLPOS"));
265 | rowNo = rowNo == -1 || rowNo >= rowCount ? 0 : rowNo;
266 | colNo = colNo == -1 || colNo >= colCount ? 0 : colNo;
267 |
268 | int pos = -1;
269 | do {
270 | for (; (pos == -1) && colNo < colCount; colNo++) {
271 | pos = findString(cache[resultset[rowNo]][colNo] + *pStartPos, searchString, isMatchCase, isWholeWords);
272 | if (pos != -1)
273 | pos += *pStartPos;
274 | *pStartPos = pos == -1 ? 0 : pos + *pStartPos + _tcslen(searchString);
275 | }
276 | colNo = pos != -1 ? colNo - 1 : 0;
277 | rowNo += pos != -1 ? 0 : isBackward ? -1 : 1;
278 | } while ((pos == -1) && (isBackward ? rowNo > 0 : rowNo < rowCount));
279 | ListView_SetItemState(hGridWnd, -1, 0, LVIS_SELECTED | LVIS_FOCUSED);
280 |
281 | TCHAR buf[256] = {0};
282 | if (pos != -1) {
283 | ListView_EnsureVisible(hGridWnd, rowNo, FALSE);
284 | ListView_SetItemState(hGridWnd, rowNo, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED);
285 |
286 | TCHAR* val = cache[resultset[rowNo]][colNo];
287 | int len = _tcslen(searchString);
288 | _sntprintf(buf, 255, TEXT("%ls%.*ls%ls"),
289 | pos > 0 ? TEXT("...") : TEXT(""),
290 | len + pos + 10, val + pos,
291 | _tcslen(val + pos + len) > 10 ? TEXT("...") : TEXT(""));
292 | SendMessage(hWnd, WMU_SET_CURRENT_CELL, rowNo, colNo);
293 | } else {
294 | MessageBox(hWnd, searchString, TEXT("Not found:"), MB_OK);
295 | }
296 | SendMessage(hStatusWnd, SB_SETTEXT, SB_AUXILIARY, (LPARAM)buf);
297 | SetFocus(hGridWnd);
298 | }
299 |
300 | return 0;
301 | }
302 |
303 | int __stdcall ListSearchText(HWND hWnd, char* searchString, int searchParameter) {
304 | DWORD len = MultiByteToWideChar(CP_ACP, 0, searchString, -1, NULL, 0);
305 | TCHAR* searchString16 = (TCHAR*)calloc (len, sizeof (TCHAR));
306 | MultiByteToWideChar(CP_ACP, 0, searchString, -1, searchString16, len);
307 | int rc = ListSearchTextW(hWnd, searchString16, searchParameter);
308 | free(searchString16);
309 | return rc;
310 | }
311 |
312 | HWND APIENTRY ListLoadW (HWND hListerWnd, TCHAR* fileToLoad, int showFlags) {
313 | int maxFileSize = getStoredValue(TEXT("max-file-size"), 100000000);
314 | struct _stat st = {0};
315 | if (_tstat(fileToLoad, &st) != 0 || maxFileSize > 0 && st.st_size > maxFileSize || st.st_size < 4)
316 | return 0;
317 |
318 | char* data = calloc(st.st_size + 1, sizeof(char));
319 | FILE *f = _tfopen(fileToLoad, TEXT("rb"));
320 | fread(data, sizeof(char), st.st_size, f);
321 | fclose(f);
322 |
323 | int cp = detectCodePage(data);
324 | if (cp == CP_UTF16BE) {
325 | for (int i = 0; i < st.st_size/2; i++) {
326 | int c = data[2 * i];
327 | data[2 * i] = data[2 * i + 1];
328 | data[2 * i + 1] = c;
329 | }
330 | }
331 |
332 | if (cp == CP_UTF16LE || cp == CP_UTF16BE) {
333 | char* data8 = utf16to8((TCHAR*)data);
334 | free(data);
335 | data = data8;
336 | }
337 |
338 | if (cp == CP_ACP) {
339 | DWORD len = MultiByteToWideChar(CP_ACP, 0, data, -1, NULL, 0);
340 | TCHAR* data16 = (TCHAR*)calloc (len, sizeof (TCHAR));
341 | MultiByteToWideChar(CP_ACP, 0, data, -1, data16, len);
342 | free(data);
343 | char* data8 = utf16to8((TCHAR*)data16);
344 | data = data8;
345 | free(data16);
346 | }
347 |
348 | struct xml_element *xml = xml_parse(data);
349 |
350 | // If doc doesn't have a root, add it
351 | int nCount = 0;
352 | if (xml) {
353 | xml_element *node = xml->first_child;
354 | while (node) {
355 | nCount += node->key && strchr("?! ", node->key[0]) == 0;
356 | node = node->next;
357 | }
358 | }
359 |
360 | if (nCount > 1) {
361 | char* data2 = calloc(st.st_size + 64, sizeof(char));
362 | sprintf(data2, "%s", data);
363 | free(data);
364 | xml_free(xml);
365 |
366 | data = data2;
367 | xml = xml_parse(data);
368 | }
369 |
370 | if (!xml) {
371 | free(data);
372 | return 0;
373 | }
374 |
375 | INITCOMMONCONTROLSEX icex;
376 | icex.dwSize = sizeof(icex);
377 | icex.dwICC = ICC_LISTVIEW_CLASSES | ICC_TREEVIEW_CLASSES;
378 | InitCommonControlsEx(&icex);
379 | LoadLibrary(TEXT("msftedit.dll"));
380 |
381 | setlocale(LC_CTYPE, "");
382 |
383 | BOOL isStandalone = GetParent(hListerWnd) == HWND_DESKTOP;
384 | HWND hMainWnd = CreateWindowEx(WS_EX_CONTROLPARENT, WC_STATIC, APP_NAME, WS_CHILD | (isStandalone ? SS_SUNKEN : 0),
385 | 0, 0, 100, 100, hListerWnd, (HMENU)IDC_MAIN, GetModuleHandle(0), NULL);
386 |
387 | SetProp(hMainWnd, TEXT("WNDPROC"), (HANDLE)SetWindowLongPtr(hMainWnd, GWLP_WNDPROC, (LONG_PTR)&cbNewMain));
388 | SetProp(hMainWnd, TEXT("FILTERROW"), calloc(1, sizeof(int)));
389 | SetProp(hMainWnd, TEXT("XML"), xml);
390 | SetProp(hMainWnd, TEXT("DATA"), data);
391 | SetProp(hMainWnd, TEXT("CACHE"), 0);
392 | SetProp(hMainWnd, TEXT("RESULTSET"), 0);
393 | SetProp(hMainWnd, TEXT("XMLNODES"), 0);
394 | SetProp(hMainWnd, TEXT("ROWCOUNT"), calloc(1, sizeof(int)));
395 | SetProp(hMainWnd, TEXT("TOTALROWCOUNT"), calloc(1, sizeof(int)));
396 | SetProp(hMainWnd, TEXT("ORDERBY"), calloc(1, sizeof(int)));
397 | SetProp(hMainWnd, TEXT("CURRENTROWNO"), calloc(1, sizeof(int)));
398 | SetProp(hMainWnd, TEXT("CURRENTCOLNO"), calloc(1, sizeof(int)));
399 | SetProp(hMainWnd, TEXT("SEARCHCELLPOS"), calloc(1, sizeof(int)));
400 | SetProp(hMainWnd, TEXT("SPLITTERPOSITION"), calloc(1, sizeof(int)));
401 | SetProp(hMainWnd, TEXT("ISFORMAT"), calloc(1, sizeof(int)));
402 | SetProp(hMainWnd, TEXT("FONT"), 0);
403 | SetProp(hMainWnd, TEXT("FONTFAMILY"), getStoredString(TEXT("font"), TEXT("Arial")));
404 | SetProp(hMainWnd, TEXT("FONTSIZE"), calloc(1, sizeof(int)));
405 | SetProp(hMainWnd, TEXT("FILTERALIGN"), calloc(1, sizeof(int)));
406 | SetProp(hMainWnd, TEXT("MAXHIGHLIGHTLENGTH"), calloc(1, sizeof(int)));
407 | SetProp(hMainWnd, TEXT("SAMEFILTER"), calloc(255, sizeof(TCHAR)));
408 |
409 | SetProp(hMainWnd, TEXT("DARKTHEME"), calloc(1, sizeof(int)));
410 | SetProp(hMainWnd, TEXT("TEXTCOLOR"), calloc(1, sizeof(int)));
411 | SetProp(hMainWnd, TEXT("BACKCOLOR"), calloc(1, sizeof(int)));
412 | SetProp(hMainWnd, TEXT("BACKCOLOR2"), calloc(1, sizeof(int)));
413 | SetProp(hMainWnd, TEXT("FILTERTEXTCOLOR"), calloc(1, sizeof(int)));
414 | SetProp(hMainWnd, TEXT("FILTERBACKCOLOR"), calloc(1, sizeof(int)));
415 | SetProp(hMainWnd, TEXT("CURRENTCELLCOLOR"), calloc(1, sizeof(int)));
416 | SetProp(hMainWnd, TEXT("SELECTIONTEXTCOLOR"), calloc(1, sizeof(int)));
417 | SetProp(hMainWnd, TEXT("SELECTIONBACKCOLOR"), calloc(1, sizeof(int)));
418 | SetProp(hMainWnd, TEXT("SPLITTERCOLOR"), calloc(1, sizeof(int)));
419 | SetProp(hMainWnd, TEXT("XMLTEXTCOLOR"), calloc(1, sizeof(int)));
420 | SetProp(hMainWnd, TEXT("XMLTAGCOLOR"), calloc(1, sizeof(int)));
421 | SetProp(hMainWnd, TEXT("XMLSTRINGCOLOR"), calloc(1, sizeof(int)));
422 | SetProp(hMainWnd, TEXT("XMLVALUECOLOR"), calloc(1, sizeof(int)));
423 | SetProp(hMainWnd, TEXT("XMLCDATACOLOR"), calloc(1, sizeof(int)));
424 | SetProp(hMainWnd, TEXT("XMLCOMMENTCOLOR"), calloc(1, sizeof(int)));
425 |
426 | *(int*)GetProp(hMainWnd, TEXT("SPLITTERPOSITION")) = getStoredValue(TEXT("splitter-position"), 200);
427 | *(int*)GetProp(hMainWnd, TEXT("FONTSIZE")) = getStoredValue(TEXT("font-size"), 16);
428 | *(int*)GetProp(hMainWnd, TEXT("ISFORMAT")) = getStoredValue(TEXT("format"), 1);
429 | *(int*)GetProp(hMainWnd, TEXT("MAXHIGHLIGHTLENGTH")) = getStoredValue(TEXT("max-highlight-length"), 64000);
430 | *(int*)GetProp(hMainWnd, TEXT("FILTERROW")) = getStoredValue(TEXT("filter-row"), 1);
431 | *(int*)GetProp(hMainWnd, TEXT("DARKTHEME")) = getStoredValue(TEXT("dark-theme"), 0);
432 | *(int*)GetProp(hMainWnd, TEXT("FILTERALIGN")) = getStoredValue(TEXT("filter-align"), 0);
433 |
434 | HWND hStatusWnd = CreateStatusWindow(WS_CHILD | WS_VISIBLE | (isStandalone ? SBARS_SIZEGRIP : 0), NULL, hMainWnd, IDC_STATUSBAR);
435 | HDC hDC = GetDC(hMainWnd);
436 | float z = GetDeviceCaps(hDC, LOGPIXELSX) / 96.0; // 96 = 100%, 120 = 125%, 144 = 150%
437 | ReleaseDC(hMainWnd, hDC);
438 | int sizes[7] = {35 * z, 95 * z, 150 * z, 200 * z, 400 * z, 500 * z, -1};
439 | SendMessage(hStatusWnd, SB_SETPARTS, 7, (LPARAM)&sizes);
440 |
441 | HWND hTreeWnd = CreateWindow(WC_TREEVIEW, NULL, WS_VISIBLE | WS_CHILD | TVS_HASBUTTONS | TVS_HASLINES | TVS_SHOWSELALWAYS | WS_TABSTOP,
442 | 0, 0, 100, 100, hMainWnd, (HMENU)IDC_TREE, GetModuleHandle(0), NULL);
443 | SetProp(hTreeWnd, TEXT("WNDPROC"), (HANDLE)SetWindowLongPtr(hTreeWnd, GWLP_WNDPROC, (LONG_PTR)cbHotKey));
444 |
445 | HWND hTabWnd = CreateWindow(WC_TABCONTROL, NULL, WS_CHILD | WS_CLIPSIBLINGS | WS_VISIBLE | WS_TABSTOP, 100, 100, 100, 100,
446 | hMainWnd, (HMENU)IDC_TAB, GetModuleHandle(0), NULL);
447 | SetProp(hTabWnd, TEXT("WNDPROC"), (HANDLE)SetWindowLongPtr(hTabWnd, GWLP_WNDPROC, (LONG_PTR)cbNewTab));
448 |
449 | TCITEM tci;
450 | tci.mask = TCIF_TEXT | TCIF_IMAGE;
451 | tci.iImage = -1;
452 | tci.pszText = TEXT("Grid");
453 | tci.cchTextMax = 5;
454 | TabCtrl_InsertItem(hTabWnd, 0, &tci);
455 |
456 | tci.pszText = TEXT("Text");
457 | tci.cchTextMax = 5;
458 | TabCtrl_InsertItem(hTabWnd, 1, &tci);
459 |
460 | int tabNo = getStoredValue(TEXT("tab-no"), 0);
461 | HWND hGridWnd = CreateWindow(WC_LISTVIEW, NULL, (tabNo == 0 ? WS_VISIBLE : 0) | WS_CHILD | LVS_REPORT | LVS_SHOWSELALWAYS | LVS_OWNERDATA | WS_TABSTOP,
462 | 205, 0, 100, 100, hTabWnd, (HMENU)IDC_GRID, GetModuleHandle(0), NULL);
463 |
464 | int noLines = getStoredValue(TEXT("disable-grid-lines"), 0);
465 | ListView_SetExtendedListViewStyle(hGridWnd, LVS_EX_FULLROWSELECT | LVS_EX_DOUBLEBUFFER | (noLines ? 0 : LVS_EX_GRIDLINES) | LVS_EX_LABELTIP | LVS_EX_HEADERDRAGDROP);
466 | SetProp(hGridWnd, TEXT("WNDPROC"), (HANDLE)SetWindowLongPtr(hGridWnd, GWLP_WNDPROC, (LONG_PTR)cbHotKey));
467 |
468 | HWND hHeader = ListView_GetHeader(hGridWnd);
469 | LONG_PTR styles = GetWindowLongPtr(hHeader, GWL_STYLE);
470 | SetWindowLongPtr(hHeader, GWL_STYLE, styles | HDS_FILTERBAR);
471 | SetWindowTheme(hHeader, TEXT(" "), TEXT(" "));
472 | SetProp(hHeader, TEXT("WNDPROC"), (HANDLE)SetWindowLongPtr(hHeader, GWLP_WNDPROC, (LONG_PTR)cbNewHeader));
473 |
474 | HWND hTextWnd = CreateWindowEx(0, TEXT("RICHEDIT50W"), NULL, (tabNo == 1 ? WS_VISIBLE : 0) | WS_CHILD | ES_MULTILINE | ES_AUTOVSCROLL | ES_AUTOHSCROLL | ES_WANTRETURN | WS_VSCROLL | WS_HSCROLL | WS_TABSTOP | ES_NOHIDESEL | ES_READONLY,
475 | 0, 0, 100, 100, hTabWnd, (HMENU)IDC_TEXT, GetModuleHandle(0), NULL);
476 | SetProp(hTextWnd, TEXT("WNDPROC"), (HANDLE)SetWindowLongPtr(hTextWnd, GWLP_WNDPROC, (LONG_PTR)cbNewText));
477 | TabCtrl_SetCurSel(hTabWnd, tabNo);
478 |
479 | HMENU hTreeMenu = CreatePopupMenu();
480 | AppendMenu(hTreeMenu, MF_STRING, IDM_COPY_XPATH, TEXT("Copy XPath"));
481 | AppendMenu(hTreeMenu, MF_STRING, IDM_SHOW_SAME, TEXT("Show the same siblings"));
482 | SetProp(hMainWnd, TEXT("TREEMENU"), hTreeMenu);
483 |
484 | HMENU hGridMenu = CreatePopupMenu();
485 | AppendMenu(hGridMenu, MF_STRING, IDM_COPY_CELL, TEXT("Copy cell"));
486 | AppendMenu(hGridMenu, MF_STRING, IDM_COPY_ROWS, TEXT("Copy row(s)"));
487 | AppendMenu(hGridMenu, MF_STRING, IDM_COPY_COLUMN, TEXT("Copy column"));
488 | AppendMenu(hGridMenu, MF_STRING, IDM_COPY_XPATH, TEXT("Copy XPath"));
489 | AppendMenu(hGridMenu, MF_STRING, 0, NULL);
490 | AppendMenu(hGridMenu, MF_STRING, IDM_HIDE_COLUMN, TEXT("Hide column"));
491 | AppendMenu(hGridMenu, MF_STRING, 0, NULL);
492 | AppendMenu(hGridMenu, (*(int*)GetProp(hMainWnd, TEXT("FILTERROW")) != 0 ? MF_CHECKED : 0) | MF_STRING, IDM_FILTER_ROW, TEXT("Filters"));
493 | AppendMenu(hGridMenu, (*(int*)GetProp(hMainWnd, TEXT("DARKTHEME")) != 0 ? MF_CHECKED : 0) | MF_STRING, IDM_DARK_THEME, TEXT("Dark theme"));
494 | SetProp(hMainWnd, TEXT("GRIDMENU"), hGridMenu);
495 |
496 | HMENU hTextMenu = CreatePopupMenu();
497 | AppendMenu(hTextMenu, MF_STRING, IDM_COPY_TEXT, TEXT("Copy"));
498 | AppendMenu(hTextMenu, MF_STRING, IDM_SELECTALL, TEXT("Select all"));
499 | AppendMenu(hTextMenu, MF_STRING, 0, NULL);
500 | AppendMenu(hTextMenu, MF_STRING | (*(int*)GetProp(hMainWnd, TEXT("ISFORMAT")) != 0 ? MF_CHECKED : 0), IDM_FORMAT, TEXT("Format"));
501 | AppendMenu(hTextMenu, MF_STRING, IDM_LOCATE, TEXT("Locate"));
502 | AppendMenu(hTextMenu, MF_STRING, 0, NULL);
503 | AppendMenu(hTextMenu, (*(int*)GetProp(hMainWnd, TEXT("DARKTHEME")) != 0 ? MF_CHECKED : 0) | MF_STRING, IDM_DARK_THEME, TEXT("Dark theme"));
504 | SetProp(hMainWnd, TEXT("TEXTMENU"), hTextMenu);
505 |
506 | TCHAR buf[255];
507 | _sntprintf(buf, 32, TEXT(" %ls"), APP_VERSION);
508 | SendMessage(hStatusWnd, SB_SETTEXT, SB_VERSION, (LPARAM)buf);
509 | SendMessage(hStatusWnd, SB_SETTEXT, SB_CODEPAGE,
510 | (LPARAM)(cp == CP_UTF8 ? TEXT(" UTF-8") : cp == CP_UTF16LE ? TEXT(" UTF-16LE") : cp == CP_UTF16BE ? TEXT(" UTF-16BE") : TEXT(" ANSI")));
511 |
512 | xml_element* node = xml->last_child;
513 | while (node) {
514 | HTREEITEM hItem = addNode(hTreeWnd, TVI_ROOT, node);
515 | node->userdata = (void*)hItem;
516 | TreeView_Expand(hTreeWnd, hItem, TVE_EXPAND);
517 | node = node->prev;
518 | }
519 |
520 | SendMessage(hMainWnd, WMU_SET_FONT, 0, 0);
521 | SendMessage(hMainWnd, WMU_SET_THEME, 0, 0);
522 | HTREEITEM hItem = TreeView_GetNextItem(hTreeWnd, TVI_ROOT, TVGN_CHILD);
523 | if (getStoredValue(TEXT("open-first-element"), 1)) {
524 | xml_element* node = (xml_element*)TreeView_GetItemParam(hTreeWnd, hItem);
525 | while(node && (!node->key || node->key[0] == '?' || node->key[0] == '!'))
526 | node = node->next;
527 |
528 | if (node)
529 | hItem = (HTREEITEM)node->userdata;
530 | }
531 | TreeView_Select(hTreeWnd, hItem, TVGN_CARET);
532 | ShowWindow(hMainWnd, SW_SHOW);
533 | SetFocus(hTreeWnd);
534 |
535 | return hMainWnd;
536 | }
537 |
538 | HWND APIENTRY ListLoad (HWND hListerWnd, char* fileToLoad, int showFlags) {
539 | DWORD size = MultiByteToWideChar(CP_ACP, 0, fileToLoad, -1, NULL, 0);
540 | TCHAR* fileToLoadW = (TCHAR*)calloc (size, sizeof (TCHAR));
541 | MultiByteToWideChar(CP_ACP, 0, fileToLoad, -1, fileToLoadW, size);
542 | HWND hWnd = ListLoadW(hListerWnd, fileToLoadW, showFlags);
543 | free(fileToLoadW);
544 | return hWnd;
545 | }
546 |
547 | void __stdcall ListCloseWindow(HWND hWnd) {
548 | setStoredValue(TEXT("splitter-position"), *(int*)GetProp(hWnd, TEXT("SPLITTERPOSITION")));
549 | setStoredValue(TEXT("font-size"), *(int*)GetProp(hWnd, TEXT("FONTSIZE")));
550 | setStoredValue(TEXT("format"), *(int*)GetProp(hWnd, TEXT("ISFORMAT")));
551 | setStoredValue(TEXT("tab-no"), TabCtrl_GetCurSel(GetDlgItem(hWnd, IDC_TAB)));
552 | setStoredValue(TEXT("filter-row"), *(int*)GetProp(hWnd, TEXT("FILTERROW")));
553 | setStoredValue(TEXT("dark-theme"), *(int*)GetProp(hWnd, TEXT("DARKTHEME")));
554 |
555 | SendMessage(hWnd, WMU_RESET_CACHE, 0, 0);
556 | xml_free((xml_element*)GetProp(hWnd, TEXT("XML")));
557 | free((int*)GetProp(hWnd, TEXT("FILTERROW")));
558 | free((int*)GetProp(hWnd, TEXT("DARKTHEME")));
559 | free((char*)GetProp(hWnd, TEXT("DATA")));
560 | free((int*)GetProp(hWnd, TEXT("ROWCOUNT")));
561 | free((int*)GetProp(hWnd, TEXT("TOTALROWCOUNT")));
562 | free((int*)GetProp(hWnd, TEXT("ORDERBY")));
563 | free((int*)GetProp(hWnd, TEXT("CURRENTROWNO")));
564 | free((int*)GetProp(hWnd, TEXT("CURRENTCOLNO")));
565 | free((int*)GetProp(hWnd, TEXT("SEARCHCELLPOS")));
566 | free((int*)GetProp(hWnd, TEXT("SPLITTERPOSITION")));
567 | free((int*)GetProp(hWnd, TEXT("ISFORMAT")));
568 | free((int*)GetProp(hWnd, TEXT("MAXHIGHLIGHTLENGTH")));
569 | free((TCHAR*)GetProp(hWnd, TEXT("FONTFAMILY")));
570 | free((int*)GetProp(hWnd, TEXT("FILTERALIGN")));
571 | free((TCHAR*)GetProp(hWnd, TEXT("SAMEFILTER")));
572 |
573 | free((int*)GetProp(hWnd, TEXT("TEXTCOLOR")));
574 | free((int*)GetProp(hWnd, TEXT("BACKCOLOR")));
575 | free((int*)GetProp(hWnd, TEXT("BACKCOLOR2")));
576 | free((int*)GetProp(hWnd, TEXT("FILTERTEXTCOLOR")));
577 | free((int*)GetProp(hWnd, TEXT("FILTERBACKCOLOR")));
578 | free((int*)GetProp(hWnd, TEXT("CURRENTCELLCOLOR")));
579 | free((int*)GetProp(hWnd, TEXT("SELECTIONTEXTCOLOR")));
580 | free((int*)GetProp(hWnd, TEXT("SELECTIONBACKCOLOR")));
581 | free((int*)GetProp(hWnd, TEXT("SPLITTERCOLOR")));
582 | free((int*)GetProp(hWnd, TEXT("XMLTEXTCOLOR")));
583 | free((int*)GetProp(hWnd, TEXT("XMLTAGCOLOR")));
584 | free((int*)GetProp(hWnd, TEXT("XMLSTRINGCOLOR")));
585 | free((int*)GetProp(hWnd, TEXT("XMLVALUECOLOR")));
586 | free((int*)GetProp(hWnd, TEXT("XMLCDATACOLOR")));
587 | free((int*)GetProp(hWnd, TEXT("XMLCOMMENTCOLOR")));
588 |
589 | DeleteFont(GetProp(hWnd, TEXT("FONT")));
590 | DeleteObject(GetProp(hWnd, TEXT("BACKBRUSH")));
591 | DeleteObject(GetProp(hWnd, TEXT("FILTERBACKBRUSH")));
592 | DeleteObject(GetProp(hWnd, TEXT("SPLITTERBRUSH")));
593 | DestroyMenu(GetProp(hWnd, TEXT("TREEMENU")));
594 | DestroyMenu(GetProp(hWnd, TEXT("GRIDMENU")));
595 | DestroyMenu(GetProp(hWnd, TEXT("TEXTMENU")));
596 |
597 | RemoveProp(hWnd, TEXT("WNDPROC"));
598 | RemoveProp(hWnd, TEXT("FILTERROW"));
599 | RemoveProp(hWnd, TEXT("DARKTHEME"));
600 | RemoveProp(hWnd, TEXT("CACHE"));
601 | RemoveProp(hWnd, TEXT("RESULTSET"));
602 | RemoveProp(hWnd, TEXT("ROWCOUNT"));
603 | RemoveProp(hWnd, TEXT("TOTALROWCOUNT"));
604 | RemoveProp(hWnd, TEXT("ORDERBY"));
605 | RemoveProp(hWnd, TEXT("CURRENTROWNO"));
606 | RemoveProp(hWnd, TEXT("CURRENTCOLNO"));
607 | RemoveProp(hWnd, TEXT("SEARCHCELLPOS"));
608 | RemoveProp(hWnd, TEXT("XML"));
609 | RemoveProp(hWnd, TEXT("DATA"));
610 | RemoveProp(hWnd, TEXT("SPLITTERPOSITION"));
611 | RemoveProp(hWnd, TEXT("ISFORMAT"));
612 | RemoveProp(hWnd, TEXT("MAXHIGHLIGHTLENGTH"));
613 | RemoveProp(hWnd, TEXT("FILTERALIGN"));
614 | RemoveProp(hWnd, TEXT("LASTFOCUS"));
615 | RemoveProp(hWnd, TEXT("SAMEFILTER"));
616 |
617 | RemoveProp(hWnd, TEXT("FONT"));
618 | RemoveProp(hWnd, TEXT("FONTFAMILY"));
619 | RemoveProp(hWnd, TEXT("FONTSIZE"));
620 | RemoveProp(hWnd, TEXT("TEXTCOLOR"));
621 | RemoveProp(hWnd, TEXT("BACKCOLOR"));
622 | RemoveProp(hWnd, TEXT("BACKCOLOR2"));
623 | RemoveProp(hWnd, TEXT("FILTERTEXTCOLOR"));
624 | RemoveProp(hWnd, TEXT("FILTERBACKCOLOR"));
625 | RemoveProp(hWnd, TEXT("CURRENTCELLCOLOR"));
626 | RemoveProp(hWnd, TEXT("SELECTIONTEXTCOLOR"));
627 | RemoveProp(hWnd, TEXT("SELECTIONBACKCOLOR"));
628 | RemoveProp(hWnd, TEXT("SPLITTERCOLOR"));
629 | RemoveProp(hWnd, TEXT("XMLTEXTCOLOR"));
630 | RemoveProp(hWnd, TEXT("XMLTAGCOLOR"));
631 | RemoveProp(hWnd, TEXT("XMLSTRINGCOLOR"));
632 | RemoveProp(hWnd, TEXT("XMLVALUECOLOR"));
633 | RemoveProp(hWnd, TEXT("XMLCDATACOLOR"));
634 | RemoveProp(hWnd, TEXT("XMLCOMMENTCOLOR"));
635 | RemoveProp(hWnd, TEXT("BACKBRUSH"));
636 | RemoveProp(hWnd, TEXT("FILTERBACKBRUSH"));
637 | RemoveProp(hWnd, TEXT("SPLITTERBRUSH"));
638 | RemoveProp(hWnd, TEXT("TREEMENU"));
639 | RemoveProp(hWnd, TEXT("GRIDMENU"));
640 | RemoveProp(hWnd, TEXT("TEXTMENU"));
641 |
642 | DestroyWindow(hWnd);
643 | }
644 |
645 | LRESULT CALLBACK cbNewMain(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
646 | switch (msg) {
647 | case WM_SIZE: {
648 | HWND hStatusWnd = GetDlgItem(hWnd, IDC_STATUSBAR);
649 | SendMessage(hStatusWnd, WM_SIZE, 0, 0);
650 | RECT rc;
651 | GetClientRect(hStatusWnd, &rc);
652 | int statusH = rc.bottom;
653 |
654 | int splitterW = *(int*)GetProp(hWnd, TEXT("SPLITTERPOSITION"));
655 | GetClientRect(hWnd, &rc);
656 | HWND hTreeWnd = GetDlgItem(hWnd, IDC_TREE);
657 | HWND hTabWnd = GetDlgItem(hWnd, IDC_TAB);
658 | HWND hGridWnd = GetDlgItem(hTabWnd, IDC_GRID);
659 | HWND hTextWnd = GetDlgItem(hTabWnd, IDC_TEXT);
660 | SetWindowPos(hTreeWnd, 0, 0, 0, splitterW, rc.bottom - statusH, SWP_NOMOVE | SWP_NOZORDER);
661 | SetWindowPos(hTabWnd, 0, splitterW + SPLITTER_WIDTH, 0, rc.right - splitterW - SPLITTER_WIDTH, rc.bottom - statusH, SWP_NOZORDER);
662 |
663 | RECT rc2;
664 | GetClientRect(hTabWnd, &rc);
665 | TabCtrl_GetItemRect(hTabWnd, 0, &rc2);
666 | SetWindowPos(hTextWnd, 0, 2, rc2.bottom + 3, rc.right - rc.left - SPLITTER_WIDTH, rc.bottom - rc2.bottom - 7, SWP_NOZORDER);
667 | SetWindowPos(hGridWnd, 0, 2, rc2.bottom + 3, rc.right - rc.left - SPLITTER_WIDTH, rc.bottom - rc2.bottom - 7, SWP_NOZORDER);
668 | }
669 | break;
670 |
671 | case WM_PAINT: {
672 | PAINTSTRUCT ps = {0};
673 | HDC hDC = BeginPaint(hWnd, &ps);
674 |
675 | RECT rc;
676 | GetClientRect(hWnd, &rc);
677 | rc.left = *(int*)GetProp(hWnd, TEXT("SPLITTERPOSITION"));
678 | rc.right = rc.left + SPLITTER_WIDTH;
679 | FillRect(hDC, &rc, (HBRUSH)GetProp(hWnd, TEXT("SPLITTERBRUSH")));
680 | EndPaint(hWnd, &ps);
681 |
682 | return 0;
683 | }
684 | break;
685 |
686 | // https://groups.google.com/g/comp.os.ms-windows.programmer.win32/c/1XhCKATRXws
687 | case WM_NCHITTEST: {
688 | return 1;
689 | }
690 | break;
691 |
692 | case WM_SETCURSOR: {
693 | SetCursor(LoadCursor(0, GetProp(hWnd, TEXT("ISMOUSEHOVER")) ? IDC_SIZEWE : IDC_ARROW));
694 | return TRUE;
695 | }
696 | break;
697 |
698 | case WM_SETFOCUS: {
699 | SetFocus(GetProp(hWnd, TEXT("LASTFOCUS")));
700 | }
701 | break;
702 |
703 | case WM_LBUTTONDOWN: {
704 | int x = GET_X_LPARAM(lParam);
705 | int pos = *(int*)GetProp(hWnd, TEXT("SPLITTERPOSITION"));
706 | if (x >= pos && x <= pos + SPLITTER_WIDTH) {
707 | SetProp(hWnd, TEXT("ISMOUSEDOWN"), (HANDLE)1);
708 | SetCapture(hWnd);
709 | }
710 | return 0;
711 | }
712 | break;
713 |
714 | case WM_LBUTTONUP: {
715 | ReleaseCapture();
716 | RemoveProp(hWnd, TEXT("ISMOUSEDOWN"));
717 | }
718 | break;
719 |
720 | case WM_MOUSEMOVE: {
721 | DWORD x = GET_X_LPARAM(lParam);
722 | int* pPos = (int*)GetProp(hWnd, TEXT("SPLITTERPOSITION"));
723 |
724 | if (!GetProp(hWnd, TEXT("ISMOUSEHOVER")) && *pPos <= x && x <= *pPos + SPLITTER_WIDTH) {
725 | TRACKMOUSEEVENT tme = {sizeof(TRACKMOUSEEVENT), TME_LEAVE, hWnd, 0};
726 | TrackMouseEvent(&tme);
727 | SetProp(hWnd, TEXT("ISMOUSEHOVER"), (HANDLE)1);
728 | }
729 |
730 | if (GetProp(hWnd, TEXT("ISMOUSEDOWN")) && x > 0 && x < 32000) {
731 | *pPos = x;
732 | SendMessage(hWnd, WM_SIZE, 0, 0);
733 | }
734 | }
735 | break;
736 |
737 | case WM_MOUSELEAVE: {
738 | SetProp(hWnd, TEXT("ISMOUSEHOVER"), 0);
739 | }
740 | break;
741 |
742 | case WM_MOUSEWHEEL: {
743 | if (LOWORD(wParam) == MK_CONTROL) {
744 | SendMessage(hWnd, WMU_SET_FONT, GET_WHEEL_DELTA_WPARAM(wParam) > 0 ? 1: -1, 0);
745 | return 1;
746 | }
747 | }
748 | break;
749 |
750 | case WM_KEYDOWN: {
751 | if (SendMessage(hWnd, WMU_HOT_KEYS, wParam, lParam))
752 | return 0;
753 | }
754 | break;
755 |
756 | case WM_CONTEXTMENU: {
757 | POINT p = {LOWORD(lParam), HIWORD(lParam)};
758 | int id = GetDlgCtrlID(WindowFromPoint(p));
759 | if (id == IDC_TEXT) {
760 | HWND hTabWnd = GetDlgItem(hWnd, IDC_TAB);
761 | HWND hTextWnd = GetDlgItem(hTabWnd, IDC_TEXT);
762 | ScreenToClient(hTextWnd, &p);
763 | int pos = SendMessage(hTextWnd, EM_CHARFROMPOS, 0, (LPARAM)&p);
764 | int start, end;
765 | SendMessage(hTextWnd, EM_GETSEL, (WPARAM)&start, (LPARAM)&end);
766 | if (start == end || pos < start || pos > end)
767 | SendMessage(hTextWnd, EM_SETSEL, pos, pos);
768 |
769 | ClientToScreen(hTextWnd, &p);
770 | TrackPopupMenu(GetProp(hWnd, TEXT("TEXTMENU")), TPM_RIGHTBUTTON | TPM_TOPALIGN | TPM_LEFTALIGN, p.x, p.y, 0, hWnd, NULL);
771 | }
772 |
773 | // update selected item on right click
774 | if (id == IDC_TREE) {
775 | HWND hTreeWnd = GetDlgItem(hWnd, IDC_TREE);
776 | POINT p2 = {0};
777 | GetCursorPos(&p2);
778 | ScreenToClient(hTreeWnd, &p2);
779 | TVHITTESTINFO thi = {p2, TVHT_ONITEM};
780 | HTREEITEM hItem = TreeView_HitTest(hTreeWnd, &thi);
781 | TreeView_SelectItem(hTreeWnd, hItem);
782 | if (!hItem)
783 | return 0;
784 |
785 | TCHAR nodeName[256] = {0};
786 | TreeView_GetItemText(hTreeWnd, hItem, nodeName, 255);
787 |
788 | HMENU hTreeMenu = GetProp(hWnd, TEXT("TREEMENU"));
789 | Menu_SetItemState(hTreeMenu, IDM_SHOW_SAME, _istalpha(nodeName[0]) ? MFS_ENABLED : MFS_DISABLED);
790 | TrackPopupMenu(hTreeMenu, TPM_RIGHTBUTTON | TPM_TOPALIGN | TPM_LEFTALIGN, p.x, p.y, 0, hWnd, NULL);
791 | }
792 | }
793 | break;
794 |
795 | case WM_COMMAND: {
796 | WORD cmd = LOWORD(wParam);
797 | if (cmd == IDM_COPY_CELL || cmd == IDM_COPY_ROWS || cmd == IDM_COPY_COLUMN) {
798 | HWND hTabWnd = GetDlgItem(hWnd, IDC_TAB);
799 | HWND hGridWnd = GetDlgItem(hTabWnd, IDC_GRID);
800 | HWND hHeader = ListView_GetHeader(hGridWnd);
801 | int rowNo = *(int*)GetProp(hWnd, TEXT("CURRENTROWNO"));
802 | int colNo = *(int*)GetProp(hWnd, TEXT("CURRENTCOLNO"));
803 |
804 | int colCount = Header_GetItemCount(hHeader);
805 | int rowCount = *(int*)GetProp(hWnd, TEXT("ROWCOUNT"));
806 | int selCount = ListView_GetSelectedCount(hGridWnd);
807 |
808 | if (rowNo == -1 ||
809 | rowNo >= rowCount ||
810 | colCount == 0 ||
811 | cmd == IDM_COPY_CELL && colNo == -1 ||
812 | cmd == IDM_COPY_CELL && colNo >= colCount ||
813 | cmd == IDM_COPY_COLUMN && colNo == -1 ||
814 | cmd == IDM_COPY_COLUMN && colNo >= colCount ||
815 | cmd == IDM_COPY_ROWS && selCount == 0) {
816 | setClipboardText(TEXT(""));
817 | return 0;
818 | }
819 |
820 | TCHAR*** cache = (TCHAR***)GetProp(hWnd, TEXT("CACHE"));
821 | int* resultset = (int*)GetProp(hWnd, TEXT("RESULTSET"));
822 | if (!resultset)
823 | return 0;
824 |
825 | TCHAR* delimiter = getStoredString(TEXT("column-delimiter"), TEXT("\t"));
826 |
827 | int len = 0;
828 | if (cmd == IDM_COPY_CELL)
829 | len = _tcslen(cache[resultset[rowNo]][colNo]);
830 |
831 | if (cmd == IDM_COPY_ROWS) {
832 | int rowNo = ListView_GetNextItem(hGridWnd, -1, LVNI_SELECTED);
833 | while (rowNo != -1) {
834 | for (int colNo = 0; colNo < colCount; colNo++) {
835 | if (ListView_GetColumnWidth(hGridWnd, colNo))
836 | len += _tcslen(cache[resultset[rowNo]][colNo]) + 1; /* column delimiter */
837 | }
838 |
839 | len++; /* \n */
840 | rowNo = ListView_GetNextItem(hGridWnd, rowNo, LVNI_SELECTED);
841 | }
842 | }
843 |
844 | if (cmd == IDM_COPY_COLUMN) {
845 | int rowNo = selCount < 2 ? 0 : ListView_GetNextItem(hGridWnd, -1, LVNI_SELECTED);
846 | while (rowNo != -1 && rowNo < rowCount) {
847 | len += _tcslen(cache[resultset[rowNo]][colNo]) + 1 /* \n */;
848 | rowNo = selCount < 2 ? rowNo + 1 : ListView_GetNextItem(hGridWnd, rowNo, LVNI_SELECTED);
849 | }
850 | }
851 |
852 | TCHAR* buf = calloc(len + 1, sizeof(TCHAR));
853 | if (cmd == IDM_COPY_CELL)
854 | _tcscat(buf, cache[resultset[rowNo]][colNo]);
855 |
856 | if (cmd == IDM_COPY_ROWS) {
857 | int pos = 0;
858 | int rowNo = ListView_GetNextItem(hGridWnd, -1, LVNI_SELECTED);
859 |
860 | int* colOrder = calloc(colCount, sizeof(int));
861 | Header_GetOrderArray(hHeader, colCount, colOrder);
862 |
863 | while (rowNo != -1) {
864 | for (int idx = 0; idx < colCount; idx++) {
865 | int colNo = colOrder[idx];
866 | if (ListView_GetColumnWidth(hGridWnd, colNo)) {
867 | int len = _tcslen(cache[resultset[rowNo]][colNo]);
868 | _tcsncpy(buf + pos, cache[resultset[rowNo]][colNo], len);
869 | buf[pos + len] = delimiter[0];
870 | pos += len + 1;
871 | }
872 | }
873 |
874 | buf[pos - (pos > 0)] = TEXT('\n');
875 | rowNo = ListView_GetNextItem(hGridWnd, rowNo, LVNI_SELECTED);
876 | }
877 | buf[pos - 1] = 0; // remove last \n
878 |
879 | free(colOrder);
880 | }
881 |
882 | if (cmd == IDM_COPY_COLUMN) {
883 | int pos = 0;
884 | int rowNo = selCount < 2 ? 0 : ListView_GetNextItem(hGridWnd, -1, LVNI_SELECTED);
885 | while (rowNo != -1 && rowNo < rowCount) {
886 | int len = _tcslen(cache[resultset[rowNo]][colNo]);
887 | _tcsncpy(buf + pos, cache[resultset[rowNo]][colNo], len);
888 | rowNo = selCount < 2 ? rowNo + 1 : ListView_GetNextItem(hGridWnd, rowNo, LVNI_SELECTED);
889 | if (rowNo != -1 && rowNo < rowCount)
890 | buf[pos + len] = TEXT('\n');
891 | pos += len + 1;
892 | }
893 | }
894 |
895 | setClipboardText(buf);
896 | free(buf);
897 | free(delimiter);
898 | }
899 |
900 | if (cmd == IDM_COPY_TEXT || cmd == IDM_SELECTALL) {
901 | HWND hTabWnd = GetDlgItem(hWnd, IDC_TAB);
902 | HWND hTextWnd = GetDlgItem(hTabWnd, IDC_TEXT);
903 | SendMessage(hTextWnd, cmd == IDM_COPY_TEXT ? WM_COPY : EM_SETSEL, 0, -1);
904 | }
905 |
906 | if (cmd == IDM_FORMAT) {
907 | HMENU hMenu = (HMENU)GetProp(hWnd, TEXT("TEXTMENU"));
908 | int* pIsFormat = (int*)GetProp(hWnd, TEXT("ISFORMAT"));
909 | *pIsFormat = (*pIsFormat + 1) % 2;
910 |
911 | MENUITEMINFO mii = {0};
912 | mii.cbSize = sizeof(MENUITEMINFO);
913 | mii.fMask = MIIM_STATE;
914 | mii.fState = *pIsFormat ? MFS_CHECKED : 0;
915 | SetMenuItemInfo(hMenu, IDM_FORMAT, FALSE, &mii);
916 |
917 | SendMessage(hWnd, WMU_UPDATE_TEXT, 0, 0);
918 | }
919 |
920 | if (cmd == IDM_LOCATE) {
921 | HWND hTreeWnd = GetDlgItem(hWnd, IDC_TREE);
922 | HWND hTabWnd = GetDlgItem(hWnd, IDC_TAB);
923 | HWND hTextWnd = GetDlgItem(hTabWnd, IDC_TEXT);
924 | int pos16;
925 | SendMessage(hTextWnd, EM_GETSEL, (WPARAM)&pos16, 0);
926 |
927 | GETTEXTLENGTHEX gtl = {GTL_NUMBYTES, 0};
928 | int len = SendMessage(hTextWnd, EM_GETTEXTLENGTHEX, (WPARAM)>l, 1200);
929 | TCHAR* xml16 = calloc(len + sizeof(TCHAR), sizeof(char));
930 | GETTEXTEX gt = {0};
931 | gt.cb = len + sizeof(TCHAR);
932 | gt.flags = 0;
933 | gt.codepage = 1200;
934 | SendMessage(hTextWnd, EM_GETTEXTEX, (WPARAM)>, (LPARAM)xml16);
935 |
936 | char* xml8 = utf16to8(xml16);
937 | char* xml8_ = utf16to8(xml16 + pos16);
938 | int pos8 = strlen(xml8) - strlen(xml8_);
939 | free(xml8_);
940 |
941 | xml_element* xml = xml_parse(xml8);
942 | xml_element* node = xml->first_child;
943 | HTREEITEM hItem = TreeView_GetSelection(hTreeWnd);
944 |
945 | while (node) {
946 | if (node->next && node->next->offset < pos8) {
947 | node = node->next;
948 | if (node->key || node->value && !isEmpty(node->value))
949 | hItem = TreeView_GetNextSibling(hTreeWnd, hItem) ? : hItem;
950 |
951 | continue;
952 | }
953 |
954 | if (node->first_child && node->first_child->offset < pos8) {
955 | node = node->first_child;
956 | TreeView_Expand(hTreeWnd, hItem, TVE_EXPAND);
957 | while (!(node->key || node->value && !isEmpty(node->value)))
958 | node = node->next;
959 | hItem = TreeView_GetChild(hTreeWnd, hItem) ? : hItem;
960 |
961 | continue;
962 | }
963 |
964 | break;
965 | }
966 |
967 | xml_free(xml);
968 | free(xml8);
969 | free(xml16);
970 |
971 | if (hItem)
972 | TreeView_SelectItem(hTreeWnd, hItem);
973 | else
974 | MessageBeep(0);
975 | }
976 |
977 | if (cmd == IDM_HIDE_COLUMN) {
978 | int colNo = *(int*)GetProp(hWnd, TEXT("CURRENTCOLNO"));
979 | SendMessage(hWnd, WMU_HIDE_COLUMN, colNo, 0);
980 | }
981 |
982 | if (cmd == IDM_SHOW_SAME) {
983 | HWND hTreeWnd = GetDlgItem(hWnd, IDC_TREE);
984 | HTREEITEM hItem = TreeView_GetSelection(hTreeWnd);
985 | if (hItem) {
986 | xml_element* node = (xml_element*)TreeView_GetItemParam(hTreeWnd, hItem);
987 | if (node->key && strlen(node->key)) {
988 | TCHAR* nodeName = utf8to16(node->key);
989 | _tcsncpy((TCHAR*)GetProp(hWnd, TEXT("SAMEFILTER")), nodeName, 255);
990 | free(nodeName);
991 | }
992 | TreeView_SelectItem(hTreeWnd, TreeView_GetParent(hTreeWnd, hItem));
993 | }
994 | }
995 |
996 | if (cmd == IDM_FILTER_ROW || cmd == IDM_DARK_THEME) {
997 | HMENU hGridMenu = (HMENU)GetProp(hWnd, TEXT("GRIDMENU"));
998 | HMENU hTextMenu = (HMENU)GetProp(hWnd, TEXT("TEXTMENU"));
999 | int* pOpt = (int*)GetProp(hWnd, cmd == IDM_FILTER_ROW ? TEXT("FILTERROW") : TEXT("DARKTHEME"));
1000 | *pOpt = (*pOpt + 1) % 2;
1001 | Menu_SetItemState(hGridMenu, cmd, *pOpt ? MFS_CHECKED : 0);
1002 | Menu_SetItemState(hTextMenu, cmd, *pOpt ? MFS_CHECKED : 0);
1003 |
1004 | UINT msg = cmd == IDM_FILTER_ROW ? WMU_SET_HEADER_FILTERS : WMU_SET_THEME;
1005 | SendMessage(hWnd, msg, 0, 0);
1006 | }
1007 |
1008 | if (cmd == IDM_COPY_XPATH) {
1009 | HWND hTreeWnd = GetDlgItem(hWnd, IDC_TREE);
1010 | HWND hTabWnd = GetDlgItem(hWnd, IDC_TAB);
1011 | HWND hGridWnd = GetDlgItem(hTabWnd, IDC_GRID);
1012 |
1013 | TCHAR*** cache = (TCHAR***)GetProp(hWnd, TEXT("CACHE"));
1014 | xml_element* node = 0;
1015 | TCHAR* attr16 = 0;
1016 | if (GetFocus() == hGridWnd) {
1017 | int rowNo = *(int*)GetProp(hWnd, TEXT("CURRENTROWNO"));
1018 | int* resultset = (int*)GetProp(hWnd, TEXT("RESULTSET"));
1019 | xml_element** xmlnodes = (xml_element**)GetProp(hWnd, TEXT("XMLNODES"));
1020 |
1021 | node = xmlnodes[resultset[rowNo]];
1022 | if (!node)
1023 | attr16 = cache[rowNo][0];
1024 | }
1025 |
1026 | if (!node) {
1027 | HTREEITEM hItem = TreeView_GetSelection(hTreeWnd);
1028 | node = (xml_element*)TreeView_GetItemParam(hTreeWnd, hItem);
1029 | }
1030 |
1031 | if (node) {
1032 | char* xpath8 = xml_path(node);
1033 | TCHAR* xpath16 = utf8to16(xpath8);
1034 | int len = _tcslen(xpath16) + (attr16 ? _tcslen(attr16) : 0) + 10;
1035 | TCHAR* buf16 = calloc(len + 1, sizeof(TCHAR));
1036 | if (attr16)
1037 | _sntprintf(buf16, len, TEXT("%ls/%ls"), xpath16, attr16);
1038 | else
1039 | _sntprintf(buf16, len, TEXT("%ls"), xpath16);
1040 |
1041 | setClipboardText(buf16);
1042 |
1043 | free(buf16);
1044 | free(xpath8);
1045 | free(xpath16);
1046 | } else {
1047 | MessageBeep(0);
1048 | }
1049 |
1050 | }
1051 | }
1052 | break;
1053 |
1054 | case WM_NOTIFY : {
1055 | NMHDR* pHdr = (LPNMHDR)lParam;
1056 | if (pHdr->idFrom == IDC_TAB && pHdr->code == TCN_SELCHANGE) {
1057 | HWND hTabWnd = pHdr->hwndFrom;
1058 | BOOL isText = TabCtrl_GetCurSel(hTabWnd) == 1;
1059 | ShowWindow(GetDlgItem(hTabWnd, IDC_GRID), isText ? SW_HIDE : SW_SHOW);
1060 | ShowWindow(GetDlgItem(hTabWnd, IDC_TEXT), isText ? SW_SHOW : SW_HIDE);
1061 | }
1062 |
1063 | if (pHdr->idFrom == IDC_TREE && pHdr->code == NM_CLICK) {
1064 | HWND hTreeWnd = pHdr->hwndFrom;
1065 | POINT p = {0};
1066 | GetCursorPos(&p);
1067 | ScreenToClient(hTreeWnd, &p);
1068 | TVHITTESTINFO thi = {p, TVHT_ONITEM};
1069 | if (TreeView_HitTest(hTreeWnd, &thi) == TreeView_GetSelection(hTreeWnd)) {
1070 | SendMessage(hWnd, WMU_UPDATE_GRID, 0, 0);
1071 | SendMessage(hWnd, WMU_UPDATE_TEXT, 0, 0);
1072 | }
1073 | }
1074 |
1075 | if (pHdr->idFrom == IDC_TREE && pHdr->code == TVN_SELCHANGED) {
1076 | SendMessage(hWnd, WMU_UPDATE_GRID, 0, 0);
1077 | SendMessage(hWnd, WMU_UPDATE_TEXT, 0, 0);
1078 | }
1079 |
1080 | if (pHdr->idFrom == IDC_TREE && pHdr->code == TVN_ITEMEXPANDING) {
1081 | HWND hTreeWnd = pHdr->hwndFrom;
1082 | NMTREEVIEW* tv = (NMTREEVIEW*) lParam;
1083 | HTREEITEM hItem = tv->itemNew.hItem;
1084 | HTREEITEM hChild = TreeView_GetChild(hTreeWnd, hItem);
1085 |
1086 | if (hChild) {
1087 | TCHAR nodeName[256];
1088 | TreeView_GetItemText(hTreeWnd, hChild, nodeName, 255);
1089 | if (_tcscmp(nodeName, LOADING) == 0) {
1090 | TreeView_DeleteItem(hTreeWnd, hChild);
1091 |
1092 | xml_element* node = (xml_element*)TreeView_GetItemParam(hTreeWnd, hItem);
1093 | xml_element* subnode = node != NULL ? node->last_child : 0;
1094 | while (subnode) {
1095 | subnode->userdata = (void*)addNode(hTreeWnd, hItem, subnode);
1096 | subnode = subnode->prev;
1097 | }
1098 | }
1099 | }
1100 | }
1101 |
1102 | if (pHdr->idFrom == IDC_GRID && pHdr->code == LVN_GETDISPINFO) {
1103 | LV_DISPINFO* pDispInfo = (LV_DISPINFO*)lParam;
1104 | LV_ITEM* pItem = &(pDispInfo)->item;
1105 |
1106 | TCHAR*** cache = (TCHAR***)GetProp(hWnd, TEXT("CACHE"));
1107 | int* resultset = (int*)GetProp(hWnd, TEXT("RESULTSET"));
1108 | if(resultset && pItem->mask & LVIF_TEXT) {
1109 | int rowNo = resultset[pItem->iItem];
1110 | pItem->pszText = cache[rowNo][pItem->iSubItem];
1111 | }
1112 | }
1113 |
1114 | if (pHdr->idFrom == IDC_GRID && pHdr->code == LVN_COLUMNCLICK) {
1115 | NMLISTVIEW* lv = (NMLISTVIEW*)lParam;
1116 | return SendMessage(hWnd, HIWORD(GetKeyState(VK_CONTROL)) ? WMU_HIDE_COLUMN : WMU_SORT_COLUMN, lv->iSubItem, 0);
1117 | }
1118 |
1119 | if (pHdr->idFrom == IDC_GRID && (pHdr->code == (DWORD)NM_CLICK || pHdr->code == (DWORD)NM_RCLICK)) {
1120 | NMITEMACTIVATE* ia = (LPNMITEMACTIVATE) lParam;
1121 | SendMessage(hWnd, WMU_SET_CURRENT_CELL, ia->iItem, ia->iSubItem);
1122 | }
1123 |
1124 | if (pHdr->idFrom == IDC_GRID && pHdr->code == (DWORD)NM_RCLICK) {
1125 | POINT p;
1126 | GetCursorPos(&p);
1127 | TrackPopupMenu(GetProp(hWnd, TEXT("GRIDMENU")), TPM_RIGHTBUTTON | TPM_TOPALIGN | TPM_LEFTALIGN, p.x, p.y, 0, hWnd, NULL);
1128 | }
1129 |
1130 | if (pHdr->idFrom == IDC_GRID && pHdr->code == (DWORD)LVN_ITEMCHANGED) {
1131 | NMLISTVIEW* lv = (NMLISTVIEW*)lParam;
1132 | if (lv->uOldState != lv->uNewState && (lv->uNewState & LVIS_SELECTED))
1133 | SendMessage(hWnd, WMU_SET_CURRENT_CELL, lv->iItem, *(int*)GetProp(hWnd, TEXT("CURRENTCOLNO")));
1134 | }
1135 |
1136 | if (pHdr->idFrom == IDC_GRID && pHdr->code == (DWORD)NM_CLICK && HIWORD(GetKeyState(VK_MENU))) {
1137 | NMITEMACTIVATE* ia = (LPNMITEMACTIVATE) lParam;
1138 | TCHAR*** cache = (TCHAR***)GetProp(hWnd, TEXT("CACHE"));
1139 | int* resultset = (int*)GetProp(hWnd, TEXT("RESULTSET"));
1140 |
1141 | TCHAR* url = extractUrl(cache[resultset[ia->iItem]][ia->iSubItem]);
1142 | ShellExecute(0, TEXT("open"), url, 0, 0 , SW_SHOW);
1143 | free(url);
1144 | }
1145 |
1146 | if (pHdr->idFrom == IDC_GRID && pHdr->code == (DWORD)LVN_KEYDOWN) {
1147 | NMLVKEYDOWN* kd = (LPNMLVKEYDOWN) lParam;
1148 | if (kd->wVKey == 0x43) { // C
1149 | BOOL isCtrl = HIWORD(GetKeyState(VK_CONTROL));
1150 | BOOL isShift = HIWORD(GetKeyState(VK_SHIFT));
1151 | BOOL isCopyColumn = getStoredValue(TEXT("copy-column"), 0) && ListView_GetSelectedCount(pHdr->hwndFrom) > 1;
1152 | if (!isCtrl && !isShift)
1153 | return FALSE;
1154 |
1155 | int action = !isShift && !isCopyColumn ? IDM_COPY_CELL : isCtrl || isCopyColumn ? IDM_COPY_COLUMN : IDM_COPY_ROWS;
1156 | SendMessage(hWnd, WM_COMMAND, action, 0);
1157 | return TRUE;
1158 | }
1159 |
1160 | if (kd->wVKey == 0x41 && HIWORD(GetKeyState(VK_CONTROL))) { // Ctrl + A
1161 | HWND hGridWnd = pHdr->hwndFrom;
1162 | SendMessage(hGridWnd, WM_SETREDRAW, FALSE, 0);
1163 | int rowNo = *(int*)GetProp(hWnd, TEXT("CURRENTROWNO"));
1164 | int colNo = *(int*)GetProp(hWnd, TEXT("CURRENTCOLNO"));
1165 | ListView_SetItemState(hGridWnd, -1, LVIS_SELECTED, LVIS_SELECTED | LVIS_FOCUSED);
1166 | SendMessage(hWnd, WMU_SET_CURRENT_CELL, rowNo, colNo);
1167 | SendMessage(hGridWnd, WM_SETREDRAW, TRUE, 0);
1168 | InvalidateRect(hGridWnd, NULL, TRUE);
1169 | }
1170 |
1171 | if (kd->wVKey == 0x20 && HIWORD(GetKeyState(VK_CONTROL))) { // Ctrl + Space
1172 | SendMessage(hWnd, WMU_SHOW_COLUMNS, 0, 0);
1173 | return TRUE;
1174 | }
1175 |
1176 | if (kd->wVKey == VK_LEFT || kd->wVKey == VK_RIGHT) {
1177 | HWND hTabWnd = GetDlgItem(hWnd, IDC_TAB);
1178 | HWND hGridWnd = GetDlgItem(hTabWnd, IDC_GRID);
1179 | HWND hHeader = ListView_GetHeader(hGridWnd);
1180 |
1181 | int colCount = Header_GetItemCount(ListView_GetHeader(pHdr->hwndFrom));
1182 | int colNo = *(int*)GetProp(hWnd, TEXT("CURRENTCOLNO"));
1183 |
1184 | int* colOrder = calloc(colCount, sizeof(int));
1185 | Header_GetOrderArray(hHeader, colCount, colOrder);
1186 |
1187 | int dir = kd->wVKey == VK_RIGHT ? 1 : -1;
1188 | int idx = 0;
1189 | for (idx; colOrder[idx] != colNo; idx++);
1190 | do {
1191 | idx = (colCount + idx + dir) % colCount;
1192 | } while (ListView_GetColumnWidth(hGridWnd, colOrder[idx]) == 0);
1193 |
1194 | colNo = colOrder[idx];
1195 | free(colOrder);
1196 |
1197 | SendMessage(hWnd, WMU_SET_CURRENT_CELL, *(int*)GetProp(hWnd, TEXT("CURRENTROWNO")), colNo);
1198 | return TRUE;
1199 | }
1200 | }
1201 |
1202 | if (pHdr->idFrom == IDC_GRID && pHdr->code == (DWORD)NM_DBLCLK) {
1203 | NMITEMACTIVATE* ia = (NMITEMACTIVATE*) lParam;
1204 | if (ia->iItem == -1)
1205 | return 0;
1206 |
1207 | TCHAR*** cache = (TCHAR***)GetProp(hWnd, TEXT("CACHE"));
1208 | int* resultset = (int*)GetProp(hWnd, TEXT("RESULTSET"));
1209 | xml_element** xmlnodes = (xml_element**)GetProp(hWnd, TEXT("XMLNODES"));
1210 |
1211 |
1212 | HWND hGridWnd = pHdr->hwndFrom;
1213 | HWND hTreeWnd = GetDlgItem(hWnd, IDC_TREE);
1214 |
1215 | int rowNo = resultset[ia->iItem];
1216 | xml_element* node = xmlnodes[rowNo];
1217 | HTREEITEM hItem = TreeView_GetSelection(hTreeWnd);
1218 | TreeView_Expand(hTreeWnd, hItem, TVE_EXPAND);
1219 | hItem = TreeView_GetChild(hTreeWnd, hItem);
1220 | if (node) {
1221 | while (hItem && node != (xml_element*)TreeView_GetItemParam(hTreeWnd, hItem))
1222 | hItem = TreeView_GetNextSibling(hTreeWnd, hItem);
1223 | }
1224 |
1225 | if (hItem)
1226 | TreeView_SelectItem(hTreeWnd, hItem);
1227 | }
1228 |
1229 | if ((pHdr->code == HDN_ITEMCHANGED || pHdr->code == HDN_ENDDRAG) && pHdr->hwndFrom == ListView_GetHeader(GetDlgItem(GetDlgItem(hWnd, IDC_TAB), IDC_GRID)))
1230 | PostMessage(hWnd, WMU_UPDATE_FILTER_SIZE, 0, 0);
1231 |
1232 | if (pHdr->code == (UINT)NM_SETFOCUS)
1233 | SetProp(hWnd, TEXT("LASTFOCUS"), pHdr->hwndFrom);
1234 |
1235 | if (pHdr->idFrom == IDC_GRID && pHdr->code == (UINT)NM_CUSTOMDRAW) {
1236 | int result = CDRF_DODEFAULT;
1237 |
1238 | NMLVCUSTOMDRAW* pCustomDraw = (LPNMLVCUSTOMDRAW)lParam;
1239 | if (pCustomDraw->nmcd.dwDrawStage == CDDS_PREPAINT)
1240 | result = CDRF_NOTIFYITEMDRAW;
1241 |
1242 | if (pCustomDraw->nmcd.dwDrawStage == CDDS_ITEMPREPAINT) {
1243 | if (ListView_GetItemState(pHdr->hwndFrom, pCustomDraw->nmcd.dwItemSpec, LVIS_SELECTED)) {
1244 | pCustomDraw->nmcd.uItemState &= ~CDIS_SELECTED;
1245 | result = CDRF_NOTIFYSUBITEMDRAW;
1246 | } else {
1247 | pCustomDraw->clrTextBk = *(int*)GetProp(hWnd, pCustomDraw->nmcd.dwItemSpec % 2 == 0 ? TEXT("BACKCOLOR") : TEXT("BACKCOLOR2"));
1248 | }
1249 | }
1250 |
1251 | if (pCustomDraw->nmcd.dwDrawStage == (CDDS_ITEMPREPAINT | CDDS_SUBITEM)) {
1252 | int rowNo = *(int*)GetProp(hWnd, TEXT("CURRENTROWNO"));
1253 | int colNo = *(int*)GetProp(hWnd, TEXT("CURRENTCOLNO"));
1254 | BOOL isCurrCell = (pCustomDraw->nmcd.dwItemSpec == (DWORD)rowNo) && (pCustomDraw->iSubItem == colNo);
1255 | pCustomDraw->clrText = *(int*)GetProp(hWnd, TEXT("SELECTIONTEXTCOLOR"));
1256 | pCustomDraw->clrTextBk = *(int*)GetProp(hWnd, isCurrCell ? TEXT("CURRENTCELLCOLOR") : TEXT("SELECTIONBACKCOLOR"));
1257 | }
1258 |
1259 | return result;
1260 | }
1261 | }
1262 | break;
1263 |
1264 | // wParam = colNo
1265 | case WMU_HIDE_COLUMN: {
1266 | HWND hTabWnd = GetDlgItem(hWnd, IDC_TAB);
1267 | HWND hGridWnd = GetDlgItem(hTabWnd, IDC_GRID);
1268 | HWND hHeader = ListView_GetHeader(hGridWnd);
1269 | int colNo = (int)wParam;
1270 |
1271 | HWND hEdit = GetDlgItem(hHeader, IDC_HEADER_EDIT + colNo);
1272 | SetWindowLongPtr(hEdit, GWLP_USERDATA, (LONG_PTR)ListView_GetColumnWidth(hGridWnd, colNo));
1273 | ListView_SetColumnWidth(hGridWnd, colNo, 0);
1274 | InvalidateRect(hHeader, NULL, TRUE);
1275 | }
1276 | break;
1277 |
1278 | case WMU_SHOW_COLUMNS: {
1279 | HWND hTabWnd = GetDlgItem(hWnd, IDC_TAB);
1280 | HWND hGridWnd = GetDlgItem(hTabWnd, IDC_GRID);
1281 | HWND hHeader = ListView_GetHeader(hGridWnd);
1282 | int colCount = Header_GetItemCount(ListView_GetHeader(hGridWnd));
1283 | for (int colNo = 0; colNo < colCount; colNo++) {
1284 | if (ListView_GetColumnWidth(hGridWnd, colNo) == 0) {
1285 | HWND hEdit = GetDlgItem(hHeader, IDC_HEADER_EDIT + colNo);
1286 | ListView_SetColumnWidth(hGridWnd, colNo, (int)GetWindowLongPtr(hEdit, GWLP_USERDATA));
1287 | }
1288 | }
1289 |
1290 | InvalidateRect(hGridWnd, NULL, TRUE);
1291 | }
1292 | break;
1293 |
1294 | // wParam = colNo
1295 | case WMU_SORT_COLUMN: {
1296 | int colNo = (int)wParam + 1;
1297 | if (colNo <= 0)
1298 | return FALSE;
1299 |
1300 | int* pOrderBy = (int*)GetProp(hWnd, TEXT("ORDERBY"));
1301 | int orderBy = *pOrderBy;
1302 | *pOrderBy = colNo == orderBy || colNo == -orderBy ? -orderBy : colNo;
1303 | SendMessage(hWnd, WMU_UPDATE_RESULTSET, 0, 0);
1304 | }
1305 | break;
1306 |
1307 | case WMU_UPDATE_GRID: {
1308 | HWND hTreeWnd = GetDlgItem(hWnd, IDC_TREE);
1309 | HWND hTabWnd = GetDlgItem(hWnd, IDC_TAB);
1310 | HWND hGridWnd = GetDlgItem(hTabWnd, IDC_GRID);
1311 | HWND hStatusWnd = GetDlgItem(hWnd, IDC_STATUSBAR);
1312 | int filterAlign = *(int*)GetProp(hWnd, TEXT("FILTERALIGN"));
1313 | BOOL isShowContent = getStoredValue(TEXT("show-content"), 0);
1314 |
1315 | HTREEITEM hItem = TreeView_GetSelection(hTreeWnd);
1316 | xml_element* node = (xml_element*)TreeView_GetItemParam(hTreeWnd, hItem);
1317 | if (!node)
1318 | return 0;
1319 |
1320 | TCHAR* sameFilter16 = (TCHAR*)GetProp(hWnd, TEXT("SAMEFILTER"));
1321 | char* sameFilter8 = utf16to8(sameFilter16);
1322 | BOOL isSameFilter = strlen(sameFilter8) > 0;
1323 |
1324 | HWND hHeader = ListView_GetHeader(hGridWnd);
1325 | SendMessage(hWnd, WMU_RESET_CACHE, 0, 0);
1326 | SendMessage(hWnd, WMU_SET_CURRENT_CELL, 0, 0);
1327 | *(int*)GetProp(hWnd, TEXT("ORDERBY")) = 0;
1328 |
1329 | int colCount = Header_GetItemCount(hHeader);
1330 | for (int colNo = 0; colNo < colCount; colNo++)
1331 | DestroyWindow(GetDlgItem(hHeader, IDC_HEADER_EDIT + colNo));
1332 |
1333 | for (int colNo = 0; colNo < colCount; colNo++)
1334 | ListView_DeleteColumn(hGridWnd, colCount - colNo - 1);
1335 |
1336 | BOOL isTable = TRUE;
1337 | xml_element* template = 0;
1338 | int childCount = 0;
1339 |
1340 | xml_element* subnode = node->first_child;
1341 | char* tagName = isSameFilter ? sameFilter8 : 0;
1342 | while (subnode) {
1343 | childCount += subnode->key != 0 && (!isSameFilter || isSameFilter && strcmp(subnode->key, sameFilter8) == 0);
1344 | if (tagName && subnode->key && !isSameFilter)
1345 | isTable = isTable && strcmp(subnode->key, tagName) == 0;
1346 |
1347 | if (!template && subnode->key && strlen(subnode->key) > 0 &&
1348 | (!isSameFilter ||
1349 | isSameFilter && stricmp(subnode->key, sameFilter8) == 0)) {
1350 | tagName = subnode->key;
1351 | template = subnode;
1352 | }
1353 |
1354 | subnode = subnode->next;
1355 | }
1356 | isTable = isTable && childCount > 1 || isSameFilter;
1357 |
1358 | if (isTable) {
1359 | xml_attribute* attr = template->first_attribute;
1360 | while (attr) {
1361 | char* colName8 = calloc(strlen(attr->key) + 2, sizeof(char));
1362 | sprintf(colName8, "@%s", attr->key);
1363 | TCHAR* colName16 = utf8to16(colName8);
1364 | ListView_AddColumn(hGridWnd, colName16);
1365 | free(colName16);
1366 | free(colName8);
1367 |
1368 | attr = attr->next;
1369 | }
1370 |
1371 | subnode = template->first_child;
1372 | while (subnode) {
1373 | if (subnode->key && strlen(subnode->key) > 0) {
1374 | TCHAR* colName16 = utf8to16(subnode->key);
1375 | ListView_AddColumn(hGridWnd, colName16);
1376 | free(colName16);
1377 | }
1378 | subnode = subnode->next;
1379 | }
1380 | if (isShowContent)
1381 | ListView_AddColumn(hGridWnd, TEXT("#CONTENT"));
1382 | } else {
1383 | ListView_AddColumn(hGridWnd, TEXT("Key"));
1384 | ListView_AddColumn(hGridWnd, TEXT("Value"));
1385 | }
1386 |
1387 | colCount = Header_GetItemCount(hHeader);
1388 | int align = filterAlign == -1 ? ES_LEFT : filterAlign == 1 ? ES_RIGHT : ES_CENTER;
1389 | for (int colNo = 0; colNo < colCount; colNo++) {
1390 | // Use WS_BORDER to vertical text aligment
1391 | HWND hEdit = CreateWindowEx(WS_EX_TOPMOST, WC_EDIT, NULL, align | ES_AUTOHSCROLL | WS_CHILD | WS_TABSTOP | WS_BORDER,
1392 | 0, 0, 0, 0, hHeader, (HMENU)(INT_PTR)(IDC_HEADER_EDIT + colNo), GetModuleHandle(0), NULL);
1393 | SendMessage(hEdit, WM_SETFONT, (LPARAM)GetProp(hWnd, TEXT("FONT")), TRUE);
1394 | SetProp(hEdit, TEXT("WNDPROC"), (HANDLE)SetWindowLongPtr(hEdit, GWLP_WNDPROC, (LONG_PTR)cbNewFilterEdit));
1395 | }
1396 |
1397 |
1398 | // Cache data
1399 | int* pTotalRowCount = (int*)GetProp(hWnd, TEXT("TOTALROWCOUNT"));
1400 | ListView_SetItemCount(hGridWnd, 0);
1401 | SetProp(hWnd, TEXT("CACHE"), 0);
1402 |
1403 | TCHAR*** cache = 0;
1404 | xml_element** xmlnodes = 0;
1405 | int rowCount = 0;
1406 | int rowNo = 0;
1407 | if (isTable) {
1408 | rowCount = node->child_count;
1409 | cache = calloc(rowCount, sizeof(TCHAR*));
1410 | xmlnodes = calloc(rowCount, sizeof(xml_element*));
1411 |
1412 | xml_element* subnode = node->first_child;
1413 | while (subnode) {
1414 | if (!subnode->key || isSameFilter && strcmp(subnode->key, sameFilter8)) {
1415 | subnode = subnode->next;
1416 | continue;
1417 | }
1418 |
1419 | cache[rowNo] = (TCHAR**)calloc (colCount, sizeof (TCHAR*));
1420 | xmlnodes[rowNo] = subnode;
1421 | int colNo = 0;
1422 |
1423 | xml_attribute* attr = template->first_attribute;
1424 | while (attr) {
1425 | xml_attribute* sa = xml_find_attribute(subnode, attr->key);
1426 | cache[rowNo][colNo] = utf8to16(sa ? sa->value : "N/A");
1427 |
1428 | colNo++;
1429 | attr = attr->next;
1430 | }
1431 |
1432 | xml_element* tagNode = template->first_child;
1433 | while (tagNode) {
1434 | if (!tagNode->key) {
1435 | tagNode = tagNode->next;
1436 | continue;
1437 | }
1438 |
1439 | xml_element* sn = xml_find_element(subnode, tagNode->key);
1440 | char* content = sn ? xml_content(sn) : calloc(1, sizeof(char));
1441 | cache[rowNo][colNo] = utf8to16(sn && !isEmpty(content) ? content : "N/A");
1442 | free(content);
1443 |
1444 | colNo++;
1445 | tagNode = tagNode->next;
1446 | }
1447 |
1448 | if (isShowContent) {
1449 | char* content = subnode ? xml_content(subnode): calloc(1, sizeof(char));
1450 | cache[rowNo][colNo] = utf8to16(subnode && !isEmpty(content) ? content : "");
1451 | free(content);
1452 | }
1453 |
1454 | rowNo++;
1455 | subnode = subnode->next;
1456 | }
1457 | } else {
1458 | rowCount = node->attribute_count + node->child_count + 1;
1459 | cache = calloc(rowCount, sizeof(TCHAR*));
1460 | xmlnodes = calloc(rowCount, sizeof(xml_element*));
1461 |
1462 | xml_attribute* attr = node->first_attribute;
1463 | while (attr) {
1464 | cache[rowNo] = (TCHAR**)calloc (colCount, sizeof (TCHAR*));
1465 | char* buf8 = calloc(strlen(attr->key) + 2, sizeof(char));
1466 | sprintf(buf8, "@%s", attr->key);
1467 | cache[rowNo][0] = utf8to16(buf8);
1468 | free(buf8);
1469 | cache[rowNo][1] = utf8to16(attr->value);
1470 |
1471 | rowNo++;
1472 | attr = attr->next;
1473 | }
1474 |
1475 | BOOL isSpecial = node->key && (strncmp(node->key, "![CDATA[", 8) == 0 || strncmp(node->key, "!--", 3) == 0) || node->value;
1476 | xml_element* subnode = isSpecial ? node : node->first_child;
1477 | while (subnode) {
1478 | if (!subnode->key && isEmpty(subnode->value)){
1479 | subnode = subnode->next;
1480 | continue;
1481 | }
1482 |
1483 | cache[rowNo] = (TCHAR**)calloc (colCount, sizeof (TCHAR*));
1484 | xmlnodes[rowNo] = subnode;
1485 | if (subnode->key) {
1486 | if (strncmp(subnode->key, "![CDATA[", 8) == 0) {
1487 | int len = strlen(subnode->key);
1488 | char* buf = calloc(len + 1, sizeof(char));
1489 | sprintf(buf, "%.*s", len - strlen("![CDATA[]]>"), subnode->key + 8);
1490 | cache[rowNo][0] = utf8to16(XML_CDATA);
1491 | cache[rowNo][1] = utf8to16(buf);
1492 | free(buf);
1493 | } else if (strncmp(subnode->key, "!--", 3) == 0) {
1494 | int len = strlen(subnode->key);
1495 | char* buf = calloc(len + 1, sizeof(char));
1496 | sprintf(buf, "%.*s", len - strlen("!----"), subnode->key + 3);
1497 | cache[rowNo][0] = utf8to16(XML_COMMENT);
1498 | cache[rowNo][1] = utf8to16(buf);
1499 | free(buf);
1500 | } else {
1501 | cache[rowNo][0] = utf8to16(subnode->key);
1502 |
1503 | char* content = xml_content(subnode);
1504 | cache[rowNo][1] = utf8to16(!isEmpty(content) ? content : "");
1505 | free(content);
1506 | }
1507 | } else if (subnode->value && !isEmpty(subnode->value)) {
1508 | cache[rowNo][0] = utf8to16(XML_TEXT);
1509 | cache[rowNo][1] = utf8to16(subnode->value);
1510 | }
1511 |
1512 | rowNo++;
1513 | subnode = isSpecial ? 0 : subnode->next;
1514 | }
1515 | }
1516 |
1517 | if (rowNo == 0) {
1518 | if (cache)
1519 | free(cache);
1520 | if (xmlnodes)
1521 | free(xmlnodes);
1522 | } else {
1523 | cache = realloc(cache, rowNo * sizeof(TCHAR*));
1524 | SetProp(hWnd, TEXT("CACHE"), cache);
1525 | SetProp(hWnd, TEXT("XMLNODES"), xmlnodes);
1526 | }
1527 |
1528 | SendMessage(hStatusWnd, SB_SETTEXT, SB_MODE, (LPARAM)(isSameFilter ? TEXT(" SAME") : isTable ? TEXT(" TABLE") : TEXT(" SINGLE")));
1529 | TCHAR buf16[300];
1530 | if (isSameFilter) {
1531 | _sntprintf(buf16, 300, TEXT(" Shows only \"%ls\"-nodes"), sameFilter16);
1532 | } else if (isTable) {
1533 | _sntprintf(buf16, 300, TEXT(" Shows all child nodes"));
1534 | } else {
1535 | _sntprintf(buf16, 300, TEXT(" Shows the node"), sameFilter16);
1536 | }
1537 | SendMessage(hStatusWnd, SB_SETTEXT, SB_AUXILIARY, (LPARAM)buf16);
1538 |
1539 | sameFilter16[0] = 0;
1540 | free(sameFilter8);
1541 |
1542 | *pTotalRowCount = rowNo;
1543 | SendMessage(hWnd, WMU_UPDATE_RESULTSET, 0, 0);
1544 | SendMessage(hWnd, WMU_SET_HEADER_FILTERS, 0, 0);
1545 | SendMessage(hWnd, WMU_AUTO_COLUMN_SIZE, 0, 0);
1546 | }
1547 | break;
1548 |
1549 | case WMU_UPDATE_RESULTSET: {
1550 | HWND hTabWnd = GetDlgItem(hWnd, IDC_TAB);
1551 | HWND hGridWnd = GetDlgItem(hTabWnd, IDC_GRID);
1552 | HWND hStatusWnd = GetDlgItem(hWnd, IDC_STATUSBAR);
1553 | HWND hHeader = ListView_GetHeader(hGridWnd);
1554 |
1555 | ListView_SetItemCount(hGridWnd, 0);
1556 | TCHAR*** cache = (TCHAR***)GetProp(hWnd, TEXT("CACHE"));
1557 | int* pTotalRowCount = (int*)GetProp(hWnd, TEXT("TOTALROWCOUNT"));
1558 | int* pRowCount = (int*)GetProp(hWnd, TEXT("ROWCOUNT"));
1559 | int* pOrderBy = (int*)GetProp(hWnd, TEXT("ORDERBY"));
1560 | int* resultset = (int*)GetProp(hWnd, TEXT("RESULTSET"));
1561 | BOOL isCaseSensitive = getStoredValue(TEXT("filter-case-sensitive"), 0);
1562 |
1563 | if (resultset)
1564 | free(resultset);
1565 |
1566 | if (!cache)
1567 | return 1;
1568 |
1569 | if (*pTotalRowCount == 0)
1570 | return 1;
1571 |
1572 | int colCount = Header_GetItemCount(hHeader);
1573 | if (colCount == 0)
1574 | return 1;
1575 |
1576 | BOOL* bResultset = (BOOL*)calloc(*pTotalRowCount, sizeof(BOOL));
1577 | for (int rowNo = 0; rowNo < *pTotalRowCount; rowNo++)
1578 | bResultset[rowNo] = TRUE;
1579 |
1580 | for (int colNo = 0; colNo < colCount; colNo++) {
1581 | HWND hEdit = GetDlgItem(hHeader, IDC_HEADER_EDIT + colNo);
1582 | TCHAR filter[MAX_LENGTH];
1583 | GetWindowText(hEdit, filter, MAX_LENGTH);
1584 | int len = _tcslen(filter);
1585 | if (len == 0)
1586 | continue;
1587 |
1588 | for (int rowNo = 0; rowNo < *pTotalRowCount; rowNo++) {
1589 | if (!bResultset[rowNo])
1590 | continue;
1591 |
1592 | TCHAR* value = cache[rowNo][colNo];
1593 | if (len > 1 && (filter[0] == TEXT('<') || filter[0] == TEXT('>')) && isNumber(filter + 1)) {
1594 | TCHAR* end = 0;
1595 | double df = _tcstod(filter + 1, &end);
1596 | double dv = _tcstod(value, &end);
1597 | bResultset[rowNo] = (filter[0] == TEXT('<') && dv < df) || (filter[0] == TEXT('>') && dv > df);
1598 | } else {
1599 | bResultset[rowNo] = len == 1 ? hasString(value, filter, isCaseSensitive) :
1600 | filter[0] == TEXT('=') && isCaseSensitive ? _tcscmp(value, filter + 1) == 0 :
1601 | filter[0] == TEXT('=') && !isCaseSensitive ? _tcsicmp(value, filter + 1) == 0 :
1602 | filter[0] == TEXT('!') ? !hasString(value, filter + 1, isCaseSensitive) :
1603 | filter[0] == TEXT('<') ? _tcscmp(value, filter + 1) < 0 :
1604 | filter[0] == TEXT('>') ? _tcscmp(value, filter + 1) > 0 :
1605 | hasString(value, filter, isCaseSensitive);
1606 | }
1607 | }
1608 | }
1609 |
1610 | int rowCount = 0;
1611 | resultset = (int*)calloc(*pTotalRowCount, sizeof(int));
1612 | for (int rowNo = 0; rowNo < *pTotalRowCount; rowNo++) {
1613 | if (!bResultset[rowNo])
1614 | continue;
1615 |
1616 | resultset[rowCount] = rowNo;
1617 | rowCount++;
1618 | }
1619 | free(bResultset);
1620 |
1621 | if (rowCount > 0) {
1622 | if (rowCount > *pTotalRowCount)
1623 | MessageBeep(0);
1624 | resultset = realloc(resultset, rowCount * sizeof(int));
1625 | SetProp(hWnd, TEXT("RESULTSET"), (HANDLE)resultset);
1626 | int orderBy = *pOrderBy;
1627 |
1628 | if (orderBy) {
1629 | int colNo = orderBy > 0 ? orderBy - 1 : - orderBy - 1;
1630 | BOOL isBackward = orderBy < 0;
1631 |
1632 | BOOL isNum = TRUE;
1633 | for (int i = 0; i < *pTotalRowCount && i <= 5; i++)
1634 | isNum = isNum && isNumber(cache[i][colNo]);
1635 |
1636 | if (isNum) {
1637 | double* nums = calloc(*pTotalRowCount, sizeof(double));
1638 | for (int i = 0; i < rowCount; i++)
1639 | nums[resultset[i]] = _tcstod(cache[resultset[i]][colNo], NULL);
1640 |
1641 | mergeSort(resultset, (void*)nums, 0, rowCount - 1, isBackward, isNum);
1642 | free(nums);
1643 | } else {
1644 | TCHAR** strings = calloc(*pTotalRowCount, sizeof(TCHAR*));
1645 | for (int i = 0; i < rowCount; i++)
1646 | strings[resultset[i]] = cache[resultset[i]][colNo];
1647 | mergeSort(resultset, (void*)strings, 0, rowCount - 1, isBackward, isNum);
1648 | free(strings);
1649 | }
1650 | }
1651 | } else {
1652 | SetProp(hWnd, TEXT("RESULTSET"), (HANDLE)0);
1653 | free(resultset);
1654 | }
1655 |
1656 | *pRowCount = rowCount;
1657 | ListView_SetItemCount(hGridWnd, rowCount);
1658 | InvalidateRect(hGridWnd, NULL, TRUE);
1659 |
1660 | TCHAR buf[255];
1661 | _sntprintf(buf, 255, TEXT(" Rows: %i/%i"), rowCount, *pTotalRowCount);
1662 | SendMessage(hStatusWnd, SB_SETTEXT, SB_ROW_COUNT, (LPARAM)buf);
1663 |
1664 | PostMessage(hWnd, WMU_UPDATE_FILTER_SIZE, 0, 0);
1665 | }
1666 | break;
1667 |
1668 | case WMU_UPDATE_TEXT: {
1669 | HWND hTreeWnd = GetDlgItem(hWnd, IDC_TREE);
1670 | HWND hTabWnd = GetDlgItem(hWnd, IDC_TAB);
1671 | HWND hTextWnd = GetDlgItem(hTabWnd, IDC_TEXT);
1672 | char* data = (char*)GetProp(hWnd, TEXT("DATA"));
1673 |
1674 | HTREEITEM hItem = TreeView_GetSelection(hTreeWnd);
1675 | xml_element* node = (xml_element*)TreeView_GetItemParam(hTreeWnd, hItem);
1676 | if (!node)
1677 | return 0;
1678 |
1679 | char* text8 = calloc(node->length + 1, sizeof(char));
1680 | strncpy(text8, data + node->offset, node->length);
1681 |
1682 | if (*(int*)GetProp(hWnd, TEXT("ISFORMAT"))) {
1683 | char* ftext8 = formatXML(text8);
1684 | free(text8);
1685 | text8 = ftext8;
1686 | }
1687 |
1688 | TCHAR* text16 = utf8to16(text8);
1689 | free(text8);
1690 |
1691 | LockWindowUpdate(hTextWnd);
1692 | SendMessage(hTextWnd, EM_EXLIMITTEXT, 0, _tcslen(text16) + 1);
1693 | SetWindowText(hTextWnd, text16);
1694 | LockWindowUpdate(0);
1695 | free(text16);
1696 |
1697 | SendMessage(hWnd, WMU_UPDATE_HIGHLIGHT, 0, 0);
1698 | }
1699 | break;
1700 |
1701 | case WMU_UPDATE_HIGHLIGHT: {
1702 | HWND hTabWnd = GetDlgItem(hWnd, IDC_TAB);
1703 | HWND hTextWnd = GetDlgItem(hTabWnd, IDC_TEXT);
1704 |
1705 | GETTEXTLENGTHEX gtl = {GTL_NUMBYTES, 0};
1706 | int len = SendMessage(hTextWnd, EM_GETTEXTLENGTHEX, (WPARAM)>l, 1200);
1707 | if (len < *(int*)GetProp(hWnd, TEXT("MAXHIGHLIGHTLENGTH"))) {
1708 | TCHAR* text = calloc(len + sizeof(TCHAR), sizeof(char));
1709 | GETTEXTEX gt = {0};
1710 | gt.cb = len + sizeof(TCHAR);
1711 | gt.flags = 0;
1712 | gt.codepage = 1200;
1713 | SendMessage(hTextWnd, EM_GETTEXTEX, (WPARAM)>, (LPARAM)text);
1714 | LockWindowUpdate(hTextWnd);
1715 |
1716 | CHARFORMAT2 cf2 = {0};
1717 | cf2.cbSize = sizeof(CHARFORMAT2) ;
1718 | cf2.dwMask = CFM_COLOR;
1719 | cf2.crTextColor = *(int*)GetProp(hWnd, TEXT("XMLTEXTCOLOR"));
1720 | SendMessage(hTextWnd, EM_SETSEL, 0, -1);
1721 | SendMessage(hTextWnd, EM_SETCHARFORMAT, SCF_ALL, (LPARAM) &cf2);
1722 |
1723 | highlightText(hTextWnd, text);
1724 | free(text);
1725 | SendMessage(hTextWnd, EM_SETSEL, 0, 0);
1726 | LockWindowUpdate(0);
1727 | InvalidateRect(hTextWnd, NULL, TRUE);
1728 | }
1729 | }
1730 | break;
1731 |
1732 | case WMU_UPDATE_FILTER_SIZE: {
1733 | HWND hTabWnd = GetDlgItem(hWnd, IDC_TAB);
1734 | HWND hGridWnd = GetDlgItem(hTabWnd, IDC_GRID);
1735 | HWND hHeader = ListView_GetHeader(hGridWnd);
1736 | int colCount = Header_GetItemCount(hHeader);
1737 | SendMessage(hHeader, WM_SIZE, 0, 0);
1738 |
1739 | int* colOrder = calloc(colCount, sizeof(int));
1740 | Header_GetOrderArray(hHeader, colCount, colOrder);
1741 |
1742 | for (int idx = 0; idx < colCount; idx++) {
1743 | int colNo = colOrder[idx];
1744 | RECT rc;
1745 | Header_GetItemRect(hHeader, colNo, &rc);
1746 | int h2 = round((rc.bottom - rc.top) / 2);
1747 | SetWindowPos(GetDlgItem(hHeader, IDC_HEADER_EDIT + colNo), 0, rc.left, h2, rc.right - rc.left, h2 + 1, SWP_NOZORDER);
1748 | }
1749 |
1750 | free(colOrder);
1751 | }
1752 | break;
1753 |
1754 | case WMU_SET_HEADER_FILTERS: {
1755 | HWND hTabWnd = GetDlgItem(hWnd, IDC_TAB);
1756 | HWND hGridWnd = GetDlgItem(hTabWnd, IDC_GRID);
1757 | HWND hHeader = ListView_GetHeader(hGridWnd);
1758 | int isFilterRow = *(int*)GetProp(hWnd, TEXT("FILTERROW"));
1759 | int colCount = Header_GetItemCount(hHeader);
1760 |
1761 | SendMessage(hWnd, WM_SETREDRAW, FALSE, 0);
1762 | LONG_PTR styles = GetWindowLongPtr(hHeader, GWL_STYLE);
1763 | styles = isFilterRow ? styles | HDS_FILTERBAR : styles & (~HDS_FILTERBAR);
1764 | SetWindowLongPtr(hHeader, GWL_STYLE, styles);
1765 |
1766 | for (int colNo = 0; colNo < colCount; colNo++)
1767 | ShowWindow(GetDlgItem(hHeader, IDC_HEADER_EDIT + colNo), isFilterRow ? SW_SHOW : SW_HIDE);
1768 |
1769 | // Bug fix: force Windows to redraw header
1770 | SetWindowPos(hGridWnd, 0, 0, 0, 0, 0, SWP_NOZORDER | SWP_NOMOVE);
1771 | SendMessage(getMainWindow(hWnd), WM_SIZE, 0, 0);
1772 |
1773 | if (isFilterRow)
1774 | SendMessage(hWnd, WMU_UPDATE_FILTER_SIZE, 0, 0);
1775 |
1776 | SendMessage(hWnd, WM_SETREDRAW, TRUE, 0);
1777 | InvalidateRect(hWnd, NULL, TRUE);
1778 | }
1779 | break;
1780 |
1781 | case WMU_AUTO_COLUMN_SIZE: {
1782 | HWND hTabWnd = GetDlgItem(hWnd, IDC_TAB);
1783 | HWND hGridWnd = GetDlgItem(hTabWnd, IDC_GRID);
1784 | SendMessage(hGridWnd, WM_SETREDRAW, FALSE, 0);
1785 | HWND hHeader = ListView_GetHeader(hGridWnd);
1786 | int colCount = Header_GetItemCount(hHeader);
1787 |
1788 | for (int colNo = 0; colNo < colCount - 1; colNo++)
1789 | ListView_SetColumnWidth(hGridWnd, colNo, colNo < colCount - 1 ? LVSCW_AUTOSIZE_USEHEADER : LVSCW_AUTOSIZE);
1790 |
1791 | if (colCount == 1 && ListView_GetColumnWidth(hGridWnd, 0) < 100)
1792 | ListView_SetColumnWidth(hGridWnd, 0, 100);
1793 |
1794 | int maxWidth = getStoredValue(TEXT("max-column-width"), 300);
1795 | if (colCount > 1) {
1796 | for (int colNo = 0; colNo < colCount; colNo++) {
1797 | if (ListView_GetColumnWidth(hGridWnd, colNo) > maxWidth)
1798 | ListView_SetColumnWidth(hGridWnd, colNo, maxWidth);
1799 | }
1800 | }
1801 |
1802 | // Fix last column
1803 | if (colCount > 1) {
1804 | int colNo = colCount - 1;
1805 | ListView_SetColumnWidth(hGridWnd, colNo, LVSCW_AUTOSIZE);
1806 | TCHAR name16[MAX_LENGTH + 1];
1807 | Header_GetItemText(hHeader, colNo, name16, MAX_LENGTH);
1808 |
1809 | SIZE s = {0};
1810 | HDC hDC = GetDC(hHeader);
1811 | HFONT hOldFont = (HFONT)SelectObject(hDC, (HFONT)GetProp(hWnd, TEXT("FONT")));
1812 | GetTextExtentPoint32(hDC, name16, _tcslen(name16), &s);
1813 | SelectObject(hDC, hOldFont);
1814 | ReleaseDC(hHeader, hDC);
1815 |
1816 | int w = s.cx + 12;
1817 | if (ListView_GetColumnWidth(hGridWnd, colNo) < w)
1818 | ListView_SetColumnWidth(hGridWnd, colNo, w);
1819 |
1820 | if (ListView_GetColumnWidth(hGridWnd, colNo) > maxWidth)
1821 | ListView_SetColumnWidth(hGridWnd, colNo, maxWidth);
1822 | }
1823 |
1824 | SendMessage(hGridWnd, WM_SETREDRAW, TRUE, 0);
1825 | InvalidateRect(hGridWnd, NULL, TRUE);
1826 |
1827 | PostMessage(hWnd, WMU_UPDATE_FILTER_SIZE, 0, 0);
1828 | }
1829 | break;
1830 |
1831 | // wParam = rowNo, lParam = colNo
1832 | case WMU_SET_CURRENT_CELL: {
1833 | HWND hTabWnd = GetDlgItem(hWnd, IDC_TAB);
1834 | HWND hGridWnd = GetDlgItem(hTabWnd, IDC_GRID);
1835 | HWND hHeader = ListView_GetHeader(hGridWnd);
1836 | HWND hStatusWnd = GetDlgItem(hWnd, IDC_STATUSBAR);
1837 | SendMessage(hStatusWnd, SB_SETTEXT, SB_AUXILIARY, (LPARAM)0);
1838 |
1839 | int *pRowNo = (int*)GetProp(hWnd, TEXT("CURRENTROWNO"));
1840 | int *pColNo = (int*)GetProp(hWnd, TEXT("CURRENTCOLNO"));
1841 | if (*pRowNo == wParam && *pColNo == lParam)
1842 | return 0;
1843 |
1844 | RECT rc, rc2;
1845 | ListView_GetSubItemRect(hGridWnd, *pRowNo, *pColNo, LVIR_BOUNDS, &rc);
1846 | if (*pColNo == 0)
1847 | rc.right = ListView_GetColumnWidth(hGridWnd, *pColNo);
1848 | InvalidateRect(hGridWnd, &rc, TRUE);
1849 |
1850 | *pRowNo = wParam;
1851 | *pColNo = lParam;
1852 | ListView_GetSubItemRect(hGridWnd, *pRowNo, *pColNo, LVIR_BOUNDS, &rc);
1853 | if (*pColNo == 0)
1854 | rc.right = ListView_GetColumnWidth(hGridWnd, *pColNo);
1855 | InvalidateRect(hGridWnd, &rc, FALSE);
1856 |
1857 | GetClientRect(hGridWnd, &rc2);
1858 | int w = rc.right - rc.left;
1859 | int dx = rc2.right < rc.right ? rc.left - rc2.right + w : rc.left < 0 ? rc.left : 0;
1860 |
1861 | ListView_Scroll(hGridWnd, dx, 0);
1862 |
1863 | TCHAR buf[32] = {0};
1864 | if (*pColNo != - 1 && *pRowNo != -1)
1865 | _sntprintf(buf, 32, TEXT(" %i:%i"), *pRowNo + 1, *pColNo + 1);
1866 | SendMessage(hStatusWnd, SB_SETTEXT, SB_CURRENT_CELL, (LPARAM)buf);
1867 | }
1868 | break;
1869 |
1870 | case WMU_RESET_CACHE: {
1871 | HWND hTabWnd = GetDlgItem(hWnd, IDC_TAB);
1872 | HWND hGridWnd = GetDlgItem(hTabWnd, IDC_GRID);
1873 | TCHAR*** cache = (TCHAR***)GetProp(hWnd, TEXT("CACHE"));
1874 | int* pTotalRowCount = (int*)GetProp(hWnd, TEXT("TOTALROWCOUNT"));
1875 |
1876 | int colCount = Header_GetItemCount(ListView_GetHeader(hGridWnd));
1877 | if (colCount > 0 && cache != 0) {
1878 | for (int rowNo = 0; rowNo < *pTotalRowCount; rowNo++) {
1879 | if (cache[rowNo]) {
1880 | for (int colNo = 0; colNo < colCount; colNo++)
1881 | if (cache[rowNo][colNo])
1882 | free(cache[rowNo][colNo]);
1883 |
1884 | free(cache[rowNo]);
1885 | }
1886 | cache[rowNo] = 0;
1887 | }
1888 | free(cache);
1889 | }
1890 |
1891 | xml_element* xmlnodes = (xml_element*)GetProp(hWnd, TEXT("XMLNODES"));
1892 | if (xmlnodes)
1893 | free(xmlnodes);
1894 | SetProp(hWnd, TEXT("XMLNODES"), 0);
1895 |
1896 | int* resultset = (int*)GetProp(hWnd, TEXT("RESULTSET"));
1897 | if (resultset)
1898 | free(resultset);
1899 | SetProp(hWnd, TEXT("RESULTSET"), 0);
1900 |
1901 | int* pRowCount = (int*)GetProp(hWnd, TEXT("ROWCOUNT"));
1902 | *pRowCount = 0;
1903 |
1904 | SetProp(hWnd, TEXT("CACHE"), 0);
1905 | *pTotalRowCount = 0;
1906 | }
1907 | break;
1908 |
1909 | // wParam - size delta
1910 | case WMU_SET_FONT: {
1911 | int* pFontSize = (int*)GetProp(hWnd, TEXT("FONTSIZE"));
1912 | if (*pFontSize + wParam < 10 || *pFontSize + wParam > 48)
1913 | return 0;
1914 | *pFontSize += wParam;
1915 | DeleteFont(GetProp(hWnd, TEXT("FONT")));
1916 |
1917 | int weight = getStoredValue(TEXT("font-weight"), 0);
1918 | HFONT hFont = CreateFont (*pFontSize, 0, 0, 0, weight < 0 || weight > 9 ? FW_DONTCARE : weight * 100, FALSE, FALSE, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH | FF_SWISS, (TCHAR*)GetProp(hWnd, TEXT("FONTFAMILY")));
1919 | HWND hTreeWnd = GetDlgItem(hWnd, IDC_TREE);
1920 | HWND hTabWnd = GetDlgItem(hWnd, IDC_TAB);
1921 | HWND hGridWnd = GetDlgItem(hTabWnd, IDC_GRID);
1922 | HWND hTextWnd = GetDlgItem(hTabWnd, IDC_TEXT);
1923 | SendMessage(hTreeWnd, WM_SETFONT, (LPARAM)hFont, TRUE);
1924 | SendMessage(hGridWnd, WM_SETFONT, (LPARAM)hFont, TRUE);
1925 | SendMessage(hTextWnd, WM_SETFONT, (LPARAM)hFont, TRUE);
1926 | SendMessage(hTabWnd, WM_SETFONT, (LPARAM)GetStockObject(DEFAULT_GUI_FONT), TRUE);
1927 |
1928 | HWND hHeader = ListView_GetHeader(hGridWnd);
1929 | for (int colNo = 0; colNo < Header_GetItemCount(hHeader); colNo++)
1930 | SendMessage(GetDlgItem(hHeader, IDC_HEADER_EDIT + colNo), WM_SETFONT, (LPARAM)hFont, TRUE);
1931 |
1932 | SetProp(hWnd, TEXT("FONT"), hFont);
1933 |
1934 | PostMessage(hWnd, WMU_UPDATE_HIGHLIGHT, 0, 0);
1935 | PostMessage(hWnd, WMU_AUTO_COLUMN_SIZE, 0, 0);
1936 | }
1937 | break;
1938 |
1939 | case WMU_SET_THEME: {
1940 | HWND hTreeWnd = GetDlgItem(hWnd, IDC_TREE);
1941 | HWND hTabWnd = GetDlgItem(hWnd, IDC_TAB);
1942 | HWND hGridWnd = GetDlgItem(hTabWnd, IDC_GRID);
1943 | HWND hTextWnd = GetDlgItem(hTabWnd, IDC_TEXT);
1944 | BOOL isDark = *(int*)GetProp(hWnd, TEXT("DARKTHEME"));
1945 |
1946 | int textColor = !isDark ? getStoredValue(TEXT("text-color"), RGB(0, 0, 0)) : getStoredValue(TEXT("text-color-dark"), RGB(220, 220, 220));
1947 | int backColor = !isDark ? getStoredValue(TEXT("back-color"), RGB(255, 255, 255)) : getStoredValue(TEXT("back-color-dark"), RGB(32, 32, 32));
1948 | int backColor2 = !isDark ? getStoredValue(TEXT("back-color2"), RGB(240, 240, 240)) : getStoredValue(TEXT("back-color2-dark"), RGB(52, 52, 52));
1949 | int filterTextColor = !isDark ? getStoredValue(TEXT("filter-text-color"), RGB(0, 0, 0)) : getStoredValue(TEXT("filter-text-color-dark"), RGB(255, 255, 255));
1950 | int filterBackColor = !isDark ? getStoredValue(TEXT("filter-back-color"), RGB(240, 240, 240)) : getStoredValue(TEXT("filter-back-color-dark"), RGB(60, 60, 60));
1951 | int currCellColor = !isDark ? getStoredValue(TEXT("current-cell-back-color"), RGB(70, 96, 166)) : getStoredValue(TEXT("current-cell-back-color-dark"), RGB(32, 62, 62));
1952 | int selectionTextColor = !isDark ? getStoredValue(TEXT("selection-text-color"), RGB(255, 255, 255)) : getStoredValue(TEXT("selection-text-color-dark"), RGB(220, 220, 220));
1953 | int selectionBackColor = !isDark ? getStoredValue(TEXT("selection-back-color"), RGB(10, 36, 106)) : getStoredValue(TEXT("selection-back-color-dark"), RGB(72, 102, 102));
1954 | int splitterColor = !isDark ? getStoredValue(TEXT("splitter-color"), GetSysColor(COLOR_BTNFACE)) : getStoredValue(TEXT("splitter-color-dark"), GetSysColor(COLOR_BTNFACE));
1955 |
1956 | *(int*)GetProp(hWnd, TEXT("TEXTCOLOR")) = textColor;
1957 | *(int*)GetProp(hWnd, TEXT("BACKCOLOR")) = backColor;
1958 | *(int*)GetProp(hWnd, TEXT("BACKCOLOR2")) = backColor2;
1959 | *(int*)GetProp(hWnd, TEXT("FILTERTEXTCOLOR")) = filterTextColor;
1960 | *(int*)GetProp(hWnd, TEXT("FILTERBACKCOLOR")) = filterBackColor;
1961 | *(int*)GetProp(hWnd, TEXT("CURRENTCELLCOLOR")) = currCellColor;
1962 | *(int*)GetProp(hWnd, TEXT("SELECTIONTEXTCOLOR")) = selectionTextColor;
1963 | *(int*)GetProp(hWnd, TEXT("SELECTIONBACKCOLOR")) = selectionBackColor;
1964 | *(int*)GetProp(hWnd, TEXT("SPLITTERCOLOR")) = splitterColor;
1965 |
1966 | *(int*)GetProp(hWnd, TEXT("XMLTEXTCOLOR")) = !isDark ? getStoredValue(TEXT("xml-text-color"), RGB(0, 0, 0)) : getStoredValue(TEXT("xml-text-color-dark"), RGB(220, 220, 220));
1967 | *(int*)GetProp(hWnd, TEXT("XMLTAGCOLOR")) = !isDark ? getStoredValue(TEXT("xml-tag-color"), RGB(0, 0, 128)) : getStoredValue(TEXT("xml-tag-color-dark"), RGB(0, 0, 255));
1968 | *(int*)GetProp(hWnd, TEXT("XMLSTRINGCOLOR")) = !isDark ? getStoredValue(TEXT("xml-string-color"), RGB(0, 128, 0)) : getStoredValue(TEXT("xml-string-color-dark"), RGB(0, 196, 0));
1969 | *(int*)GetProp(hWnd, TEXT("XMLVALUECOLOR")) = !isDark ? getStoredValue(TEXT("xml-value-color"), RGB(0, 128, 128)) : getStoredValue(TEXT("xml-value-color-dark"), RGB(0, 196, 128));
1970 | *(int*)GetProp(hWnd, TEXT("XMLCDATACOLOR")) = !isDark ? getStoredValue(TEXT("xml-cdata-color"), RGB(220, 220, 220)) : getStoredValue(TEXT("xml-cdata-color-dark"), RGB(220, 220, 220));
1971 | *(int*)GetProp(hWnd, TEXT("XMLCOMMENTCOLOR")) = !isDark ? getStoredValue(TEXT("xml-comment-color"), RGB(220, 220, 128)) : getStoredValue(TEXT("xml-comment-color-dark"), RGB(220, 220, 128));
1972 |
1973 | DeleteObject(GetProp(hWnd, TEXT("BACKBRUSH")));
1974 | DeleteObject(GetProp(hWnd, TEXT("FILTERBACKBRUSH")));
1975 | DeleteObject(GetProp(hWnd, TEXT("SPLITTERBRUSH")));
1976 | SetProp(hWnd, TEXT("BACKBRUSH"), CreateSolidBrush(backColor));
1977 | SetProp(hWnd, TEXT("FILTERBACKBRUSH"), CreateSolidBrush(filterBackColor));
1978 | SetProp(hWnd, TEXT("SPLITTERBRUSH"), CreateSolidBrush(splitterColor));
1979 |
1980 | TreeView_SetTextColor(hTreeWnd, textColor);
1981 | TreeView_SetBkColor(hTreeWnd, backColor);
1982 |
1983 | ListView_SetTextColor(hGridWnd, textColor);
1984 | ListView_SetBkColor(hGridWnd, backColor);
1985 | ListView_SetTextBkColor(hGridWnd, backColor);
1986 |
1987 | SendMessage(hTextWnd, EM_SETBKGNDCOLOR, 0, backColor);
1988 |
1989 | SendMessage(hWnd, WMU_UPDATE_HIGHLIGHT, 0, 0);
1990 | InvalidateRect(hWnd, NULL, TRUE);
1991 | }
1992 | break;
1993 |
1994 | // wParam = tab index
1995 | case WMU_SWITCH_TAB: {
1996 | HWND hTabWnd = GetDlgItem(hWnd, IDC_TAB);
1997 | TabCtrl_SetCurSel(hTabWnd, wParam);
1998 | NMHDR Hdr = {hTabWnd, IDC_TAB, TCN_SELCHANGE};
1999 | SendMessage(hWnd, WM_NOTIFY, IDC_TAB, (LPARAM)&Hdr);
2000 | }
2001 | break;
2002 |
2003 | case WMU_HOT_KEYS: {
2004 | BOOL isCtrl = HIWORD(GetKeyState(VK_CONTROL));
2005 | if (wParam == VK_TAB) {
2006 | HWND hFocus = GetFocus();
2007 | HWND wnds[1000] = {0};
2008 | EnumChildWindows(hWnd, (WNDENUMPROC)cbEnumTabStopChildren, (LPARAM)wnds);
2009 |
2010 | int no = 0;
2011 | while(wnds[no] && wnds[no] != hFocus)
2012 | no++;
2013 |
2014 | int cnt = no;
2015 | while(wnds[cnt])
2016 | cnt++;
2017 |
2018 | no += isCtrl ? -1 : 1;
2019 | SetFocus(wnds[no] && no >= 0 ? wnds[no] : (isCtrl ? wnds[cnt - 1] : wnds[0]));
2020 | }
2021 |
2022 | if (wParam == VK_F1) {
2023 | ShellExecute(0, 0, TEXT("https://github.com/little-brother/xmltab-wlx/wiki"), 0, 0 , SW_SHOW);
2024 | return TRUE;
2025 | }
2026 |
2027 | if (wParam == 0x20 && isCtrl) { // Ctrl + Space
2028 | SendMessage(hWnd, WMU_SHOW_COLUMNS, 0, 0);
2029 | return TRUE;
2030 | }
2031 |
2032 | if (wParam == VK_ESCAPE || wParam == VK_F11 ||
2033 | wParam == VK_F3 || wParam == VK_F5 || wParam == VK_F7 || (isCtrl && wParam == 0x46) || // Ctrl + F
2034 | ((wParam >= 0x31 && wParam <= 0x38) && !getStoredValue(TEXT("disable-num-keys"), 0) && !isCtrl || // 1 - 8
2035 | (wParam == 0x4E || wParam == 0x50) && !getStoredValue(TEXT("disable-np-keys"), 0) || // N, P
2036 | wParam == 0x51 && getStoredValue(TEXT("exit-by-q"), 0)) && // Q
2037 | GetDlgCtrlID(GetFocus()) / 100 * 100 != IDC_HEADER_EDIT) {
2038 | SetFocus(GetParent(hWnd));
2039 | keybd_event(wParam, wParam, KEYEVENTF_EXTENDEDKEY, 0);
2040 |
2041 | return TRUE;
2042 | }
2043 |
2044 | if (isCtrl && wParam >= 0x30 && wParam <= 0x39 && !getStoredValue(TEXT("disable-num-keys"), 0)) {// 0-9
2045 | HWND hTabWnd = GetDlgItem(hWnd, IDC_TAB);
2046 | HWND hGridWnd = GetDlgItem(hTabWnd, IDC_GRID);
2047 | HWND hHeader = ListView_GetHeader(hGridWnd);
2048 | int colCount = Header_GetItemCount(ListView_GetHeader(hGridWnd));
2049 |
2050 | BOOL isCurrent = wParam == 0x30;
2051 | int colNo = isCurrent ? *(int*)GetProp(hWnd, TEXT("CURRENTCOLNO")) : wParam - 0x30 - 1;
2052 | if (colNo < 0 || colNo > colCount - 1 || isCurrent && ListView_GetColumnWidth(hGridWnd, colNo) == 0)
2053 | return FALSE;
2054 |
2055 | if (!isCurrent) {
2056 | int* colOrder = calloc(colCount, sizeof(int));
2057 | Header_GetOrderArray(hHeader, colCount, colOrder);
2058 |
2059 | int hiddenColCount = 0;
2060 | for (int idx = 0; (idx < colCount) && (idx - hiddenColCount <= colNo); idx++)
2061 | hiddenColCount += ListView_GetColumnWidth(hGridWnd, colOrder[idx]) == 0;
2062 |
2063 | colNo = colOrder[colNo + hiddenColCount];
2064 | free(colOrder);
2065 | }
2066 |
2067 | SendMessage(hWnd, WMU_SORT_COLUMN, colNo, 0);
2068 | return TRUE;
2069 | }
2070 |
2071 | return FALSE;
2072 | }
2073 | break;
2074 |
2075 | case WMU_HOT_CHARS: {
2076 | BOOL isCtrl = HIWORD(GetKeyState(VK_CONTROL));
2077 |
2078 | unsigned char scancode = ((unsigned char*)&lParam)[2];
2079 | UINT key = MapVirtualKey(scancode, MAPVK_VSC_TO_VK);
2080 |
2081 | return !_istprint(wParam) && (
2082 | wParam == VK_ESCAPE || wParam == VK_F11 || wParam == VK_F1 ||
2083 | wParam == VK_F3 || wParam == VK_F5 || wParam == VK_F7) ||
2084 | wParam == VK_TAB || wParam == VK_RETURN ||
2085 | isCtrl && key == 0x46 || // Ctrl + F
2086 | getStoredValue(TEXT("exit-by-q"), 0) && key == 0x51 && GetDlgCtrlID(GetFocus()) / 100 * 100 != IDC_HEADER_EDIT; // Q
2087 | }
2088 | break;
2089 | }
2090 |
2091 | return CallWindowProc((WNDPROC)GetProp(hWnd, TEXT("WNDPROC")), hWnd, msg, wParam, lParam);
2092 | }
2093 |
2094 | LRESULT CALLBACK cbHotKey(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
2095 | if (msg == WM_KEYDOWN && SendMessage(getMainWindow(hWnd), WMU_HOT_KEYS, wParam, lParam))
2096 | return 0;
2097 |
2098 | // Prevent beep
2099 | if (msg == WM_CHAR && SendMessage(getMainWindow(hWnd), WMU_HOT_CHARS, wParam, lParam))
2100 | return 0;
2101 |
2102 | return CallWindowProc((WNDPROC)GetProp(hWnd, TEXT("WNDPROC")), hWnd, msg, wParam, lParam);
2103 | }
2104 |
2105 | LRESULT CALLBACK cbNewHeader(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
2106 | if (msg == WM_CTLCOLOREDIT) {
2107 | HWND hMainWnd = getMainWindow(hWnd);
2108 | SetBkColor((HDC)wParam, *(int*)GetProp(hMainWnd, TEXT("FILTERBACKCOLOR")));
2109 | SetTextColor((HDC)wParam, *(int*)GetProp(hMainWnd, TEXT("FILTERTEXTCOLOR")));
2110 | return (INT_PTR)(HBRUSH)GetProp(hMainWnd, TEXT("FILTERBACKBRUSH"));
2111 | }
2112 |
2113 | return CallWindowProc((WNDPROC)GetProp(hWnd, TEXT("WNDPROC")), hWnd, msg, wParam, lParam);
2114 | }
2115 |
2116 | LRESULT CALLBACK cbNewFilterEdit(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
2117 | WNDPROC cbDefault = (WNDPROC)GetProp(hWnd, TEXT("WNDPROC"));
2118 |
2119 | switch(msg){
2120 | case WM_PAINT: {
2121 | cbDefault(hWnd, msg, wParam, lParam);
2122 |
2123 | RECT rc;
2124 | GetClientRect(hWnd, &rc);
2125 | HWND hMainWnd = getMainWindow(hWnd);
2126 | BOOL isDark = *(int*)GetProp(hMainWnd, TEXT("DARKTHEME"));
2127 |
2128 | HDC hDC = GetWindowDC(hWnd);
2129 | HPEN hPen = CreatePen(PS_SOLID, 1, *(int*)GetProp(hMainWnd, TEXT("FILTERBACKCOLOR")));
2130 | HPEN oldPen = SelectObject(hDC, hPen);
2131 | MoveToEx(hDC, 1, 0, 0);
2132 | LineTo(hDC, rc.right - 1, 0);
2133 | LineTo(hDC, rc.right - 1, rc.bottom - 1);
2134 |
2135 | if (isDark) {
2136 | DeleteObject(hPen);
2137 | hPen = CreatePen(PS_SOLID, 1, GetSysColor(COLOR_BTNFACE));
2138 | SelectObject(hDC, hPen);
2139 |
2140 | MoveToEx(hDC, 0, 0, 0);
2141 | LineTo(hDC, 0, rc.bottom);
2142 | MoveToEx(hDC, 0, rc.bottom - 1, 0);
2143 | LineTo(hDC, rc.right, rc.bottom - 1);
2144 | MoveToEx(hDC, 0, rc.bottom - 2, 0);
2145 | LineTo(hDC, rc.right, rc.bottom - 2);
2146 | }
2147 |
2148 | SelectObject(hDC, oldPen);
2149 | DeleteObject(hPen);
2150 | ReleaseDC(hWnd, hDC);
2151 |
2152 | return 0;
2153 | }
2154 | break;
2155 |
2156 | case WM_SETFOCUS: {
2157 | SetProp(getMainWindow(hWnd), TEXT("LASTFOCUS"), hWnd);
2158 | }
2159 | break;
2160 |
2161 | case WM_KEYDOWN: {
2162 | HWND hMainWnd = getMainWindow(hWnd);
2163 | if (wParam == VK_RETURN) {
2164 | SendMessage(hMainWnd, WMU_UPDATE_RESULTSET, 0, 0);
2165 | return 0;
2166 | }
2167 |
2168 | if (SendMessage(hMainWnd, WMU_HOT_KEYS, wParam, lParam))
2169 | return 0;
2170 | }
2171 | break;
2172 |
2173 | // Prevent beep
2174 | case WM_CHAR: {
2175 | if (SendMessage(getMainWindow(hWnd), WMU_HOT_CHARS, wParam, lParam))
2176 | return 0;
2177 | }
2178 | break;
2179 |
2180 | case WM_DESTROY: {
2181 | RemoveProp(hWnd, TEXT("WNDPROC"));
2182 | }
2183 | break;
2184 | }
2185 |
2186 | return CallWindowProc(cbDefault, hWnd, msg, wParam, lParam);
2187 | }
2188 |
2189 | LRESULT CALLBACK cbNewTab(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
2190 | if (msg == WM_NOTIFY) {
2191 | NMHDR* pHdr = (LPNMHDR)lParam;
2192 | if (pHdr->idFrom == IDC_GRID && pHdr->code == (UINT)NM_CUSTOMDRAW)
2193 | return SendMessage(GetParent(hWnd), msg, wParam, lParam);
2194 | }
2195 |
2196 | if (msg == WM_KEYDOWN && SendMessage(getMainWindow(hWnd), WMU_HOT_KEYS, wParam, lParam))
2197 | return 0;
2198 |
2199 | // Prevent beep
2200 | if (msg == WM_CHAR && SendMessage(getMainWindow(hWnd), WMU_HOT_CHARS, wParam, lParam))
2201 | return 0;
2202 |
2203 | return CallWindowProc((WNDPROC)GetProp(hWnd, TEXT("WNDPROC")), hWnd, msg, wParam, lParam);
2204 | }
2205 |
2206 | LRESULT CALLBACK cbNewText(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
2207 | if (msg == EM_SETZOOM)
2208 | return 0;
2209 |
2210 | if (msg == WM_MOUSEWHEEL && LOWORD(wParam) == MK_CONTROL) {
2211 | HWND hTabWnd = GetParent(hWnd);
2212 | SendMessage(GetParent(hTabWnd), msg, wParam, lParam);
2213 | return 0;
2214 | }
2215 |
2216 | if (msg == WM_KEYDOWN && SendMessage(getMainWindow(hWnd), WMU_HOT_KEYS, wParam, lParam))
2217 | return 0;
2218 |
2219 | // Prevent beep
2220 | if (msg == WM_CHAR && SendMessage(getMainWindow(hWnd), WMU_HOT_CHARS, wParam, lParam))
2221 | return 0;
2222 |
2223 | return CallWindowProc((WNDPROC)GetProp(hWnd, TEXT("WNDPROC")), hWnd, msg, wParam, lParam);
2224 | }
2225 |
2226 | HTREEITEM addNode(HWND hTreeWnd, HTREEITEM hParentItem, xml_element* node) {
2227 | if (!node)
2228 | return FALSE;
2229 |
2230 | HTREEITEM hItem = 0;
2231 | xml_element* subnode = node->first_child;
2232 | if (node->key) {
2233 | const char* value = node->value ? node->value : node->child_count == 1 && subnode && !subnode->key ? subnode->value : 0;
2234 | char* buf8 = calloc(strlen(node->key) + (value ? strlen(value) : 0) + 10, sizeof(char));
2235 | if (node->key && strncmp(node->key, "![CDATA[", 8) == 0) {
2236 | sprintf(buf8, "%s (%.2fKb)", XML_CDATA, strlen(node->key)/1024.0);
2237 | } else if (node->key && strncmp(node->key, "!--", 3) == 0) {
2238 | sprintf(buf8, XML_COMMENT);
2239 | } else if (!isEmpty(value)) {
2240 | // trim value
2241 | int l = 0, r = 0;
2242 | int len = strlen(value);
2243 | for (int i = 0; strchr(WHITESPACE, value[i]) && i < len; i++)
2244 | l++;
2245 | for (int i = len - 1; strchr(WHITESPACE, value[i]) && i > 0; i--)
2246 | r++;
2247 | len -= l + r;
2248 | sprintf(buf8, "%s = %.*s", node->key, len, value + l);
2249 | } else if (isEmpty(value) && node->child_count == 0 && getStoredValue(TEXT("show-empty"), 0)) {
2250 | sprintf(buf8, "%s = %s", node->key, "");
2251 | } else {
2252 | sprintf(buf8, "%s", node->key);
2253 | }
2254 |
2255 | TCHAR* name16 = utf8to16(buf8);
2256 | hItem = TreeView_AddItem(hTreeWnd, name16, hParentItem, (LPARAM)node);
2257 | free(name16);
2258 | free(buf8);
2259 | } else if (node->value && !isEmpty(node->value)) {
2260 | hItem = TreeView_AddItem(hTreeWnd, TEXT(XML_TEXT), hParentItem, (LPARAM)node);
2261 | }
2262 |
2263 | BOOL hasSubnode = FALSE;
2264 | while (!hasSubnode && subnode) {
2265 | hasSubnode = subnode->key != NULL;
2266 | subnode = subnode->next;
2267 | }
2268 | if (hasSubnode)
2269 | TreeView_AddItem(hTreeWnd, LOADING, hItem, (LPARAM)subnode);
2270 |
2271 | return hItem;
2272 | }
2273 |
2274 | void showNode(HWND hTreeWnd, xml_element* node) {
2275 | if (node == NULL)
2276 | return;
2277 |
2278 | xml_element* parent = node->parent;
2279 | if (!parent)
2280 | return;
2281 |
2282 | if (!parent->userdata)
2283 | showNode(hTreeWnd, parent);
2284 |
2285 | TreeView_Expand(hTreeWnd, (HTREEITEM)parent->userdata, TVE_EXPAND);
2286 | }
2287 |
2288 | void highlightText (HWND hWnd, TCHAR* text) {
2289 | BOOL inTag = FALSE;
2290 | TCHAR q = 0;
2291 | BOOL isValue = FALSE;
2292 |
2293 | HWND hMainWnd = getMainWindow(hWnd);
2294 | COLORREF textColor = *(int*)GetProp(hMainWnd, TEXT("XMLTEXTCOLOR"));
2295 | COLORREF tagColor = *(int*)GetProp(hMainWnd, TEXT("XMLTAGCOLOR"));
2296 | COLORREF stringColor = *(int*)GetProp(hMainWnd, TEXT("XMLSTRINGCOLOR"));
2297 | COLORREF valueColor = *(int*)GetProp(hMainWnd, TEXT("XMLVALUECOLOR"));
2298 | COLORREF cdataColor = *(int*)GetProp(hMainWnd, TEXT("XMLCDATACOLOR"));
2299 | COLORREF commentColor = *(int*)GetProp(hMainWnd, TEXT("XMLCOMMENTCOLOR"));
2300 | BOOL useBold = getStoredValue(TEXT("font-use-bold"), 0);
2301 |
2302 | CHARFORMAT2 cf2 = {0};
2303 | cf2.cbSize = sizeof(CHARFORMAT2) ;
2304 | cf2.dwMask = CFM_COLOR | CFM_BOLD;
2305 |
2306 | int len = _tcslen(text);
2307 | int pos = 0;
2308 | while (pos < len) {
2309 | int start = pos;
2310 | TCHAR c = text[pos];
2311 |
2312 | cf2.crTextColor = textColor;
2313 | cf2.dwEffects = 0;
2314 |
2315 | if (c == TEXT('\'') || c == TEXT('"')) {
2316 | TCHAR* p = _tcschr(text + pos + 1, c);
2317 | if (p != NULL) {
2318 | pos = p - text;
2319 | cf2.crTextColor = stringColor;
2320 | }
2321 | } else if (c == TEXT('<') && _tcsncmp(text + pos, TEXT(""));
2323 | if (p != NULL) {
2324 | pos = p - text + 2;
2325 | cf2.crTextColor = cdataColor;
2326 | }
2327 | } else if (c == TEXT('<') && _tcsncmp(text + pos, TEXT(""));
2329 | if (p != NULL) {
2330 | pos = p - text + 2;
2331 | cf2.crTextColor = commentColor;
2332 | }
2333 | } else if (c == TEXT('<')) {
2334 | TCHAR* p = _tcspbrk(text + pos, TEXT(" \t\r\n>"));
2335 | if (p != NULL) {
2336 | pos = p - text;
2337 | cf2.crTextColor = tagColor;
2338 | cf2.dwEffects = useBold ? CFM_BOLD : 0;
2339 | }
2340 | } else if (c == TEXT('>')) {
2341 | if (pos > 0 && text[pos - 1] == TEXT('/'))
2342 | start--;
2343 |
2344 | cf2.crTextColor = tagColor;
2345 | cf2.dwEffects = useBold ? CFM_BOLD : 0;
2346 | } else if (pos > 0 && text[pos - 1] == TEXT('>')) {
2347 | TCHAR* p = _tcschr(text + pos + 1, TEXT('<'));
2348 | if (p != NULL) {
2349 | pos = p - text - 1;
2350 | cf2.crTextColor = valueColor;
2351 | }
2352 | }
2353 |
2354 | SendMessage(hWnd, EM_SETSEL, start, pos + 1);
2355 | SendMessage(hWnd, EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM) &cf2);
2356 | pos++;
2357 | }
2358 | }
2359 |
2360 | char* formatXML(const char* data) {
2361 | if (!data)
2362 | return 0;
2363 |
2364 | int len = strlen(data);
2365 | int bLen = len;
2366 | char* buf = calloc(bLen, sizeof(char));
2367 |
2368 | BOOL inTag = FALSE;
2369 | BOOL isAttr = FALSE;
2370 | BOOL isValue = FALSE;
2371 | char quote = 0;
2372 | BOOL isOpenTag = FALSE;
2373 | BOOL isCData = FALSE;
2374 |
2375 | int bPos = 0;
2376 | int pos = 0;
2377 | int level = 0;
2378 | while (pos < len) {
2379 | char c = data[pos];
2380 | char p = bPos > 0 ? buf[bPos - 1] : 0;
2381 | char n = data[pos + 1];
2382 |
2383 | BOOL isSave = FALSE;
2384 | BOOL isSpace = FALSE;
2385 | BOOL isNun = strchr(WHITESPACE, c) != 0;
2386 | BOOL isCDataStart = FALSE;
2387 |
2388 | if (1000 == 1000 && isCData) {
2389 | isSave = TRUE;
2390 | isCData = !(data[pos - 2] == ']' && data[pos - 1] == ']' && c == '>');
2391 | } else if (quote && c != quote) {
2392 | isSave = TRUE;
2393 | } else if (quote && c == quote) {
2394 | isSave = TRUE;
2395 | quote = 0;
2396 | } else if (c == '<') {
2397 | inTag = TRUE;
2398 | isSave = TRUE;
2399 | isValue = FALSE;
2400 | isOpenTag = n != '/';
2401 | isCDataStart = strncmp(data + pos, "') {
2406 | isSave = TRUE;
2407 | inTag = FALSE;
2408 | isAttr = FALSE;
2409 | isValue = FALSE;
2410 | if (p == '/' || p == '-')
2411 | level--;
2412 | quote = 0;
2413 | } else if (inTag && c == '=') {
2414 | isSave = TRUE;
2415 | isSpace = TRUE;
2416 | } else if (inTag && !isNun) {
2417 | isSave = TRUE;
2418 | isSpace = !isAttr && p != '<' && p != '/' || p == '=';
2419 | isAttr = TRUE;
2420 | } else if (inTag && isNun) {
2421 | isAttr = FALSE;
2422 | } else if (!inTag && p == '>') {
2423 | isValue = FALSE;
2424 | isSave = !isNun;
2425 | } else if (!inTag && !isValue && !isNun) {
2426 | isSave = TRUE;
2427 | isValue = TRUE;
2428 | } else if (!inTag && isValue && !isNun) {
2429 | isSave = TRUE;
2430 | } else if (!inTag && isValue && isNun) {
2431 | int n = strspn(data + pos, WHITESPACE);
2432 | BOOL isEmptyTail = data[pos + n] == '<';
2433 | if (isEmptyTail) {
2434 | isValue = FALSE;
2435 | } else {
2436 | isSave = TRUE;
2437 | }
2438 | }
2439 |
2440 | if (isSave && bPos > 0 && !quote && (c == '<' || isCDataStart || p == '>' && !isCData)) {
2441 | buf[bPos] = '\n';
2442 | bPos++;
2443 |
2444 | for (int l = 0; l < level; l++) {
2445 | buf[bPos] = '\t';
2446 | bPos++;
2447 | }
2448 | }
2449 |
2450 | if (isSpace) {
2451 | buf[bPos] = ' ';
2452 | bPos++;
2453 | }
2454 |
2455 | if (isSave) {
2456 | buf[bPos] = data[pos];
2457 | bPos++;
2458 |
2459 | if (bLen - bPos < 1000) {
2460 | bLen += 32000;
2461 | buf = realloc(buf, bLen);
2462 | }
2463 | }
2464 |
2465 | if (c == '<' && isOpenTag && !isCData)
2466 | level++;
2467 |
2468 | pos++;
2469 | }
2470 | buf[bPos] = 0;
2471 |
2472 | return buf;
2473 | }
2474 |
2475 | HWND getMainWindow(HWND hWnd) {
2476 | HWND hMainWnd = hWnd;
2477 | while (hMainWnd && GetDlgCtrlID(hMainWnd) != IDC_MAIN)
2478 | hMainWnd = GetParent(hMainWnd);
2479 | return hMainWnd;
2480 | }
2481 |
2482 | void setStoredValue(TCHAR* name, int value) {
2483 | TCHAR buf[128];
2484 | _sntprintf(buf, 128, TEXT("%i"), value);
2485 | WritePrivateProfileString(APP_NAME, name, buf, iniPath);
2486 | }
2487 |
2488 | int getStoredValue(TCHAR* name, int defValue) {
2489 | TCHAR buf[128];
2490 | return GetPrivateProfileString(APP_NAME, name, NULL, buf, 128, iniPath) ? _ttoi(buf) : defValue;
2491 | }
2492 |
2493 | TCHAR* getStoredString(TCHAR* name, TCHAR* defValue) {
2494 | TCHAR* buf = calloc(256, sizeof(TCHAR));
2495 | if (0 == GetPrivateProfileString(APP_NAME, name, NULL, buf, 128, iniPath) && defValue)
2496 | _tcsncpy(buf, defValue, 255);
2497 | return buf;
2498 | }
2499 |
2500 | int CALLBACK cbEnumTabStopChildren (HWND hWnd, LPARAM lParam) {
2501 | if (GetWindowLong(hWnd, GWL_STYLE) & WS_TABSTOP && IsWindowVisible(hWnd)) {
2502 | int no = 0;
2503 | HWND* wnds = (HWND*)lParam;
2504 | while (wnds[no])
2505 | no++;
2506 | wnds[no] = hWnd;
2507 | }
2508 |
2509 | return TRUE;
2510 | }
2511 |
2512 | TCHAR* utf8to16(const char* in) {
2513 | TCHAR *out;
2514 | if (!in || strlen(in) == 0) {
2515 | out = (TCHAR*)calloc (1, sizeof (TCHAR));
2516 | } else {
2517 | DWORD size = MultiByteToWideChar(CP_UTF8, 0, in, -1, NULL, 0);
2518 | out = (TCHAR*)calloc (size, sizeof (TCHAR));
2519 | MultiByteToWideChar(CP_UTF8, 0, in, -1, out, size);
2520 | }
2521 | return out;
2522 | }
2523 |
2524 | char* utf16to8(const TCHAR* in) {
2525 | char* out;
2526 | if (!in || _tcslen(in) == 0) {
2527 | out = (char*)calloc (1, sizeof(char));
2528 | } else {
2529 | int len = WideCharToMultiByte(CP_UTF8, 0, in, -1, NULL, 0, 0, 0);
2530 | out = (char*)calloc (len, sizeof(char));
2531 | WideCharToMultiByte(CP_UTF8, 0, in, -1, out, len, 0, 0);
2532 | }
2533 | return out;
2534 | }
2535 |
2536 | int findString(TCHAR* text, TCHAR* word, BOOL isMatchCase, BOOL isWholeWords) {
2537 | if (!text || !word)
2538 | return -1;
2539 |
2540 | int res = -1;
2541 | int tlen = _tcslen(text);
2542 | int wlen = _tcslen(word);
2543 | if (!tlen || !wlen)
2544 | return res;
2545 |
2546 | if (!isMatchCase) {
2547 | TCHAR* ltext = calloc(tlen + 1, sizeof(TCHAR));
2548 | _tcsncpy(ltext, text, tlen);
2549 | text = _tcslwr(ltext);
2550 |
2551 | TCHAR* lword = calloc(wlen + 1, sizeof(TCHAR));
2552 | _tcsncpy(lword, word, wlen);
2553 | word = _tcslwr(lword);
2554 | }
2555 |
2556 | if (isWholeWords) {
2557 | for (int pos = 0; (res == -1) && (pos <= tlen - wlen); pos++)
2558 | res = (pos == 0 || pos > 0 && !_istalnum(text[pos - 1])) &&
2559 | !_istalnum(text[pos + wlen]) &&
2560 | _tcsncmp(text + pos, word, wlen) == 0 ? pos : -1;
2561 | } else {
2562 | TCHAR* s = _tcsstr(text, word);
2563 | res = s != NULL ? s - text : -1;
2564 | }
2565 |
2566 | if (!isMatchCase) {
2567 | free(text);
2568 | free(word);
2569 | }
2570 |
2571 | return res;
2572 | }
2573 |
2574 | int findString8(char* text, char* word, BOOL isMatchCase, BOOL isWholeWords) {
2575 | if (!text || !word)
2576 | return -1;
2577 |
2578 | int res = -1;
2579 | int tlen = strlen(text);
2580 | int wlen = strlen(word);
2581 | if (!tlen || !wlen)
2582 | return res;
2583 |
2584 | if (!isMatchCase) {
2585 | char* ltext = calloc(tlen + 1, sizeof(char));
2586 | strncpy(ltext, text, tlen);
2587 | text = strlwr(ltext);
2588 |
2589 | char* lword = calloc(wlen + 1, sizeof(char));
2590 | strncpy(lword, word, wlen);
2591 | word = strlwr(lword);
2592 | }
2593 |
2594 | if (isWholeWords) {
2595 | for (int pos = 0; (res == -1) && (pos <= tlen - wlen); pos++)
2596 | res = (pos == 0 || pos > 0 && !isalnum(text[pos - 1])) &&
2597 | !isalnum(text[pos + wlen]) &&
2598 | strncmp(text + pos, word, wlen) == 0 ? pos : -1;
2599 | } else {
2600 | char* s = strstr(text, word);
2601 | res = s != NULL ? s - text : -1;
2602 | }
2603 |
2604 | if (!isMatchCase) {
2605 | free(text);
2606 | free(word);
2607 | }
2608 |
2609 | return res;
2610 | }
2611 |
2612 | BOOL hasString (const TCHAR* str, const TCHAR* sub, BOOL isCaseSensitive) {
2613 | BOOL res = FALSE;
2614 |
2615 | TCHAR* lstr = _tcsdup(str);
2616 | _tcslwr(lstr);
2617 | TCHAR* lsub = _tcsdup(sub);
2618 | _tcslwr(lsub);
2619 | res = isCaseSensitive ? _tcsstr(str, sub) != 0 : _tcsstr(lstr, lsub) != 0;
2620 | free(lstr);
2621 | free(lsub);
2622 |
2623 | return res;
2624 | };
2625 |
2626 | TCHAR* extractUrl(TCHAR* data) {
2627 | int len = data ? _tcslen(data) : 0;
2628 | int start = len;
2629 | int end = len;
2630 |
2631 | TCHAR* url = calloc(len + 10, sizeof(TCHAR));
2632 |
2633 | TCHAR* slashes = _tcsstr(data, TEXT("://"));
2634 | if (slashes) {
2635 | start = len - _tcslen(slashes);
2636 | end = start + 3;
2637 | for (; start > 0 && _istalpha(data[start - 1]); start--);
2638 | for (; end < len && data[end] != TEXT(' ') && data[end] != TEXT('"') && data[end] != TEXT('\''); end++);
2639 | _tcsncpy(url, data + start, end - start);
2640 |
2641 | } else if (_tcschr(data, TEXT('.'))) {
2642 | _sntprintf(url, len + 10, TEXT("https://%ls"), data);
2643 | }
2644 |
2645 | return url;
2646 | }
2647 |
2648 | // https://stackoverflow.com/a/25023604/6121703
2649 | int detectCodePage(const unsigned char *data) {
2650 | return strncmp(data, "\xEF\xBB\xBF", 3) == 0 ? CP_UTF8 : // BOM
2651 | strncmp(data, "\xFE\xFF", 2) == 0 ? CP_UTF16BE : // BOM
2652 | strncmp(data, "\xFF\xFE", 2) == 0 ? CP_UTF16LE : // BOM
2653 | strncmp(data, "\x00\x3C", 2) == 0 ? CP_UTF16BE : // <
2654 | strncmp(data, "\x3C\x00", 2) == 0 ? CP_UTF16LE : // <
2655 | isUtf8(data) ? CP_UTF8 :
2656 | CP_ACP;
2657 | }
2658 |
2659 | void setClipboardText(const TCHAR* text) {
2660 | int len = (_tcslen(text) + 1) * sizeof(TCHAR);
2661 | HGLOBAL hMem = GlobalAlloc(GMEM_MOVEABLE, len);
2662 | memcpy(GlobalLock(hMem), text, len);
2663 | GlobalUnlock(hMem);
2664 | OpenClipboard(0);
2665 | EmptyClipboard();
2666 | SetClipboardData(CF_UNICODETEXT, hMem);
2667 | CloseClipboard();
2668 | }
2669 |
2670 | BOOL isNumber(const TCHAR* val) {
2671 | int len = _tcslen(val);
2672 | BOOL res = TRUE;
2673 | int pCount = 0;
2674 | for (int i = 0; res && i < len; i++) {
2675 | pCount += val[i] == TEXT('.');
2676 | res = _istdigit(val[i]) || val[i] == TEXT('.');
2677 | }
2678 | return res && pCount < 2;
2679 | }
2680 |
2681 | BOOL isEmpty (const char* s) {
2682 | BOOL res = TRUE;
2683 | for (int i = 0; s && res && i < strlen(s); i++)
2684 | res = strchr(WHITESPACE, s[i]) != NULL;
2685 |
2686 | return res;
2687 | }
2688 |
2689 | // https://stackoverflow.com/a/1031773/6121703
2690 | BOOL isUtf8(const char * string) {
2691 | if (!string)
2692 | return FALSE;
2693 |
2694 | const unsigned char * bytes = (const unsigned char *)string;
2695 | while (*bytes) {
2696 | if((bytes[0] == 0x09 || bytes[0] == 0x0A || bytes[0] == 0x0D || (0x20 <= bytes[0] && bytes[0] <= 0x7E))) {
2697 | bytes += 1;
2698 | continue;
2699 | }
2700 |
2701 | if (((0xC2 <= bytes[0] && bytes[0] <= 0xDF) && (0x80 <= bytes[1] && bytes[1] <= 0xBF))) {
2702 | bytes += 2;
2703 | continue;
2704 | }
2705 |
2706 | if ((bytes[0] == 0xE0 && (0xA0 <= bytes[1] && bytes[1] <= 0xBF) && (0x80 <= bytes[2] && bytes[2] <= 0xBF)) ||
2707 | (((0xE1 <= bytes[0] && bytes[0] <= 0xEC) || bytes[0] == 0xEE || bytes[0] == 0xEF) && (0x80 <= bytes[1] && bytes[1] <= 0xBF) && (0x80 <= bytes[2] && bytes[2] <= 0xBF)) ||
2708 | (bytes[0] == 0xED && (0x80 <= bytes[1] && bytes[1] <= 0x9F) && (0x80 <= bytes[2] && bytes[2] <= 0xBF))
2709 | ) {
2710 | bytes += 3;
2711 | continue;
2712 | }
2713 |
2714 | if ((bytes[0] == 0xF0 && (0x90 <= bytes[1] && bytes[1] <= 0xBF) && (0x80 <= bytes[2] && bytes[2] <= 0xBF) && (0x80 <= bytes[3] && bytes[3] <= 0xBF)) ||
2715 | ((0xF1 <= bytes[0] && bytes[0] <= 0xF3) && (0x80 <= bytes[1] && bytes[1] <= 0xBF) && (0x80 <= bytes[2] && bytes[2] <= 0xBF) && (0x80 <= bytes[3] && bytes[3] <= 0xBF)) ||
2716 | (bytes[0] == 0xF4 && (0x80 <= bytes[1] && bytes[1] <= 0x8F) && (0x80 <= bytes[2] && bytes[2] <= 0xBF) && (0x80 <= bytes[3] && bytes[3] <= 0xBF))
2717 | ) {
2718 | bytes += 4;
2719 | continue;
2720 | }
2721 |
2722 | return FALSE;
2723 | }
2724 |
2725 | return TRUE;
2726 | }
2727 |
2728 | void mergeSortJoiner(int indexes[], void* data, int l, int m, int r, BOOL isBackward, BOOL isNums) {
2729 | int n1 = m - l + 1;
2730 | int n2 = r - m;
2731 |
2732 | int* L = calloc(n1, sizeof(int));
2733 | int* R = calloc(n2, sizeof(int));
2734 |
2735 | for (int i = 0; i < n1; i++)
2736 | L[i] = indexes[l + i];
2737 | for (int j = 0; j < n2; j++)
2738 | R[j] = indexes[m + 1 + j];
2739 |
2740 | int i = 0, j = 0, k = l;
2741 | while (i < n1 && j < n2) {
2742 | int cmp = isNums ? ((double*)data)[L[i]] <= ((double*)data)[R[j]] : _tcscmp(((TCHAR**)data)[L[i]], ((TCHAR**)data)[R[j]]) <= 0;
2743 | if (isBackward)
2744 | cmp = !cmp;
2745 |
2746 | if (cmp) {
2747 | indexes[k] = L[i];
2748 | i++;
2749 | } else {
2750 | indexes[k] = R[j];
2751 | j++;
2752 | }
2753 | k++;
2754 | }
2755 |
2756 | while (i < n1) {
2757 | indexes[k] = L[i];
2758 | i++;
2759 | k++;
2760 | }
2761 |
2762 | while (j < n2) {
2763 | indexes[k] = R[j];
2764 | j++;
2765 | k++;
2766 | }
2767 |
2768 | free(L);
2769 | free(R);
2770 | }
2771 |
2772 | void mergeSort(int indexes[], void* data, int l, int r, BOOL isBackward, BOOL isNums) {
2773 | if (l < r) {
2774 | int m = l + (r - l) / 2;
2775 | mergeSort(indexes, data, l, m, isBackward, isNums);
2776 | mergeSort(indexes, data, m + 1, r, isBackward, isNums);
2777 | mergeSortJoiner(indexes, data, l, m, r, isBackward, isNums);
2778 | }
2779 | }
2780 |
2781 | HTREEITEM TreeView_AddItem (HWND hTreeWnd, TCHAR* caption, HTREEITEM parent, LPARAM lParam) {
2782 | TVITEM tvi = {0};
2783 | TVINSERTSTRUCT tvins = {0};
2784 | tvi.mask = TVIF_TEXT | TVIF_PARAM;
2785 | tvi.pszText = caption;
2786 | tvi.cchTextMax = _tcslen(caption) + 1;
2787 | tvi.lParam = lParam;
2788 |
2789 | tvins.item = tvi;
2790 | tvins.hInsertAfter = TVI_FIRST;
2791 | tvins.hParent = parent;
2792 | return (HTREEITEM)SendMessage(hTreeWnd, TVM_INSERTITEM, 0, (LPARAM)(LPTVINSERTSTRUCT)&tvins);
2793 | };
2794 |
2795 | int TreeView_GetItemText(HWND hTreeWnd, HTREEITEM hItem, TCHAR* buf, int maxLen) {
2796 | TV_ITEM tv = {0};
2797 | tv.mask = TVIF_TEXT;
2798 | tv.hItem = hItem;
2799 | tv.cchTextMax = maxLen;
2800 | tv.pszText = buf;
2801 | return TreeView_GetItem(hTreeWnd, &tv);
2802 | }
2803 |
2804 | LPARAM TreeView_GetItemParam(HWND hTreeWnd, HTREEITEM hItem) {
2805 | TV_ITEM tv = {0};
2806 | tv.mask = TVIF_PARAM;
2807 | tv.hItem = hItem;
2808 |
2809 | return TreeView_GetItem(hTreeWnd, &tv) ? tv.lParam : 0;
2810 | }
2811 |
2812 | int TreeView_SetItemText(HWND hTreeWnd, HTREEITEM hItem, TCHAR* text) {
2813 | TV_ITEM tv = {0};
2814 | tv.mask = TVIF_TEXT;
2815 | tv.hItem = hItem;
2816 | tv.cchTextMax = _tcslen(text) + 1;
2817 | tv.pszText = text;
2818 | return TreeView_SetItem(hTreeWnd, &tv);
2819 | }
2820 |
2821 | int ListView_AddColumn(HWND hListWnd, TCHAR* colName) {
2822 | int colNo = Header_GetItemCount(ListView_GetHeader(hListWnd));
2823 | LVCOLUMN lvc = {0};
2824 | lvc.mask = LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM;
2825 | lvc.iSubItem = colNo;
2826 | lvc.pszText = colName;
2827 | lvc.cchTextMax = _tcslen(colName) + 1;
2828 | lvc.cx = 100;
2829 | return ListView_InsertColumn(hListWnd, colNo, &lvc);
2830 | }
2831 |
2832 | int Header_GetItemText(HWND hWnd, int i, TCHAR* pszText, int cchTextMax) {
2833 | if (i < 0)
2834 | return FALSE;
2835 |
2836 | TCHAR* buf = calloc(cchTextMax + 1, sizeof(TCHAR));
2837 |
2838 | HDITEM hdi = {0};
2839 | hdi.mask = HDI_TEXT;
2840 | hdi.pszText = buf;
2841 | hdi.cchTextMax = cchTextMax;
2842 | int rc = Header_GetItem(hWnd, i, &hdi);
2843 |
2844 | _tcsncpy(pszText, buf, cchTextMax);
2845 | free(buf);
2846 | return rc;
2847 | }
2848 |
2849 | void Menu_SetItemState(HMENU hMenu, UINT wID, UINT fState) {
2850 | MENUITEMINFO mii = {0};
2851 | mii.cbSize = sizeof(MENUITEMINFO);
2852 | mii.fMask = MIIM_STATE;
2853 | mii.fState = fState;
2854 | SetMenuItemInfo(hMenu, wID, FALSE, &mii);
2855 | }
--------------------------------------------------------------------------------