├── 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 | ![View](xmltab.png) 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, ""}, 38 | {TAG_PI, ""}, 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 | } --------------------------------------------------------------------------------